aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/OrphanPayload.wxs11
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/PackageInMultipleContainers.wxs14
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoAttachedContainer.wxs17
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/TestData/Container/LayoutPayloadInContainer.wxs28
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/TestData/Container/PayloadInMultipleContainers.wxs28
-rw-r--r--src/wix/Custom.Build.props6
-rw-r--r--src/wix/Directory.Build.props27
-rw-r--r--src/wix/Directory.Build.targets51
-rw-r--r--src/wix/Directory.csproj.props13
-rw-r--r--src/wix/Directory.csproj.targets26
-rw-r--r--src/wix/README.md3
-rw-r--r--src/wix/WixToolset.Core.Burn/Bind/BaseSearchFacade.cs27
-rw-r--r--src/wix/WixToolset.Core.Burn/Bind/BindBundleCommand.cs650
-rw-r--r--src/wix/WixToolset.Core.Burn/Bind/ExtensionSearchFacade.cs24
-rw-r--r--src/wix/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs237
-rw-r--r--src/wix/WixToolset.Core.Burn/Bind/LegacySearchFacade.cs185
-rw-r--r--src/wix/WixToolset.Core.Burn/Bind/ProcessBundleSoftwareTagsCommand.cs133
-rw-r--r--src/wix/WixToolset.Core.Burn/Bind/ProcessDependencyProvidersCommand.cs147
-rw-r--r--src/wix/WixToolset.Core.Burn/Bind/ResolveDownloadUrlsCommand.cs128
-rw-r--r--src/wix/WixToolset.Core.Burn/Bind/SetVariableSearchFacade.cs48
-rw-r--r--src/wix/WixToolset.Core.Burn/BundleBackend.cs77
-rw-r--r--src/wix/WixToolset.Core.Burn/Bundles/AutomaticallySlipstreamPatchesCommand.cs117
-rw-r--r--src/wix/WixToolset.Core.Burn/Bundles/BundleHashAlgorithm.cs30
-rw-r--r--src/wix/WixToolset.Core.Burn/Bundles/BurnCommon.cs385
-rw-r--r--src/wix/WixToolset.Core.Burn/Bundles/BurnReader.cs212
-rw-r--r--src/wix/WixToolset.Core.Burn/Bundles/BurnWriter.cs245
-rw-r--r--src/wix/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs290
-rw-r--r--src/wix/WixToolset.Core.Burn/Bundles/CreateBundleExeCommand.cs325
-rw-r--r--src/wix/WixToolset.Core.Burn/Bundles/CreateBundleExtensionManifestCommand.cs99
-rw-r--r--src/wix/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs700
-rw-r--r--src/wix/WixToolset.Core.Burn/Bundles/CreateContainerCommand.cs70
-rw-r--r--src/wix/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs151
-rw-r--r--src/wix/WixToolset.Core.Burn/Bundles/DetectPayloadCollisionsCommand.cs137
-rw-r--r--src/wix/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs181
-rw-r--r--src/wix/WixToolset.Core.Burn/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs171
-rw-r--r--src/wix/WixToolset.Core.Burn/Bundles/OrderSearchesCommand.cs367
-rw-r--r--src/wix/WixToolset.Core.Burn/Bundles/PackageFacade.cs25
-rw-r--r--src/wix/WixToolset.Core.Burn/Bundles/ProcessExePackageCommand.cs39
-rw-r--r--src/wix/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs558
-rw-r--r--src/wix/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs183
-rw-r--r--src/wix/WixToolset.Core.Burn/Bundles/ProcessMsuPackageCommand.cs37
-rw-r--r--src/wix/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs108
-rw-r--r--src/wix/WixToolset.Core.Burn/BurnBackendErrors.cs72
-rw-r--r--src/wix/WixToolset.Core.Burn/BurnBackendFactory.cs30
-rw-r--r--src/wix/WixToolset.Core.Burn/BurnBackendWarnings.cs36
-rw-r--r--src/wix/WixToolset.Core.Burn/BurnExtensionFactory.cs22
-rw-r--r--src/wix/WixToolset.Core.Burn/ExtensibilityServices/BurnBackendHelper.cs214
-rw-r--r--src/wix/WixToolset.Core.Burn/ExtensibilityServices/PayloadHarvester.cs68
-rw-r--r--src/wix/WixToolset.Core.Burn/IInternalBurnBackendHelper.cs14
-rw-r--r--src/wix/WixToolset.Core.Burn/ISearchFacade.cs15
-rw-r--r--src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleCommand.cs54
-rw-r--r--src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleEngineCommand.cs63
-rw-r--r--src/wix/WixToolset.Core.Burn/Interfaces/IPayloadHarvester.cs23
-rw-r--r--src/wix/WixToolset.Core.Burn/RowIndexedList.cs299
-rw-r--r--src/wix/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj39
-rw-r--r--src/wix/WixToolset.Core.Burn/WixToolsetCoreServiceProviderExtensions.cs45
-rw-r--r--src/wix/WixToolset.Core.ExtensionCache/CachedExtension.cs20
-rw-r--r--src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManager.cs248
-rw-r--r--src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManagerCommand.cs181
-rw-r--r--src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManagerExtensionCommandLine.cs41
-rw-r--r--src/wix/WixToolset.Core.ExtensionCache/ExtensionCacheManagerExtensionFactory.cs30
-rw-r--r--src/wix/WixToolset.Core.ExtensionCache/WixToolset.Core.ExtensionCache.csproj29
-rw-r--r--src/wix/WixToolset.Core.ExtensionCache/WixToolsetCoreServiceProviderExtensions.cs36
-rw-r--r--src/wix/WixToolset.Core.TestPackage/BundleExtractor.cs139
-rw-r--r--src/wix/WixToolset.Core.TestPackage/ExtractBAContainerResult.cs116
-rw-r--r--src/wix/WixToolset.Core.TestPackage/TestMessageListener.cs55
-rw-r--r--src/wix/WixToolset.Core.TestPackage/WixRunner.cs88
-rw-r--r--src/wix/WixToolset.Core.TestPackage/WixRunnerResult.cs62
-rw-r--r--src/wix/WixToolset.Core.TestPackage/WixToolset.Core.TestPackage.csproj34
-rw-r--r--src/wix/WixToolset.Core.TestPackage/XmlNodeExtensions.cs90
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/AddBackSuppressedSequenceTablesCommand.cs52
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/AddCreateFoldersCommand.cs38
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/AddRequiredStandardDirectories.cs95
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/AssemblyName.cs60
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/AssemblyNameReader.cs214
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs302
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/AttachPatchTransformsCommand.cs1305
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs646
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs211
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs445
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs171
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetResolver.cs132
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs68
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs455
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs81
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs250
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateInstanceTransformsCommand.cs260
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/CreatePatchTransformsCommand.cs93
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs83
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/CreateWindowsInstallerDataFromIRCommand.cs1621
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs221
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/FileSystemManager.cs77
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/FinalizeComponentGuids.cs262
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs408
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/GenerateTransformCommand.cs582
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs157
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesFromTransforms.cs174
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/LoadTableDefinitionsCommand.cs215
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs331
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/ModularizeCommand.cs236
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/OptimizeFileFacadesOrderCommand.cs119
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/PatchTransform.cs19
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessDependencyReferencesCommand.cs114
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessPackageSoftwareTagsCommand.cs131
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessPropertiesCommand.cs101
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs125
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs714
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs365
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateFromTextFilesCommand.cs77
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs109
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/UpdateTransformsWithFileFacades.cs451
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Bind/ValidateDatabaseCommand.cs187
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Decompile/DecompileMsiOrMsmCommand.cs96
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Decompile/Decompiler.cs7596
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Decompile/Names.cs160
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Differ.cs610
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/ExtensibilityServices/WindowsInstallerBackendHelper.cs121
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Inscribe/InscribeMsiPackageCommand.cs272
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Melter.cs399
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/MelterCore.cs32
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/MsiBackend.cs85
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/MsmBackend.cs76
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/MspBackend.cs162
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/MstBackend.cs44
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/RowDictionary.cs71
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Unbind/ExtractCabinetsCommand.cs147
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs789
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindMsiOrMsmCommand.cs55
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/Unbind/UnbindTranformCommand.cs309
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendErrors.cs24
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendFactory.cs51
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerBackendWarnings.cs24
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/WindowsInstallerExtensionFactory.cs22
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/WixToolset.Core.WindowsInstaller.csproj30
-rw-r--r--src/wix/WixToolset.Core.WindowsInstaller/WixToolsetCoreServiceProviderExtensions.cs42
-rw-r--r--src/wix/WixToolset.Core.sln156
-rw-r--r--src/wix/WixToolset.Core.v3.ncrunchsolution6
-rw-r--r--src/wix/WixToolset.Core/Bind/DelayedField.cs35
-rw-r--r--src/wix/WixToolset.Core/Bind/ExpectedExtractFile.cs16
-rw-r--r--src/wix/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs92
-rw-r--r--src/wix/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs53
-rw-r--r--src/wix/WixToolset.Core/Bind/FileResolver.cs207
-rw-r--r--src/wix/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs164
-rw-r--r--src/wix/WixToolset.Core/Bind/ResolveFieldsCommand.cs276
-rw-r--r--src/wix/WixToolset.Core/Bind/TransferFilesCommand.cs196
-rw-r--r--src/wix/WixToolset.Core/BindContext.cs65
-rw-r--r--src/wix/WixToolset.Core/BindFileWithPath.cs22
-rw-r--r--src/wix/WixToolset.Core/BindPath.cs20
-rw-r--r--src/wix/WixToolset.Core/BindResult.cs48
-rw-r--r--src/wix/WixToolset.Core/Binder.cs96
-rw-r--r--src/wix/WixToolset.Core/CommandLine/BuildCommand.cs912
-rw-r--r--src/wix/WixToolset.Core/CommandLine/CommandLine.cs199
-rw-r--r--src/wix/WixToolset.Core/CommandLine/CommandLineArguments.cs207
-rw-r--r--src/wix/WixToolset.Core/CommandLine/CommandLineContext.cs22
-rw-r--r--src/wix/WixToolset.Core/CommandLine/CommandLineParser.cs270
-rw-r--r--src/wix/WixToolset.Core/CommandLine/CompileCommand.cs94
-rw-r--r--src/wix/WixToolset.Core/CommandLine/DecompileCommand.cs256
-rw-r--r--src/wix/WixToolset.Core/CommandLine/HelpCommand.cs66
-rw-r--r--src/wix/WixToolset.Core/CommandLine/VersionCommand.cs26
-rw-r--r--src/wix/WixToolset.Core/Common.cs832
-rw-r--r--src/wix/WixToolset.Core/Compile/CompilerPayload.cs291
-rw-r--r--src/wix/WixToolset.Core/CompileContext.cs34
-rw-r--r--src/wix/WixToolset.Core/Compiler.cs8514
-rw-r--r--src/wix/WixToolset.Core/CompilerCore.cs1166
-rw-r--r--src/wix/WixToolset.Core/CompilerErrors.cs43
-rw-r--r--src/wix/WixToolset.Core/CompilerWarnings.cs65
-rw-r--r--src/wix/WixToolset.Core/Compiler_Bundle.cs3266
-rw-r--r--src/wix/WixToolset.Core/Compiler_Dependency.cs384
-rw-r--r--src/wix/WixToolset.Core/Compiler_EmbeddedUI.cs417
-rw-r--r--src/wix/WixToolset.Core/Compiler_Module.cs662
-rw-r--r--src/wix/WixToolset.Core/Compiler_Package.cs4996
-rw-r--r--src/wix/WixToolset.Core/Compiler_Patch.cs657
-rw-r--r--src/wix/WixToolset.Core/Compiler_PatchCreation.cs1265
-rw-r--r--src/wix/WixToolset.Core/Compiler_Tag.cs315
-rw-r--r--src/wix/WixToolset.Core/Compiler_UI.cs1808
-rw-r--r--src/wix/WixToolset.Core/ComponentKeyPath.cs25
-rw-r--r--src/wix/WixToolset.Core/DecompileContext.cs49
-rw-r--r--src/wix/WixToolset.Core/DecompileResult.cs18
-rw-r--r--src/wix/WixToolset.Core/Decompiler.cs68
-rw-r--r--src/wix/WixToolset.Core/ExtensibilityServices/BackendHelper.cs176
-rw-r--r--src/wix/WixToolset.Core/ExtensibilityServices/ExtensionManager.cs233
-rw-r--r--src/wix/WixToolset.Core/ExtensibilityServices/FileFacade.cs172
-rw-r--r--src/wix/WixToolset.Core/ExtensibilityServices/FileTransfer.cs20
-rw-r--r--src/wix/WixToolset.Core/ExtensibilityServices/Messaging.cs99
-rw-r--r--src/wix/WixToolset.Core/ExtensibilityServices/ParseHelper.cs863
-rw-r--r--src/wix/WixToolset.Core/ExtensibilityServices/PathResolver.cs118
-rw-r--r--src/wix/WixToolset.Core/ExtensibilityServices/PreprocessHelper.cs499
-rw-r--r--src/wix/WixToolset.Core/ExtensibilityServices/ResolvedDirectory.cs15
-rw-r--r--src/wix/WixToolset.Core/ExtensibilityServices/SymbolDefinitionCreator.cs70
-rw-r--r--src/wix/WixToolset.Core/ExtensibilityServices/TrackedFile.cs26
-rw-r--r--src/wix/WixToolset.Core/ExtensibilityServices/Uuid.cs81
-rw-r--r--src/wix/WixToolset.Core/ExtensibilityServices/WixBranding.cs124
-rw-r--r--src/wix/WixToolset.Core/IBinder.cs12
-rw-r--r--src/wix/WixToolset.Core/ICompiler.cs13
-rw-r--r--src/wix/WixToolset.Core/IDecompiler.cs12
-rw-r--r--src/wix/WixToolset.Core/ILayoutCreator.cs12
-rw-r--r--src/wix/WixToolset.Core/ILibrarian.cs13
-rw-r--r--src/wix/WixToolset.Core/ILinker.cs13
-rw-r--r--src/wix/WixToolset.Core/ILocalizationParser.cs27
-rw-r--r--src/wix/WixToolset.Core/IPreprocessor.cs15
-rw-r--r--src/wix/WixToolset.Core/IResolver.cs19
-rw-r--r--src/wix/WixToolset.Core/IUnbinder.cs12
-rw-r--r--src/wix/WixToolset.Core/IncludedFile.cs14
-rw-r--r--src/wix/WixToolset.Core/IncribeContext.cs26
-rw-r--r--src/wix/WixToolset.Core/LayoutContext.cs40
-rw-r--r--src/wix/WixToolset.Core/LayoutCreator.cs223
-rw-r--r--src/wix/WixToolset.Core/Librarian.cs135
-rw-r--r--src/wix/WixToolset.Core/LibraryContext.cs38
-rw-r--r--src/wix/WixToolset.Core/Link/CollateLocalizationsCommand.cs71
-rw-r--r--src/wix/WixToolset.Core/Link/ConnectToFeature.cs59
-rw-r--r--src/wix/WixToolset.Core/Link/ConnectToFeatureCollection.cs92
-rw-r--r--src/wix/WixToolset.Core/Link/ConnectToModule.cs54
-rw-r--r--src/wix/WixToolset.Core/Link/ConnectToModuleCollection.cs92
-rw-r--r--src/wix/WixToolset.Core/Link/FindEntrySectionAndLoadSymbolsCommand.cs119
-rw-r--r--src/wix/WixToolset.Core/Link/FlattenAndProcessBundleTablesCommand.cs194
-rw-r--r--src/wix/WixToolset.Core/Link/IntermediateSymbolExtensions.cs26
-rw-r--r--src/wix/WixToolset.Core/Link/ReportConflictingSymbolsCommand.cs54
-rw-r--r--src/wix/WixToolset.Core/Link/ResolveReferencesCommand.cs183
-rw-r--r--src/wix/WixToolset.Core/Link/SymbolWithSection.cs92
-rw-r--r--src/wix/WixToolset.Core/Link/WixComplexReferenceSymbolExtensions.cs75
-rw-r--r--src/wix/WixToolset.Core/Link/WixGroupingOrdering.cs683
-rw-r--r--src/wix/WixToolset.Core/LinkContext.cs33
-rw-r--r--src/wix/WixToolset.Core/Linker.cs942
-rw-r--r--src/wix/WixToolset.Core/LinkerErrors.cs48
-rw-r--r--src/wix/WixToolset.Core/LinkerWarnings.cs36
-rw-r--r--src/wix/WixToolset.Core/LocalizationParser.cs326
-rw-r--r--src/wix/WixToolset.Core/ParsedWixVariable.cs19
-rw-r--r--src/wix/WixToolset.Core/Preprocess/IfContext.cs74
-rw-r--r--src/wix/WixToolset.Core/Preprocess/IfDefEventHandler.cs28
-rw-r--r--src/wix/WixToolset.Core/Preprocess/IfState.cs22
-rw-r--r--src/wix/WixToolset.Core/Preprocess/IncludedFileEventHandler.cs43
-rw-r--r--src/wix/WixToolset.Core/Preprocess/PreprocessorOperation.cs19
-rw-r--r--src/wix/WixToolset.Core/Preprocess/ProcessedStreamEventHandler.cs43
-rw-r--r--src/wix/WixToolset.Core/Preprocess/ResolvedVariableEventHandler.cs25
-rw-r--r--src/wix/WixToolset.Core/PreprocessContext.cs35
-rw-r--r--src/wix/WixToolset.Core/PreprocessResult.cs15
-rw-r--r--src/wix/WixToolset.Core/Preprocessor.cs1520
-rw-r--r--src/wix/WixToolset.Core/Properties/AssemblyInfo.cs9
-rw-r--r--src/wix/WixToolset.Core/ResolveContext.cs42
-rw-r--r--src/wix/WixToolset.Core/ResolveFileResult.cs14
-rw-r--r--src/wix/WixToolset.Core/ResolveResult.cs23
-rw-r--r--src/wix/WixToolset.Core/ResolvedCabinet.cs22
-rw-r--r--src/wix/WixToolset.Core/Resolver.cs304
-rw-r--r--src/wix/WixToolset.Core/SourceFile.cs17
-rw-r--r--src/wix/WixToolset.Core/UnbindContext.cs29
-rw-r--r--src/wix/WixToolset.Core/Unbinder.cs99
-rw-r--r--src/wix/WixToolset.Core/VariableResolution.cs29
-rw-r--r--src/wix/WixToolset.Core/VariableResolver.cs197
-rw-r--r--src/wix/WixToolset.Core/WixToolset.Core.csproj47
-rw-r--r--src/wix/WixToolset.Core/WixToolset.Core.v3.ncrunchproject7
-rw-r--r--src/wix/WixToolset.Core/WixToolsetServiceProvider.cs117
-rw-r--r--src/wix/WixToolset.Core/WixToolsetServiceProviderFactory.cs21
-rw-r--r--src/wix/appveyor.cmd20
-rw-r--r--src/wix/appveyor.yml44
-rw-r--r--src/wix/nuget.config13
-rw-r--r--src/wix/test/CompileCoreTestExtensionWixlib/CompileCoreTestExtensionWixlib.csproj32
-rw-r--r--src/wix/test/CompileCoreTestExtensionWixlib/Program.cs37
-rw-r--r--src/wix/test/Example.Extension/Data/example.txt1
-rw-r--r--src/wix/test/Example.Extension/Data/example.wxs15
-rw-r--r--src/wix/test/Example.Extension/Example.Extension.csproj24
-rw-r--r--src/wix/test/Example.Extension/ExampleCompilerExtension.cs195
-rw-r--r--src/wix/test/Example.Extension/ExampleExtensionData.cs23
-rw-r--r--src/wix/test/Example.Extension/ExampleExtensionFactory.cs54
-rw-r--r--src/wix/test/Example.Extension/ExamplePreprocessorExtensionAndCommandLine.cs57
-rw-r--r--src/wix/test/Example.Extension/ExampleRow.cs32
-rw-r--r--src/wix/test/Example.Extension/ExampleSearchSymbol.cs30
-rw-r--r--src/wix/test/Example.Extension/ExampleSymbol.cs30
-rw-r--r--src/wix/test/Example.Extension/ExampleSymbolDefinitions.cs67
-rw-r--r--src/wix/test/Example.Extension/ExampleTableDefinitions.cs34
-rw-r--r--src/wix/test/Example.Extension/ExampleWindowsInstallerBackendExtension.cs33
-rw-r--r--src/wix/test/WixToolsetTest.Core.Burn/BurnReaderFixture.cs44
-rw-r--r--src/wix/test/WixToolsetTest.Core.Burn/WixToolsetTest.Core.Burn.csproj28
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/ApprovedExeFixture.cs64
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/BadInputFixture.cs148
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/BindVariablesFixture.cs96
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/BootstrapperApplicationFixture.cs46
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/BundleExtractionFixture.cs58
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/BundleFixture.cs478
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/BundleManifestFixture.cs365
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/CabFixture.cs107
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/ComponentFixture.cs45
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/ContainerFixture.cs385
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/CopyFileFixture.cs48
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/CustomActionFixture.cs169
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/CustomTableFixture.cs234
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/DecompileFixture.cs86
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/DependencyExtensionFixture.cs180
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/DirectoryFixture.cs271
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/ExePackageFixture.cs52
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/ExtensionFixture.cs153
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/LanguageFixture.cs174
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/LinkerFixture.cs174
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/MediaFixture.cs62
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/ModuleFixture.cs113
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/MsiFixture.cs838
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/MsiQueryFixture.cs1040
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/MsiTransactionFixture.cs131
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/MsuPackageFixture.cs36
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/PackagePayloadFixture.cs211
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/ParseFixture.cs36
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/PatchFixture.cs279
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs212
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/PreprocessorFixture.cs181
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/RegistryFixture.cs173
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/RollbackBoundaryFixture.cs41
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/ShortcutFixture.cs78
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/SoftwareTagFixture.cs100
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/.Data/burn.exebin0 -> 463360 bytes
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppId/Advertised.wxs11
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/ComponentSearch.wxs12
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DecompiledNestedDirSearchUnderRegSearch.wxs42
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/DirectorySearch.wxs12
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/FileSearch.wxs14
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/NestedDirSearchUnderRegSearch.msibin0 -> 33045 bytes
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch.wxs12
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/RegistrySearch64.wxs12
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.en-us.wxl11
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Package.wxs17
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/PackageComponents.wxs10
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/Win32Assembly.wxs13
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/candle.exebin0 -> 28672 bytes
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/test.manifest76
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadEnsureTable/BadEnsureTable.wxs11
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.en-us.wxl11
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/Package.wxs24
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/PackageComponents.wxs12
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadIf/data/test.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/BundleVariable.wxs6
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicateCacheIds.wxs12
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/DuplicatePayloadNames.wxs31
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/HiddenPersistedBundleVariable.wxs6
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/InvalidIds.wxs8
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/OrphanPayload.wxs11
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/PackageInMultipleContainers.wxs14
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/RegistryKey.wxs13
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledPackage.wxs16
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/BadInput/UnscheduledRollbackBoundary.wxs16
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/DefaultedVariable.wxs6
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/BindVariables/data/test.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/BootstrapperApplication/DpiAwareness.wxs7
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleBindVariables/CacheIdFromPackageDescription.wxs8
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleCustomTable/BundleCustomTable.wxs53
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtension.wxs6
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleExtensionSearches.wxs8
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/BundleWithSearches.wxs11
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleExtension/SimpleBundleExtension.wxs10
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/BundleWithTag.wxs15
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleTag/fakeba.dll1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle.wxs5
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithApprovedExe/Bundle64.wxs5
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithDetachedContainer/Bundle.wxs10
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/Bundle.wxs10
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/BundleWithPackageGroupRef/MinimalPackageGroup.wxs8
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Class/DecompiledOldClassTableDef.wxs22
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Class/IconIndex0.wxs11
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Class/OldClassTableDef.msibin0 -> 36864 bytes
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/OtherComponents.wxs12
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.en-us.wxl11
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/Package.wxs22
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/PackageComponents.wxs12
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/example.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/ComplexExampleExtension/data/other.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Component/GuidCollision.wxs14
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.en-us.wxl11
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/Package.wxs17
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/PackageComponents.wxs10
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Components/data/test.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoAttachedContainer.wxs17
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoDetachedContainer.wxs15
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/LayoutPayloadInContainer.wxs28
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/MultipleAttachedContainers.wxs15
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Container/PayloadInMultipleContainers.wxs28
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/CopyFile/CopyFile.wxs17
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycle.wxs18
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycleWithTail.wxs20
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/SimpleCustomAction.wxs14
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/UnscheduledCustomAction.wxs32
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomPackageDescription/CustomPackageDescription.wxs12
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable-Expected.wxs29
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTable.wxs34
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/CustomTableWithFile.wxs22
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.en-us.wxl7
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/LocalizedCustomTable.wxs21
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file1.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/file2.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/CustomTable/data/test.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/Expected.wxs16
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.cabbin0 -> 137 bytes
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.msibin0 -> 32768 bytes
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/Expected.wxs16
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.cabbin0 -> 137 bytes
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.msibin0 -> 32768 bytes
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/Expected.wxs16
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.cabbin0 -> 137 bytes
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.msibin0 -> 32768 bytes
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/Expected.wxs18
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/MergeModule1.msmbin0 -> 32768 bytes
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/DefaultDir/DefaultDir.wxs26
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Dependency/CustomProviderKeyBundle.wxs10
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Dependency/ExePackageProvidesBundle.wxs10
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Dependency/UsingProvidesBundle.wxs8
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/DialogsInInstallUISequence/PackageComponents.wxs36
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/DefaultName.wxs13
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/DuplicateTargetSourceName.wxs11
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/Empty.wxs6
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Directory/Nested.wxs11
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/DuplicateDir/DuplicateDir.wxs25
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/EnsureTable/EnsureTable.wxs10
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Environment/Environment.wxs14
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.en-us.wxl9
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/Package.wxs20
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/PackageComponents.wxs14
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/ErrorsInUI/data/test.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.en-us.wxl11
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/Package.wxs21
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/PackageComponents.wxs12
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExampleExtension/data/example.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/MissingDetectCondition.wxs9
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/ExePackage/RequireDetectCondition.wxs11
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/FeatureGroup/FeatureGroup.wxs14
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Font/FontTitle.wxs10
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Font/TrueType.wxs10
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.en-us.wxl11
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/Package.wxs19
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/PackageComponents.wxs12
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/ForEach/data/test.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Icon/SampleIcon.wxs6
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.en-us.wxl11
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/Package.wxs18
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/PackageComponents.wxs8
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/DontDoThis.wxi6
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/Package.wxi4
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/IncludePath/data/test.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.en-us.wxl11
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/Package.wxs23
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/PackageComponents.wxs10
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/InstanceTransform/data/test.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.en-us.wxl7
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.ja-jp.wxl7
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxl7
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/Package.wxs18
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/PackageWithEnSummaryInfo.ja-jp.wxl7
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Language/data/test.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/LockPermissions/EmptyPermissions.wxs13
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.en-us.wxl11
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/Package.wxs24
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/PackageComponents.wxs10
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/ManualUpgrade/data/test.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/MultiMedia.wxs28
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a1.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/a2.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b1.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Media/data/b2.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX64.wxs8
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/FirstX86.wxs8
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX64.wxs8
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/SecondX86.wxs8
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X64AfterX86Bundle.wxs12
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsiTransaction/X86AfterX64Bundle.wxs12
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/Bundle.wxs11
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/fakeba.dll1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/MsuPackage/data/test.msu1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.en-us.wxl11
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/Package.wxs26
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/PackageComponents.wxs13
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/MultiFileCompressed/data/test.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.en-us.wxl11
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/Package.wxs48
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/PackageComponents.wxs10
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/OverridableActions/data/test.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndHash.wxs10
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndName.wxs10
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/PackagePayloadInPayloadGroup.wxs15
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHash.wxs10
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHashAndMissingDownloadUrl.wxs10
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndHash.wxs10
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/WrongPackagePayloadInPayloadGroup.wxs15
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.0.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Av1.0.1.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.0.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/.data/Bv1.0.1.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Package.wxs28
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFamilyFilter/Patch.wxs16
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Package.wxs30
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchFromWixlib/Patch.wxs16
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/.data/A.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Package.wxs27
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNoFileChanges/Patch.wxs16
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleA/Bundle.wxs7
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleB/Bundle.wxs8
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/BundleC/Bundle.wxs9
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PackageA/Package.wxs44
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchA/Patch.wxs12
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchB/Patch.wxs14
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchNonSpecific/PatchC/Patch.wxs14
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.0.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Av1.0.1.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.0.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/.data/Bv1.0.1.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/BundleA/Bundle.wxs10
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Package.wxs27
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PatchSingle/Patch.wxs16
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/AbsoluteName.wxs9
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/CanonicalizeName.wxs7
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/DownloadUrlPlaceholdersBundle.wxs30
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/SharedBAAndPackagePayloadBundle.wxs16
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/ValidName.wxs7
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Preprocessor/EnvParens.wxs4
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/Package.en-us.wxl11
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageComponents.wxs10
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/PackageWithTag.wxs18
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductTag/example.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/MinimalComponentGroup.wxs10
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProductWithComponentGroupRef/Product.wxs19
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/NestedUnderClass.wxs13
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.en-us.wxl11
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/Package.wxs17
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/PackageComponents.wxs16
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/ProgId/data/test.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.en-us.wxl11
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/Package.wxs34
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PublishComponent/data/test.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/DuplicateRegistryValueIds.wxs14
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryKeyEndingWithBackslash.wxs12
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValue.wxs11
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RegistryValueMultiString.wxs17
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Registry/RemoveRegistryKey.wxs11
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/ReserveCost/ReserveCost.wxs11
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/RollbackBoundary/BeginningOfChain.wxs9
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/TestComponents.wxs16
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/a/test.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/b/test.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/SameFileFolders/data/c/test.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/DecompiledSequenceTables.wxs32
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/SequenceTables.msibin0 -> 32768 bytes
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/ServiceInstall/OwnProcess.wxs12
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.en-us.wxl11
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/Package.wxs19
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/PackageComponents.wxs10
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetProperty/data/test.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/SetVariable/Simple.wxs15
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/SharedPayloadsBetweenPackages/SharedPayloadsBetweenPackages.wxs18
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/DecompiledShortcuts.wxs19
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutProperty.wxs14
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/ShortcutSameNameShortName.wxs12
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/shortcuts.msibin0 -> 32768 bytes
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.en-us.wxl10
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/Bundle.wxs12
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBootstrapperApplication.wxs7
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/MultiFileBundle.wxs18
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/Shared.dll1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/MsiPackage/test.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/fakeba.dll1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/test.msibin0 -> 32768 bytes
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/.data/test.msmbin0 -> 24576 bytes
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.en-us.wxl11
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/Package.wxs20
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.en-us.wxl10
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wixproj48
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/Module.wxs17
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleModule/data/test.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExePackageGroup.wxs8
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs27
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.en-us.wxl11
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/Package.wxs17
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/PackageComponents.wxs13
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFile/data/test.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.en-us.wxl11
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/Package.wxs25
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/PackageComponents.wxs10
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/SingleFileCompressed/data/test.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.en-us.wxl10
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/Module.wxs10
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/SuppressModularization/data/test.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/ColorNull.wxs12
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.en-us.wxl13
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/TextStyle/SizeLocalized.wxs12
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/TypeLib/Language0.wxs11
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Upgrade/DetectOnly.wxs12
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.en-us.wxl11
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/Package.wxs16
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/PackageComponents.wxs10
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/UsingProvides/example.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.en-us.wxl11
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/Package.wxs31
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/PackageComponents.wxs10
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Variables/data/test.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.en-us.wxl11
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/Package.wxs19
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/PackageComponents.wxs14
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixVariableOverride/data/test2.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.en-us.wxl11
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/Package.wxs19
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/PackageComponents.wxs10
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/Wixipl/data/test.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.en-us.wxl11
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/Package.wxs19
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/PackageComponents.wxs26
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/alpha/foo.dll1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/mips/foo.dll1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/powerpc/foo.dll1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/WixlibWithBinaries/data/test.txt1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestXmlFixture.cs62
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/VariableResolverFixture.cs75
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/WarningFixture.cs63
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/WixToolsetTest.CoreIntegration.csproj32
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/WixiplFixture.cs205
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/WixlibFixture.cs316
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/WixlibQueryFixture.cs81
610 files changed, 80886 insertions, 0 deletions
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/OrphanPayload.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/OrphanPayload.wxs
new file mode 100644
index 00000000..92a9602f
--- /dev/null
+++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/OrphanPayload.wxs
@@ -0,0 +1,11 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <PackageGroupRef Id="MinimalPackageGroup" />
6 </PackageGroup>
7 <PayloadGroup Id="OrphanPayloads">
8 <Payload Id="OrphanPayload" SourceFile="$(sys.SOURCEFILEPATH)" />
9 </PayloadGroup>
10 </Fragment>
11</Wix>
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/PackageInMultipleContainers.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/PackageInMultipleContainers.wxs
new file mode 100644
index 00000000..a00874ce
--- /dev/null
+++ b/src/test/WixToolsetTest.CoreIntegration/TestData/BadInput/PackageInMultipleContainers.wxs
@@ -0,0 +1,14 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <PackageGroupRef Id="MinimalPackageGroup" />
6 </PackageGroup>
7 <Container Id="First">
8 <PackageGroupRef Id="BundlePackages" />
9 </Container>
10 <Container Id="Second">
11 <PackageGroupRef Id="BundlePackages" />
12 </Container>
13 </Fragment>
14</Wix>
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoAttachedContainer.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoAttachedContainer.wxs
new file mode 100644
index 00000000..ec757c5d
--- /dev/null
+++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Container/HarvestIntoAttachedContainer.wxs
@@ -0,0 +1,17 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <MsiPackage Id="FirstX86">
6 <PayloadGroupRef Id="FirstX86Payloads" />
7 </MsiPackage>
8 <MsiPackage Id="FirstX64" Name="FirstX64\FirstX64.msi" SourceFile="FirstX64\" DownloadUrl="http://example.com/{0}/{1}/{2}" />
9 </PackageGroup>
10 <Container Id="BundlePackages" Type="attached">
11 <PackageGroupRef Id="BundlePackages" />
12 </Container>
13 <PayloadGroup Id="FirstX86Payloads">
14 <MsiPackagePayload Name="FirstX86\FirstX86.msi" SourceFile="FirstX86\" DownloadUrl="http://example.com/{0}/{1}/{2}" />
15 </PayloadGroup>
16 </Fragment>
17</Wix>
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Container/LayoutPayloadInContainer.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Container/LayoutPayloadInContainer.wxs
new file mode 100644
index 00000000..0c5f8c7e
--- /dev/null
+++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Container/LayoutPayloadInContainer.wxs
@@ -0,0 +1,28 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Bundle Name="BurnBundle" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="{B5B23622-239B-4E3B-BDAB-67648CB975BF}">
4 <BootstrapperApplication>
5 <BootstrapperApplicationDll SourceFile="fakeba.dll" />
6 </BootstrapperApplication>
7 <Chain>
8 <PackageGroupRef Id="BundlePackages" />
9 </Chain>
10 <PayloadGroupRef Id="Shared" />
11 </Bundle>
12 <Fragment>
13 <PackageGroup Id="BundlePackages">
14 <PackageGroupRef Id="FirstX64" />
15 </PackageGroup>
16 <PackageGroup Id="FirstX64">
17 <MsiPackage SourceFile="FirstX64.msi">
18 <PayloadGroupRef Id="Shared" />
19 </MsiPackage>
20 </PackageGroup>
21 <Container Id="FirstX64" Name="FirstX64" Type="detached">
22 <PackageGroupRef Id="FirstX64" />
23 </Container>
24 <PayloadGroup Id="Shared">
25 <Payload Id="SharedPayload" SourceFile="$(sys.SOURCEFILEPATH)" />
26 </PayloadGroup>
27 </Fragment>
28</Wix>
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/Container/PayloadInMultipleContainers.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/Container/PayloadInMultipleContainers.wxs
new file mode 100644
index 00000000..c7f549a3
--- /dev/null
+++ b/src/test/WixToolsetTest.CoreIntegration/TestData/Container/PayloadInMultipleContainers.wxs
@@ -0,0 +1,28 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <PackageGroupRef Id="FirstX86" />
6 <PackageGroupRef Id="FirstX64" />
7 </PackageGroup>
8 <PackageGroup Id="FirstX86">
9 <MsiPackage SourceFile="FirstX86.msi">
10 <PayloadGroupRef Id="Shared" />
11 </MsiPackage>
12 </PackageGroup>
13 <PackageGroup Id="FirstX64">
14 <MsiPackage SourceFile="FirstX64.msi">
15 <PayloadGroupRef Id="Shared" />
16 </MsiPackage>
17 </PackageGroup>
18 <Container Id="FirstX86" Name="FirstX86" Type="detached">
19 <PackageGroupRef Id="FirstX86" />
20 </Container>
21 <Container Id="FirstX64" Name="FirstX64" Type="detached">
22 <PackageGroupRef Id="FirstX64" />
23 </Container>
24 <PayloadGroup Id="Shared">
25 <Payload Id="SharedPayload" SourceFile="$(sys.SOURCEFILEPATH)" />
26 </PayloadGroup>
27 </Fragment>
28</Wix>
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 @@
1<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2<Project>
3 <PropertyGroup Condition="'$(Configuration)'=='Release'">
4 <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
5 </PropertyGroup>
6</Project>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3<!--
4 Do NOT modify this file. Update the canonical version in Home\repo-template\src\Directory.Build.props
5 then update all of the repos.
6-->
7<Project>
8 <PropertyGroup>
9 <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
10 <EnableSourceLink Condition=" '$(NCrunch)' == '1' ">false</EnableSourceLink>
11 <MSBuildWarningsAsMessages>MSB3246</MSBuildWarningsAsMessages>
12
13 <ProjectName Condition=" '$(ProjectName)' == '' ">$(MSBuildProjectName)</ProjectName>
14 <BaseOutputPath>$([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)..\build\))</BaseOutputPath>
15 <BaseIntermediateOutputPath>$(BaseOutputPath)obj\$(ProjectName)\</BaseIntermediateOutputPath>
16 <OutputPath>$(BaseOutputPath)$(Configuration)\</OutputPath>
17
18 <Authors>WiX Toolset Team</Authors>
19 <Company>WiX Toolset</Company>
20 <Copyright>Copyright (c) .NET Foundation and contributors. All rights reserved.</Copyright>
21 <PackageLicenseExpression>MS-RL</PackageLicenseExpression>
22 <Product>WiX Toolset</Product>
23 </PropertyGroup>
24
25 <Import Project="Directory$(MSBuildProjectExtension).props" Condition=" Exists('Directory$(MSBuildProjectExtension).props') " />
26 <Import Project="Custom.Build.props" Condition=" Exists('Custom.Build.props') " />
27</Project>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3<!--
4 Do NOT modify this file. Update the canonical version in Home\repo-template\src\Directory.Build.targets
5 then update all of the repos.
6-->
7<!--
8 Replace PackageReferences with ProjectReferences when the projects can be found in .sln.
9 See the original here: https://github.com/dotnet/sdk/issues/1151#issuecomment-385133284
10-->
11<Project>
12 <PropertyGroup>
13 <ReplacePackageReferences>true</ReplacePackageReferences>
14 <TheSolutionPath Condition=" '$(NCrunch)'=='' ">$(SolutionPath)</TheSolutionPath>
15 <TheSolutionPath Condition=" '$(NCrunch)'=='1' ">$(NCrunchOriginalSolutionPath)</TheSolutionPath>
16 </PropertyGroup>
17
18 <Choose>
19 <When Condition="$(ReplacePackageReferences) AND '$(TheSolutionPath)' != '' AND '$(TheSolutionPath)' != '*undefined*' AND Exists('$(TheSolutionPath)')">
20
21 <PropertyGroup>
22 <SolutionFileContent>$([System.IO.File]::ReadAllText($(TheSolutionPath)))</SolutionFileContent>
23 <SmartSolutionDir>$([System.IO.Path]::GetDirectoryName( $(TheSolutionPath) ))</SmartSolutionDir>
24 <RegexPattern>(?&lt;="[PackageName]", ")(.*)(?=", ")</RegexPattern>
25 </PropertyGroup>
26
27 <ItemGroup>
28 <!-- Keep the identity of the PackageReference -->
29 <SmartPackageReference Include="@(PackageReference)">
30 <PackageName>%(Identity)</PackageName>
31 <InSolution>$(SolutionFileContent.Contains('\%(Identity).csproj'))</InSolution>
32 </SmartPackageReference>
33
34 <!-- Filter them by mapping them to another ItemGroup using the WithMetadataValue item function -->
35 <PackageInSolution Include="@(SmartPackageReference->WithMetadataValue('InSolution', True))">
36 <Pattern>$(RegexPattern.Replace('[PackageName]','%(PackageName)') )</Pattern>
37 <SmartPath>$([System.Text.RegularExpressions.Regex]::Match('$(SolutionFileContent)', '%(Pattern)'))</SmartPath>
38 </PackageInSolution>
39
40 <ProjectReference Include="@(PackageInSolution->'$(SmartSolutionDir)\%(SmartPath)' )"/>
41
42 <!-- Remove the package references that are now referenced as projects -->
43 <PackageReference Remove="@(PackageInSolution->'%(PackageName)')"/>
44 </ItemGroup>
45
46 </When>
47 </Choose>
48
49 <Import Project="Directory$(MSBuildProjectExtension).targets" Condition=" Exists('Directory$(MSBuildProjectExtension).targets') " />
50 <Import Project="Custom.Build.targets" Condition=" Exists('Custom.Build.targets') " />
51</Project>
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 @@
1<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2<!--
3 Do NOT modify this file. Update the canonical version in Home\repo-template\src\CSharp.Build.props
4 then update all of the repos.
5-->
6<Project>
7 <PropertyGroup>
8 <CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
9 <SignAssembly>true</SignAssembly>
10 <AssemblyOriginatorKeyFile>$([System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)wix.snk))</AssemblyOriginatorKeyFile>
11 <NBGV_EmitThisAssemblyClass>false</NBGV_EmitThisAssemblyClass>
12 </PropertyGroup>
13</Project>
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 @@
1<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2<!--
3 Do NOT modify this file. Update the canonical version in Home\repo-template\src\Directory.csproj.targets
4 then update all of the repos.
5-->
6<Project>
7 <PropertyGroup>
8 <CreateDocumentation Condition=" '$(CreateDocumentationFile)'!='true' ">false</CreateDocumentation>
9 <DocumentationFile Condition=" '$(CreateDocumentationFile)'=='true' ">$(OutputPath)\$(AssemblyName).xml</DocumentationFile>
10 </PropertyGroup>
11
12 <Target Name="SetNuspecProperties" DependsOnTargets="InitializeSourceControlInformation" AfterTargets="GetBuildVersion"
13 Condition=" Exists('$(MSBuildProjectName).nuspec') ">
14 <PropertyGroup>
15 <ProjectUrl Condition=" '$(ProjectUrl)'=='' and '$(PrivateRepositoryUrl)'!='' ">$(PrivateRepositoryUrl.Replace('.git',''))</ProjectUrl>
16
17 <NuspecFile>$(MSBuildProjectName).nuspec</NuspecFile>
18 <NuspecBasePath Condition=" '$(NuspecBasePath)'=='' ">$(OutputPath)..\</NuspecBasePath>
19 <NuspecProperties>$(NuspecProperties);Id=$(PackageId);Authors=$(Authors);Copyright=$(Copyright);Description=$(Description);Title=$(Title)</NuspecProperties>
20 <NuspecProperties>$(NuspecProperties);Version=$(PackageVersion);RepositoryCommit=$(SourceRevisionId);RepositoryType=$(RepositoryType);RepositoryUrl=$(PrivateRepositoryUrl);ProjectFolder=$(MSBuildProjectDirectory)\;ProjectUrl=$(ProjectUrl)</NuspecProperties>
21 <PublishRepositoryUrl>true</PublishRepositoryUrl>
22 <SymbolPackageFormat>snupkg</SymbolPackageFormat>
23 </PropertyGroup>
24 </Target>
25
26</Project>
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 @@
1# Core
2WixToolset.Core - preprocessor, compiler, linker and binder for Windows Installer and Burn
3
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn
4{
5 using System;
6 using System.Xml;
7 using WixToolset.Data.Symbols;
8
9 internal abstract class BaseSearchFacade : ISearchFacade
10 {
11 protected WixSearchSymbol SearchSymbol { get; set; }
12
13 public virtual void WriteXml(XmlTextWriter writer)
14 {
15 writer.WriteAttributeString("Id", this.SearchSymbol.Id.Id);
16 writer.WriteAttributeString("Variable", this.SearchSymbol.Variable);
17 if (!String.IsNullOrEmpty(this.SearchSymbol.Condition))
18 {
19 writer.WriteAttributeString("Condition", this.SearchSymbol.Condition);
20 }
21 if (!String.IsNullOrEmpty(this.SearchSymbol.BundleExtensionRef))
22 {
23 writer.WriteAttributeString("ExtensionId", this.SearchSymbol.BundleExtensionRef);
24 }
25 }
26 }
27}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.Globalization;
9 using System.IO;
10 using System.Linq;
11 using WixToolset.Core.Burn.Bind;
12 using WixToolset.Core.Burn.Bundles;
13 using WixToolset.Core.Burn.Interfaces;
14 using WixToolset.Data;
15 using WixToolset.Data.Burn;
16 using WixToolset.Data.Symbols;
17 using WixToolset.Extensibility;
18 using WixToolset.Extensibility.Data;
19 using WixToolset.Extensibility.Services;
20
21 /// <summary>
22 /// Binds a this.bundle.
23 /// </summary>
24 internal class BindBundleCommand
25 {
26 public BindBundleCommand(IBindContext context, IEnumerable<IBurnBackendBinderExtension> backedExtensions)
27 {
28 this.ServiceProvider = context.ServiceProvider;
29
30 this.Messaging = context.ServiceProvider.GetService<IMessaging>();
31
32 this.BackendHelper = context.ServiceProvider.GetService<IBackendHelper>();
33 this.InternalBurnBackendHelper = context.ServiceProvider.GetService<IInternalBurnBackendHelper>();
34 this.PayloadHarvester = context.ServiceProvider.GetService<IPayloadHarvester>();
35
36 this.DefaultCompressionLevel = context.DefaultCompressionLevel;
37 this.DelayedFields = context.DelayedFields;
38 this.ExpectedEmbeddedFiles = context.ExpectedEmbeddedFiles;
39 this.IntermediateFolder = context.IntermediateFolder;
40 this.Output = context.IntermediateRepresentation;
41 this.OutputPath = context.OutputPath;
42 this.OutputPdbPath = context.PdbPath;
43 //this.VariableResolver = context.VariableResolver;
44
45 this.BackendExtensions = backedExtensions;
46 }
47
48 private IServiceProvider ServiceProvider { get; }
49
50 private IMessaging Messaging { get; }
51
52 private IBackendHelper BackendHelper { get; }
53
54 private IInternalBurnBackendHelper InternalBurnBackendHelper { get; }
55
56 private IPayloadHarvester PayloadHarvester { get; }
57
58 private CompressionLevel? DefaultCompressionLevel { get; }
59
60 public IEnumerable<IDelayedField> DelayedFields { get; }
61
62 public IEnumerable<IExpectedExtractFile> ExpectedEmbeddedFiles { get; }
63
64 private IEnumerable<IBurnBackendBinderExtension> BackendExtensions { get; }
65
66 private Intermediate Output { get; }
67
68 private string OutputPath { get; }
69
70 private string OutputPdbPath { get; }
71
72 private string IntermediateFolder { get; }
73
74 private IVariableResolver VariableResolver { get; }
75
76 public IReadOnlyCollection<IFileTransfer> FileTransfers { get; private set; }
77
78 public IReadOnlyCollection<ITrackedFile> TrackedFiles { get; private set; }
79
80 public WixOutput Wixout { get; private set; }
81
82 public void Execute()
83 {
84 var section = this.Output.Sections.Single();
85
86 var fileTransfers = new List<IFileTransfer>();
87 var trackedFiles = new List<ITrackedFile>();
88
89 // First look for data we expect to find... Chain, WixGroups, etc.
90
91 // We shouldn't really get past the linker phase if there are
92 // no group items... that means that there's no UX, no Chain,
93 // *and* no Containers!
94 var chainPackageSymbols = this.GetRequiredSymbols<WixBundlePackageSymbol>();
95
96 var wixGroupSymbols = this.GetRequiredSymbols<WixGroupSymbol>();
97
98 // Ensure there is one and only one WixBundleSymbol.
99 // The compiler and linker behavior should have colluded to get
100 // this behavior.
101 var bundleSymbol = this.GetSingleSymbol<WixBundleSymbol>();
102
103 bundleSymbol.ProviderKey = bundleSymbol.BundleId = Guid.NewGuid().ToString("B").ToUpperInvariant();
104
105 bundleSymbol.Attributes |= WixBundleAttributes.PerMachine; // default to per-machine but the first-per user package wil flip the bundle per-user.
106
107 // Ensure there is one and only one WixBootstrapperApplicationDllSymbol.
108 // The compiler and linker behavior should have colluded to get
109 // this behavior.
110 var bundleApplicationDllSymbol = this.GetSingleSymbol<WixBootstrapperApplicationDllSymbol>();
111
112 // Ensure there is one and only one WixChainSymbol.
113 // The compiler and linker behavior should have colluded to get
114 // this behavior.
115 var chainSymbol = this.GetSingleSymbol<WixChainSymbol>();
116
117 if (this.Messaging.EncounteredError)
118 {
119 return;
120 }
121
122 // If there are any fields to resolve later, create the cache to populate during bind.
123 var variableCache = this.DelayedFields.Any() ? new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase) : null;
124
125 IEnumerable<ISearchFacade> orderedSearches;
126 IDictionary<string, IEnumerable<IntermediateSymbol>> extensionSearchSymbolsById;
127 {
128 var orderSearchesCommand = new OrderSearchesCommand(this.Messaging, section);
129 orderSearchesCommand.Execute();
130
131 orderedSearches = orderSearchesCommand.OrderedSearchFacades;
132 extensionSearchSymbolsById = orderSearchesCommand.ExtensionSearchSymbolsByExtensionId;
133 }
134
135 // Extract files that come from binary .wixlibs and WixExtensions (this does not extract files from merge modules).
136 {
137 var extractedFiles = this.BackendHelper.ExtractEmbeddedFiles(this.ExpectedEmbeddedFiles);
138
139 trackedFiles.AddRange(extractedFiles);
140 }
141
142 // Get the explicit payloads.
143 var payloadSymbols = section.Symbols.OfType<WixBundlePayloadSymbol>().ToDictionary(t => t.Id.Id);
144 var packagesPayloads = RecalculatePackagesPayloads(payloadSymbols, wixGroupSymbols);
145
146 var layoutDirectory = Path.GetDirectoryName(this.OutputPath);
147
148 // Process the explicitly authored payloads.
149 ISet<string> processedPayloads;
150 {
151 var command = new ProcessPayloadsCommand(this.BackendHelper, this.PayloadHarvester, payloadSymbols.Values, bundleSymbol.DefaultPackagingType, layoutDirectory);
152 command.Execute();
153
154 fileTransfers.AddRange(command.FileTransfers);
155 trackedFiles.AddRange(command.TrackedFiles);
156
157 processedPayloads = new HashSet<string>(payloadSymbols.Keys);
158 }
159
160 IDictionary<string, PackageFacade> facades;
161 {
162 var command = new GetPackageFacadesCommand(this.Messaging, chainPackageSymbols, section);
163 command.Execute();
164
165 facades = command.PackageFacades;
166 }
167
168 if (this.Messaging.EncounteredError)
169 {
170 return;
171 }
172
173 // Process each package facade. Note this is likely to add payloads and other symbols so
174 // note that any indexes created above may be out of date now.
175 foreach (var facade in facades.Values)
176 {
177 switch (facade.PackageSymbol.Type)
178 {
179 case WixBundlePackageType.Exe:
180 {
181 var command = new ProcessExePackageCommand(facade, payloadSymbols);
182 command.Execute();
183 }
184 break;
185
186 case WixBundlePackageType.Msi:
187 {
188 var command = new ProcessMsiPackageCommand(this.ServiceProvider, this.BackendExtensions, section, facade, packagesPayloads[facade.PackageId]);
189 command.Execute();
190 }
191 break;
192
193 case WixBundlePackageType.Msp:
194 {
195 var command = new ProcessMspPackageCommand(this.Messaging, section, facade, payloadSymbols);
196 command.Execute();
197 }
198 break;
199
200 case WixBundlePackageType.Msu:
201 {
202 var command = new ProcessMsuPackageCommand(facade, payloadSymbols);
203 command.Execute();
204 }
205 break;
206 }
207
208 if (null != variableCache)
209 {
210 BindBundleCommand.PopulatePackageVariableCache(facade, variableCache);
211 }
212 }
213
214 if (this.Messaging.EncounteredError)
215 {
216 return;
217 }
218
219 // Reindex the payloads now that all the payloads (minus the manifest payloads that will be created later)
220 // are present.
221 payloadSymbols = section.Symbols.OfType<WixBundlePayloadSymbol>().ToDictionary(t => t.Id.Id);
222 wixGroupSymbols = this.GetRequiredSymbols<WixGroupSymbol>();
223 packagesPayloads = RecalculatePackagesPayloads(payloadSymbols, wixGroupSymbols);
224
225 // Process the payloads that were added by processing the packages.
226 {
227 var toProcess = payloadSymbols.Values.Where(r => !processedPayloads.Contains(r.Id.Id)).ToList();
228
229 var command = new ProcessPayloadsCommand(this.BackendHelper, this.PayloadHarvester, toProcess, bundleSymbol.DefaultPackagingType, layoutDirectory);
230 command.Execute();
231
232 fileTransfers.AddRange(command.FileTransfers);
233 trackedFiles.AddRange(command.TrackedFiles);
234
235 processedPayloads = null;
236 }
237
238 // Set the package metadata from the payloads now that we have the complete payload information.
239 {
240 foreach (var facade in facades.Values)
241 {
242 facade.PackageSymbol.Size = 0;
243
244 var packagePayloads = packagesPayloads[facade.PackageId];
245
246 foreach (var payload in packagePayloads.Values)
247 {
248 facade.PackageSymbol.Size += payload.FileSize.Value;
249 }
250
251 if (!facade.PackageSymbol.InstallSize.HasValue)
252 {
253 facade.PackageSymbol.InstallSize = facade.PackageSymbol.Size;
254 }
255
256 var packagePayload = payloadSymbols[facade.PackageSymbol.PayloadRef];
257
258 if (String.IsNullOrEmpty(facade.PackageSymbol.Description))
259 {
260 facade.PackageSymbol.Description = packagePayload.Description;
261 }
262
263 if (String.IsNullOrEmpty(facade.PackageSymbol.DisplayName))
264 {
265 facade.PackageSymbol.DisplayName = packagePayload.DisplayName;
266 }
267 }
268 }
269
270 // Give the UX payloads their embedded IDs...
271 var uxPayloadIndex = 0;
272 {
273 foreach (var payload in payloadSymbols.Values.Where(p => BurnConstants.BurnUXContainerName == p.ContainerRef))
274 {
275 // In theory, UX payloads could be embedded in the UX CAB, external to the bundle EXE, or even
276 // downloaded. The current engine requires the UX to be fully present before any downloading starts,
277 // so that rules out downloading. Also, the burn engine does not currently copy external UX payloads
278 // into the temporary UX directory correctly, so we don't allow external either.
279 if (PackagingType.Embedded != payload.Packaging)
280 {
281 this.Messaging.Write(WarningMessages.UxPayloadsOnlySupportEmbedding(payload.SourceLineNumbers, payload.SourceFile.Path));
282 payload.Packaging = PackagingType.Embedded;
283 }
284
285 payload.EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnUXContainerEmbeddedIdFormat, uxPayloadIndex);
286 ++uxPayloadIndex;
287 }
288
289 if (0 == uxPayloadIndex)
290 {
291 // If we didn't get any UX payloads, it's an error!
292 throw new WixException(ErrorMessages.MissingBundleInformation("BootstrapperApplication"));
293 }
294
295 // Give the embedded payloads without an embedded id yet an embedded id.
296 var payloadIndex = 0;
297 foreach (var payload in payloadSymbols.Values)
298 {
299 Debug.Assert(PackagingType.Unknown != payload.Packaging);
300
301 if (PackagingType.Embedded == payload.Packaging && String.IsNullOrEmpty(payload.EmbeddedId))
302 {
303 payload.EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnAuthoredContainerEmbeddedIdFormat, payloadIndex);
304 ++payloadIndex;
305 }
306 }
307 }
308
309 if (this.Messaging.EncounteredError)
310 {
311 return;
312 }
313
314 // Determine patches to automatically slipstream.
315 {
316 var command = new AutomaticallySlipstreamPatchesCommand(section, facades.Values);
317 command.Execute();
318 }
319
320 if (this.Messaging.EncounteredError)
321 {
322 return;
323 }
324
325 IEnumerable<PackageFacade> orderedFacades;
326 IEnumerable<WixBundleRollbackBoundarySymbol> boundaries;
327 {
328 var command = new OrderPackagesAndRollbackBoundariesCommand(this.Messaging, section, facades);
329 command.Execute();
330
331 orderedFacades = command.OrderedPackageFacades;
332 boundaries = command.UsedRollbackBoundaries;
333 }
334
335 // Resolve any delayed fields before generating the manifest.
336 if (this.DelayedFields.Any())
337 {
338 this.BackendHelper.ResolveDelayedFields(this.DelayedFields, variableCache);
339 }
340
341 {
342 var command = new ProcessDependencyProvidersCommand(this.Messaging, section, facades);
343 command.Execute();
344
345 if (!String.IsNullOrEmpty(command.BundleProviderKey))
346 {
347 bundleSymbol.ProviderKey = command.BundleProviderKey; // set the overridable bundle provider key.
348 }
349 }
350
351 // Update the bundle per-machine/per-user scope based on the chained packages.
352 this.ResolveBundleInstallScope(section, bundleSymbol, orderedFacades);
353
354 var softwareTags = section.Symbols.OfType<WixBundleTagSymbol>().ToList();
355 if (softwareTags.Any())
356 {
357 var command = new ProcessBundleSoftwareTagsCommand(section, softwareTags);
358 command.Execute();
359 }
360
361 this.DetectDuplicateCacheIds(facades);
362
363 if (this.Messaging.EncounteredError)
364 {
365 return;
366 }
367
368 // Give the extension one last hook before generating the output files.
369 foreach (var extension in this.BackendExtensions)
370 {
371 extension.SymbolsFinalized(section);
372 }
373
374 if (this.Messaging.EncounteredError)
375 {
376 return;
377 }
378
379 // Generate data for all manifests.
380 {
381 var command = new GenerateManifestDataFromIRCommand(this.Messaging, section, this.BackendExtensions, this.InternalBurnBackendHelper, extensionSearchSymbolsById);
382 command.Execute();
383 }
384
385 if (this.Messaging.EncounteredError)
386 {
387 return;
388 }
389
390 // Generate the core-defined BA manifest tables...
391 string baManifestPath;
392 {
393 var command = new CreateBootstrapperApplicationManifestCommand(section, bundleSymbol, orderedFacades, uxPayloadIndex, payloadSymbols, packagesPayloads, this.IntermediateFolder, this.InternalBurnBackendHelper);
394 command.Execute();
395
396 var baManifestPayload = command.BootstrapperApplicationManifestPayloadRow;
397 baManifestPath = command.OutputPath;
398 payloadSymbols.Add(baManifestPayload.Id.Id, baManifestPayload);
399 ++uxPayloadIndex;
400
401 trackedFiles.Add(this.BackendHelper.TrackFile(baManifestPath, TrackedFileType.Temporary));
402 }
403
404 // Generate the bundle extension manifest...
405 string bextManifestPath;
406 {
407 var command = new CreateBundleExtensionManifestCommand(section, bundleSymbol, uxPayloadIndex, this.IntermediateFolder, this.InternalBurnBackendHelper);
408 command.Execute();
409
410 var bextManifestPayload = command.BundleExtensionManifestPayloadRow;
411 bextManifestPath = command.OutputPath;
412 payloadSymbols.Add(bextManifestPayload.Id.Id, bextManifestPayload);
413 ++uxPayloadIndex;
414
415 trackedFiles.Add(this.BackendHelper.TrackFile(bextManifestPath, TrackedFileType.Temporary));
416 }
417
418 var containers = section.Symbols.OfType<WixBundleContainerSymbol>().ToDictionary(t => t.Id.Id);
419 {
420 var command = new DetectPayloadCollisionsCommand(this.Messaging, containers, facades.Values, payloadSymbols, packagesPayloads);
421 command.Execute();
422 }
423
424 if (this.Messaging.EncounteredError)
425 {
426 return;
427 }
428
429 // Create all the containers except the UX container first so the manifest (that goes in the UX container)
430 // can contain all size and hash information about the non-UX containers.
431 WixBundleContainerSymbol uxContainer;
432 IEnumerable<WixBundlePayloadSymbol> uxPayloads;
433 {
434 var command = new CreateNonUXContainers(this.BackendHelper, this.Messaging, bundleApplicationDllSymbol, containers.Values, payloadSymbols, this.IntermediateFolder, layoutDirectory, this.DefaultCompressionLevel);
435 command.Execute();
436
437 fileTransfers.AddRange(command.FileTransfers);
438 trackedFiles.AddRange(command.TrackedFiles);
439
440 uxContainer = command.UXContainer;
441 uxPayloads = command.UXContainerPayloads;
442 }
443
444 if (this.Messaging.EncounteredError)
445 {
446 return;
447 }
448
449 // Resolve the download URLs now that we have all of the containers and payloads calculated.
450 {
451 var command = new ResolveDownloadUrlsCommand(this.Messaging, this.BackendExtensions, containers.Values, payloadSymbols);
452 command.Execute();
453 }
454
455 // Create the bundle manifest.
456 string manifestPath;
457 {
458 var executableName = Path.GetFileName(this.OutputPath);
459
460 var command = new CreateBurnManifestCommand(executableName, section, bundleSymbol, containers.Values, chainSymbol, orderedFacades, boundaries, uxPayloads, payloadSymbols, packagesPayloads, orderedSearches, this.IntermediateFolder);
461 command.Execute();
462
463 manifestPath = command.OutputPath;
464 trackedFiles.Add(this.BackendHelper.TrackFile(manifestPath, TrackedFileType.Temporary));
465 }
466
467 // Create the UX container.
468 {
469 var command = new CreateContainerCommand(manifestPath, uxPayloads, uxContainer.WorkingPath, this.DefaultCompressionLevel);
470 command.Execute();
471
472 uxContainer.Hash = command.Hash;
473 uxContainer.Size = command.Size;
474
475 trackedFiles.Add(this.BackendHelper.TrackFile(uxContainer.WorkingPath, TrackedFileType.Temporary));
476 }
477
478 {
479 var command = new CreateBundleExeCommand(this.Messaging, this.BackendHelper, this.IntermediateFolder, this.OutputPath, bundleApplicationDllSymbol, bundleSymbol, uxContainer, containers.Values);
480 command.Execute();
481
482 fileTransfers.Add(command.Transfer);
483 trackedFiles.Add(this.BackendHelper.TrackFile(this.OutputPath, TrackedFileType.Final));
484 }
485
486#if TODO // does this need to come back, or do they only need to be in TrackedFiles?
487 this.ContentFilePaths = payloadSymbols.Values.Where(p => p.ContentFile).Select(p => p.FullFileName).ToList();
488#endif
489 this.FileTransfers = fileTransfers;
490 this.TrackedFiles = trackedFiles;
491 this.Wixout = this.CreateWixout(trackedFiles, this.Output, manifestPath, baManifestPath, bextManifestPath);
492 }
493
494 private WixOutput CreateWixout(List<ITrackedFile> trackedFiles, Intermediate intermediate, string manifestPath, string baDataPath, string bextDataPath)
495 {
496 WixOutput wixout;
497
498 if (String.IsNullOrEmpty(this.OutputPdbPath))
499 {
500 wixout = WixOutput.Create();
501 }
502 else
503 {
504 var trackPdb = this.BackendHelper.TrackFile(this.OutputPdbPath, TrackedFileType.Final);
505 trackedFiles.Add(trackPdb);
506
507 wixout = WixOutput.Create(trackPdb.Path);
508 }
509
510 intermediate.Save(wixout);
511
512 wixout.ImportDataStream(BurnConstants.BurnManifestWixOutputStreamName, manifestPath);
513 wixout.ImportDataStream(BurnConstants.BootstrapperApplicationDataWixOutputStreamName, baDataPath);
514 wixout.ImportDataStream(BurnConstants.BundleExtensionDataWixOutputStreamName, bextDataPath);
515
516 wixout.Reopen();
517
518 return wixout;
519 }
520
521 /// <summary>
522 /// Populates the variable cache with specific package properties.
523 /// </summary>
524 /// <param name="facade">The package facade with properties to cache.</param>
525 /// <param name="variableCache">The property cache.</param>
526 private static void PopulatePackageVariableCache(PackageFacade facade, IDictionary<string, string> variableCache)
527 {
528 var package = facade.PackageSymbol;
529 var id = package.Id.Id;
530
531 variableCache.Add(String.Concat("packageDescription.", id), package.Description ?? String.Empty);
532 variableCache.Add(String.Concat("packageName.", id), package.DisplayName ?? String.Empty);
533 variableCache.Add(String.Concat("packageVersion.", id), package.Version);
534
535 if (facade.SpecificPackageSymbol is WixBundleMsiPackageSymbol msiPackage)
536 {
537 variableCache.Add(String.Concat("packageLanguage.", id), msiPackage.ProductLanguage.ToString());
538 variableCache.Add(String.Concat("packageManufacturer.", id), msiPackage.Manufacturer ?? String.Empty);
539 }
540 else
541 {
542 variableCache.Add(String.Concat("packageLanguage.", id), String.Empty);
543 variableCache.Add(String.Concat("packageManufacturer.", id), String.Empty);
544 }
545 }
546
547 private void ResolveBundleInstallScope(IntermediateSection section, WixBundleSymbol bundleSymbol, IEnumerable<PackageFacade> facades)
548 {
549 var dependencySymbolsById = section.Symbols.OfType<WixDependencyProviderSymbol>().ToDictionary(t => t.Id.Id);
550
551 foreach (var facade in facades)
552 {
553 if (bundleSymbol.PerMachine && YesNoDefaultType.No == facade.PackageSymbol.PerMachine)
554 {
555 this.Messaging.Write(VerboseMessages.SwitchingToPerUserPackage(facade.PackageSymbol.SourceLineNumbers, facade.PackageId));
556
557 bundleSymbol.Attributes &= ~WixBundleAttributes.PerMachine;
558 break;
559 }
560 }
561
562 foreach (var facade in facades)
563 {
564 // Update package scope from bundle scope if default.
565 if (YesNoDefaultType.Default == facade.PackageSymbol.PerMachine)
566 {
567 facade.PackageSymbol.PerMachine = bundleSymbol.PerMachine ? YesNoDefaultType.Yes : YesNoDefaultType.No;
568 }
569
570 // We will only register packages in the same scope as the bundle. Warn if any packages with providers
571 // are in a different scope and not permanent (permanents typically don't need a ref-count).
572 if (!bundleSymbol.PerMachine &&
573 YesNoDefaultType.Yes == facade.PackageSymbol.PerMachine &&
574 !facade.PackageSymbol.Permanent &&
575 dependencySymbolsById.ContainsKey(facade.PackageId))
576 {
577 this.Messaging.Write(WarningMessages.NoPerMachineDependencies(facade.PackageSymbol.SourceLineNumbers, facade.PackageId));
578 }
579 }
580 }
581
582 private void DetectDuplicateCacheIds(IDictionary<string, PackageFacade> facades)
583 {
584 var duplicateCacheIdDetector = new Dictionary<string, WixBundlePackageSymbol>();
585
586 foreach (var facade in facades.Values)
587 {
588 if (duplicateCacheIdDetector.TryGetValue(facade.PackageSymbol.CacheId, out var collisionPackage))
589 {
590 this.Messaging.Write(BurnBackendErrors.DuplicateCacheIds(facade.PackageSymbol.SourceLineNumbers, facade.PackageSymbol.CacheId, facade.PackageId));
591 this.Messaging.Write(BurnBackendErrors.DuplicateCacheIds2(collisionPackage.SourceLineNumbers));
592 }
593 else
594 {
595 duplicateCacheIdDetector.Add(facade.PackageSymbol.CacheId, facade.PackageSymbol);
596 }
597 }
598 }
599
600 private IEnumerable<T> GetRequiredSymbols<T>() where T : IntermediateSymbol
601 {
602 var symbols = this.Output.Sections.Single().Symbols.OfType<T>().ToList();
603
604 if (0 == symbols.Count)
605 {
606 throw new WixException(ErrorMessages.MissingBundleInformation(nameof(T)));
607 }
608
609 return symbols;
610 }
611
612 private T GetSingleSymbol<T>() where T : IntermediateSymbol
613 {
614 var symbols = this.Output.Sections.Single().Symbols.OfType<T>().ToList();
615
616 if (1 != symbols.Count)
617 {
618 throw new WixException(ErrorMessages.MissingBundleInformation(nameof(T)));
619 }
620
621 return symbols[0];
622 }
623
624 private static Dictionary<string, Dictionary<string, WixBundlePayloadSymbol>> RecalculatePackagesPayloads(Dictionary<string, WixBundlePayloadSymbol> payloadSymbols, IEnumerable<WixGroupSymbol> wixGroupSymbols)
625 {
626 var packagesPayloads = new Dictionary<string, Dictionary<string, WixBundlePayloadSymbol>>();
627
628 foreach (var groupSymbol in wixGroupSymbols)
629 {
630 if (ComplexReferenceChildType.Payload == groupSymbol.ChildType)
631 {
632 var payloadSymbol = payloadSymbols[groupSymbol.ChildId];
633
634 if (ComplexReferenceParentType.Package == groupSymbol.ParentType)
635 {
636 if (!packagesPayloads.TryGetValue(groupSymbol.ParentId, out var packagePayloadsById))
637 {
638 packagePayloadsById = new Dictionary<string, WixBundlePayloadSymbol>();
639 packagesPayloads.Add(groupSymbol.ParentId, packagePayloadsById);
640 }
641
642 packagePayloadsById.Add(payloadSymbol.Id.Id, payloadSymbol);
643 }
644 }
645 }
646
647 return packagesPayloads;
648 }
649 }
650}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn
4{
5 using System.Xml;
6 using WixToolset.Data.Symbols;
7
8 internal class ExtensionSearchFacade : BaseSearchFacade
9 {
10 public ExtensionSearchFacade(WixSearchSymbol searchSymbol)
11 {
12 this.SearchSymbol = searchSymbol;
13 }
14
15 public override void WriteXml(XmlTextWriter writer)
16 {
17 writer.WriteStartElement("ExtensionSearch");
18
19 base.WriteXml(writer);
20
21 writer.WriteEndElement();
22 }
23 }
24}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using System.Text;
9 using System.Xml;
10 using WixToolset.Core.Burn.Bundles;
11 using WixToolset.Core.Burn.ExtensibilityServices;
12 using WixToolset.Data;
13 using WixToolset.Data.Symbols;
14 using WixToolset.Extensibility;
15 using WixToolset.Extensibility.Services;
16
17 internal class GenerateManifestDataFromIRCommand
18 {
19 public GenerateManifestDataFromIRCommand(IMessaging messaging, IntermediateSection section, IEnumerable<IBurnBackendBinderExtension> backendExtensions, IBurnBackendHelper backendHelper, IDictionary<string, IEnumerable<IntermediateSymbol>> extensionSearchSymbolsById)
20 {
21 this.Messaging = messaging;
22 this.Section = section;
23 this.BackendExtensions = backendExtensions;
24 this.BackendHelper = backendHelper;
25 this.ExtensionSearchSymbolsById = extensionSearchSymbolsById;
26 }
27
28 private IEnumerable<IBurnBackendBinderExtension> BackendExtensions { get; }
29
30 private IBurnBackendHelper BackendHelper { get; }
31
32 private IDictionary<string, IEnumerable<IntermediateSymbol>> ExtensionSearchSymbolsById { get; }
33
34 private IMessaging Messaging { get; }
35
36 private IntermediateSection Section { get; }
37
38 public void Execute()
39 {
40 var symbols = this.Section.Symbols.ToList();
41 var cellsByCustomDataAndElementId = new Dictionary<string, List<WixBundleCustomDataCellSymbol>>();
42 var customDataById = new Dictionary<string, WixBundleCustomDataSymbol>();
43
44 foreach (var kvp in this.ExtensionSearchSymbolsById)
45 {
46 var extensionId = kvp.Key;
47 var extensionSearchSymbols = kvp.Value;
48 foreach (var extensionSearchSymbol in extensionSearchSymbols)
49 {
50 this.BackendHelper.AddBundleExtensionData(extensionId, extensionSearchSymbol, symbolIdIsIdAttribute: true);
51 symbols.Remove(extensionSearchSymbol);
52 }
53 }
54
55 foreach (var symbol in symbols)
56 {
57 var unknownSymbol = false;
58 switch (symbol.Definition.Type)
59 {
60 // Symbols used internally and are not added to a data manifest.
61 case SymbolDefinitionType.ProvidesDependency:
62 case SymbolDefinitionType.WixApprovedExeForElevation:
63 case SymbolDefinitionType.WixBootstrapperApplication:
64 case SymbolDefinitionType.WixBootstrapperApplicationDll:
65 case SymbolDefinitionType.WixBundle:
66 case SymbolDefinitionType.WixBundleContainer:
67 case SymbolDefinitionType.WixBundleCustomDataAttribute:
68 case SymbolDefinitionType.WixBundleExePackage:
69 case SymbolDefinitionType.WixBundleExePackagePayload:
70 case SymbolDefinitionType.WixBundleExtension:
71 case SymbolDefinitionType.WixBundleMsiFeature:
72 case SymbolDefinitionType.WixBundleMsiPackage:
73 case SymbolDefinitionType.WixBundleMsiPackagePayload:
74 case SymbolDefinitionType.WixBundleMsiProperty:
75 case SymbolDefinitionType.WixBundleMspPackage:
76 case SymbolDefinitionType.WixBundleMspPackagePayload:
77 case SymbolDefinitionType.WixBundleMsuPackage:
78 case SymbolDefinitionType.WixBundleMsuPackagePayload:
79 case SymbolDefinitionType.WixBundlePackage:
80 case SymbolDefinitionType.WixBundlePackageCommandLine:
81 case SymbolDefinitionType.WixBundlePackageExitCode:
82 case SymbolDefinitionType.WixBundlePackageGroup:
83 case SymbolDefinitionType.WixBundlePatchTargetCode:
84 case SymbolDefinitionType.WixBundlePayload:
85 case SymbolDefinitionType.WixBundlePayloadGroup:
86 case SymbolDefinitionType.WixBundleRelatedPackage:
87 case SymbolDefinitionType.WixBundleRollbackBoundary:
88 case SymbolDefinitionType.WixBundleSlipstreamMsp:
89 case SymbolDefinitionType.WixBundleTag:
90 case SymbolDefinitionType.WixBundleUpdate:
91 case SymbolDefinitionType.WixBundleVariable:
92 case SymbolDefinitionType.WixBuildInfo:
93 case SymbolDefinitionType.WixChain:
94 case SymbolDefinitionType.WixComponentSearch:
95 case SymbolDefinitionType.WixDependencyProvider:
96 case SymbolDefinitionType.WixFileSearch:
97 case SymbolDefinitionType.WixGroup:
98 case SymbolDefinitionType.WixProductSearch:
99 case SymbolDefinitionType.WixRegistrySearch:
100 case SymbolDefinitionType.WixRelatedBundle:
101 case SymbolDefinitionType.WixSearch:
102 case SymbolDefinitionType.WixSearchRelation:
103 case SymbolDefinitionType.WixSetVariable:
104 case SymbolDefinitionType.WixUpdateRegistration:
105 break;
106
107 // Symbols used before binding.
108 case SymbolDefinitionType.WixComplexReference:
109 case SymbolDefinitionType.WixOrdering:
110 case SymbolDefinitionType.WixSimpleReference:
111 case SymbolDefinitionType.WixVariable:
112 break;
113
114 // Symbols to investigate:
115 case SymbolDefinitionType.WixChainItem:
116 break;
117
118 case SymbolDefinitionType.WixBundleCustomData:
119 unknownSymbol = !this.IndexBundleCustomDataSymbol((WixBundleCustomDataSymbol)symbol, customDataById);
120 break;
121
122 case SymbolDefinitionType.WixBundleCustomDataCell:
123 this.IndexBundleCustomDataCellSymbol((WixBundleCustomDataCellSymbol)symbol, cellsByCustomDataAndElementId);
124 break;
125
126 case SymbolDefinitionType.MustBeFromAnExtension:
127 unknownSymbol = !this.AddSymbolFromExtension(symbol);
128 break;
129
130 default:
131 unknownSymbol = true;
132 break;
133 }
134
135 if (unknownSymbol)
136 {
137 this.Messaging.Write(WarningMessages.SymbolNotTranslatedToOutput(symbol));
138 }
139 }
140
141 this.AddIndexedCellSymbols(customDataById, cellsByCustomDataAndElementId);
142 }
143
144 private bool IndexBundleCustomDataSymbol(WixBundleCustomDataSymbol wixBundleCustomDataSymbol, Dictionary<string, WixBundleCustomDataSymbol> customDataById)
145 {
146 switch (wixBundleCustomDataSymbol.Type)
147 {
148 case WixBundleCustomDataType.BootstrapperApplication:
149 case WixBundleCustomDataType.BundleExtension:
150 break;
151 default:
152 return false;
153 }
154
155 var customDataId = wixBundleCustomDataSymbol.Id.Id;
156 customDataById.Add(customDataId, wixBundleCustomDataSymbol);
157 return true;
158 }
159
160 private void IndexBundleCustomDataCellSymbol(WixBundleCustomDataCellSymbol wixBundleCustomDataCellSymbol, Dictionary<string, List<WixBundleCustomDataCellSymbol>> cellsByCustomDataAndElementId)
161 {
162 var tableAndRowId = wixBundleCustomDataCellSymbol.CustomDataRef + "/" + wixBundleCustomDataCellSymbol.ElementId;
163 if (!cellsByCustomDataAndElementId.TryGetValue(tableAndRowId, out var cells))
164 {
165 cells = new List<WixBundleCustomDataCellSymbol>();
166 cellsByCustomDataAndElementId.Add(tableAndRowId, cells);
167 }
168
169 cells.Add(wixBundleCustomDataCellSymbol);
170 }
171
172 private void AddIndexedCellSymbols(Dictionary<string, WixBundleCustomDataSymbol> customDataById, Dictionary<string, List<WixBundleCustomDataCellSymbol>> cellsByCustomDataAndElementId)
173 {
174 foreach (var elementValues in cellsByCustomDataAndElementId.Values)
175 {
176 var elementName = elementValues[0].CustomDataRef;
177 var customDataSymbol = customDataById[elementName];
178
179 var attributeNames = customDataSymbol.AttributeNamesSeparated;
180
181 var elementValuesByAttribute = elementValues.ToDictionary(t => t.AttributeRef, t => t.Value);
182
183 var sb = new StringBuilder();
184 using (var writer = XmlWriter.Create(sb, BurnBackendHelper.WriterSettings))
185 {
186 switch (customDataSymbol.Type)
187 {
188 case WixBundleCustomDataType.BootstrapperApplication:
189 writer.WriteStartElement(elementName, BurnCommon.BADataNamespace);
190 break;
191 case WixBundleCustomDataType.BundleExtension:
192 writer.WriteStartElement(elementName, BurnCommon.BundleExtensionDataNamespace);
193 break;
194 default:
195 throw new NotImplementedException();
196 }
197
198 // Write all row data as attributes in table column order.
199 foreach (var attributeName in attributeNames)
200 {
201 if (elementValuesByAttribute.TryGetValue(attributeName, out var value))
202 {
203 writer.WriteAttributeString(attributeName, value);
204 }
205 }
206
207 writer.WriteEndElement();
208 }
209
210 switch (customDataSymbol.Type)
211 {
212 case WixBundleCustomDataType.BootstrapperApplication:
213 this.BackendHelper.AddBootstrapperApplicationData(sb.ToString());
214 break;
215 case WixBundleCustomDataType.BundleExtension:
216 this.BackendHelper.AddBundleExtensionData(customDataSymbol.BundleExtensionRef, sb.ToString());
217 break;
218 default:
219 throw new NotImplementedException();
220 }
221 }
222 }
223
224 private bool AddSymbolFromExtension(IntermediateSymbol symbol)
225 {
226 foreach (var extension in this.BackendExtensions)
227 {
228 if (extension.TryProcessSymbol(this.Section, symbol))
229 {
230 return true;
231 }
232 }
233
234 return false;
235 }
236 }
237}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn
4{
5 using System;
6 using System.Xml;
7 using WixToolset.Data;
8 using WixToolset.Data.Symbols;
9
10 internal class LegacySearchFacade : BaseSearchFacade
11 {
12 public LegacySearchFacade(WixSearchSymbol searchSymbol, IntermediateSymbol searchSpecificSymbol)
13 {
14 this.SearchSymbol = searchSymbol;
15 this.SearchSpecificSymbol = searchSpecificSymbol;
16 }
17
18 public IntermediateSymbol SearchSpecificSymbol { get; }
19
20 /// <summary>
21 /// Generates Burn manifest and ParameterInfo-style markup a search.
22 /// </summary>
23 /// <param name="writer"></param>
24 public override void WriteXml(XmlTextWriter writer)
25 {
26 switch (this.SearchSpecificSymbol)
27 {
28 case WixComponentSearchSymbol symbol:
29 this.WriteComponentSearchXml(writer, symbol);
30 break;
31 case WixFileSearchSymbol symbol:
32 this.WriteFileSearchXml(writer, symbol);
33 break;
34 case WixProductSearchSymbol symbol:
35 this.WriteProductSearchXml(writer, symbol);
36 break;
37 case WixRegistrySearchSymbol symbol:
38 this.WriteRegistrySearchXml(writer, symbol);
39 break;
40 }
41 }
42
43 private void WriteComponentSearchXml(XmlTextWriter writer, WixComponentSearchSymbol searchSymbol)
44 {
45 writer.WriteStartElement("MsiComponentSearch");
46
47 base.WriteXml(writer);
48
49 writer.WriteAttributeString("ComponentId", searchSymbol.Guid);
50
51 if (!String.IsNullOrEmpty(searchSymbol.ProductCode))
52 {
53 writer.WriteAttributeString("ProductCode", searchSymbol.ProductCode);
54 }
55
56 if (0 != (searchSymbol.Attributes & WixComponentSearchAttributes.KeyPath))
57 {
58 writer.WriteAttributeString("Type", "keyPath");
59 }
60 else if (0 != (searchSymbol.Attributes & WixComponentSearchAttributes.State))
61 {
62 writer.WriteAttributeString("Type", "state");
63 }
64 else if (0 != (searchSymbol.Attributes & WixComponentSearchAttributes.WantDirectory))
65 {
66 writer.WriteAttributeString("Type", "directory");
67 }
68
69 writer.WriteEndElement();
70 }
71
72 private void WriteFileSearchXml(XmlTextWriter writer, WixFileSearchSymbol searchSymbol)
73 {
74 writer.WriteStartElement((0 == (searchSymbol.Attributes & WixFileSearchAttributes.IsDirectory)) ? "FileSearch" : "DirectorySearch");
75
76 base.WriteXml(writer);
77
78 writer.WriteAttributeString("Path", searchSymbol.Path);
79 if (WixFileSearchAttributes.WantExists == (searchSymbol.Attributes & WixFileSearchAttributes.WantExists))
80 {
81 writer.WriteAttributeString("Type", "exists");
82 }
83 else if (WixFileSearchAttributes.WantVersion == (searchSymbol.Attributes & WixFileSearchAttributes.WantVersion))
84 {
85 // Can never get here for DirectorySearch.
86 writer.WriteAttributeString("Type", "version");
87 }
88 else
89 {
90 writer.WriteAttributeString("Type", "path");
91 }
92 writer.WriteEndElement();
93 }
94
95 private void WriteProductSearchXml(XmlTextWriter writer, WixProductSearchSymbol symbol)
96 {
97 writer.WriteStartElement("MsiProductSearch");
98
99 base.WriteXml(writer);
100
101 if (0 != (symbol.Attributes & WixProductSearchAttributes.UpgradeCode))
102 {
103 writer.WriteAttributeString("UpgradeCode", symbol.Guid);
104 }
105 else
106 {
107 writer.WriteAttributeString("ProductCode", symbol.Guid);
108 }
109
110 if (0 != (symbol.Attributes & WixProductSearchAttributes.Version))
111 {
112 writer.WriteAttributeString("Type", "version");
113 }
114 else if (0 != (symbol.Attributes & WixProductSearchAttributes.Language))
115 {
116 writer.WriteAttributeString("Type", "language");
117 }
118 else if (0 != (symbol.Attributes & WixProductSearchAttributes.State))
119 {
120 writer.WriteAttributeString("Type", "state");
121 }
122 else if (0 != (symbol.Attributes & WixProductSearchAttributes.Assignment))
123 {
124 writer.WriteAttributeString("Type", "assignment");
125 }
126
127 writer.WriteEndElement();
128 }
129
130 private void WriteRegistrySearchXml(XmlTextWriter writer, WixRegistrySearchSymbol symbol)
131 {
132 writer.WriteStartElement("RegistrySearch");
133
134 base.WriteXml(writer);
135
136 switch (symbol.Root)
137 {
138 case RegistryRootType.ClassesRoot:
139 writer.WriteAttributeString("Root", "HKCR");
140 break;
141 case RegistryRootType.CurrentUser:
142 writer.WriteAttributeString("Root", "HKCU");
143 break;
144 case RegistryRootType.LocalMachine:
145 writer.WriteAttributeString("Root", "HKLM");
146 break;
147 case RegistryRootType.Users:
148 writer.WriteAttributeString("Root", "HKU");
149 break;
150 }
151
152 writer.WriteAttributeString("Key", symbol.Key);
153
154 if (!String.IsNullOrEmpty(symbol.Value))
155 {
156 writer.WriteAttributeString("Value", symbol.Value);
157 }
158
159 var existenceOnly = 0 != (symbol.Attributes & WixRegistrySearchAttributes.WantExists);
160
161 writer.WriteAttributeString("Type", existenceOnly ? "exists" : "value");
162
163 if (0 != (symbol.Attributes & WixRegistrySearchAttributes.Win64))
164 {
165 writer.WriteAttributeString("Win64", "yes");
166 }
167
168 if (!existenceOnly)
169 {
170 if (0 != (symbol.Attributes & WixRegistrySearchAttributes.ExpandEnvironmentVariables))
171 {
172 writer.WriteAttributeString("ExpandEnvironment", "yes");
173 }
174
175 // We *always* say this is VariableType="string". If we end up
176 // needing to be more specific, we will have to expand the "Format"
177 // attribute to allow "number" and "version".
178
179 writer.WriteAttributeString("VariableType", "string");
180 }
181
182 writer.WriteEndElement();
183 }
184 }
185}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 using System.Text;
10 using System.Xml;
11 using WixToolset.Core.Native.Msi;
12 using WixToolset.Data;
13 using WixToolset.Data.Symbols;
14
15 internal class ProcessBundleSoftwareTagsCommand
16 {
17 public ProcessBundleSoftwareTagsCommand(IntermediateSection section, IEnumerable<WixBundleTagSymbol> softwareTags)
18 {
19 this.Section = section;
20 this.SoftwareTags = softwareTags;
21 }
22
23 private IntermediateSection Section { get; }
24
25 private IEnumerable<WixBundleTagSymbol> SoftwareTags { get; }
26
27 public void Execute()
28 {
29 var bundleInfo = this.Section.Symbols.OfType<WixBundleSymbol>().FirstOrDefault();
30 var bundleId = NormalizeGuid(bundleInfo.BundleId);
31 var upgradeCode = NormalizeGuid(bundleInfo.UpgradeCode);
32
33 var uniqueId = String.Concat("wix:bundle/", bundleId);
34 var persistentId = String.Concat("wix:bundle.upgrade/", upgradeCode);
35
36 // Try to collect all the software id tags from all the child packages.
37 var containedTags = CollectPackageTags(this.Section);
38
39 foreach (var bundleTag in this.SoftwareTags)
40 {
41 using (var ms = new MemoryStream())
42 {
43 CreateTagFile(ms, uniqueId, bundleInfo.Name, bundleInfo.Version, bundleTag.Regid, bundleInfo.Manufacturer, persistentId, containedTags);
44 bundleTag.Xml = Encoding.UTF8.GetString(ms.ToArray());
45 }
46 }
47 }
48
49 private static string NormalizeGuid(string guidString)
50 {
51 if (Guid.TryParse(guidString, out var guid))
52 {
53 return guid.ToString("D").ToUpperInvariant();
54 }
55
56 return guidString;
57 }
58
59 private static IEnumerable<SoftwareTag> CollectPackageTags(IntermediateSection section)
60 {
61 var tags = new List<SoftwareTag>();
62
63 var msiPackages = section.Symbols.OfType<WixBundlePackageSymbol>().Where(s => s.Type == WixBundlePackageType.Msi).ToList();
64 if (msiPackages.Any())
65 {
66 var payloadSymbolsById = section.Symbols.OfType<WixBundlePayloadSymbol>().ToDictionary(s => s.Id.Id);
67
68 foreach (var msiPackage in msiPackages)
69 {
70 var payload = payloadSymbolsById[msiPackage.PayloadRef];
71
72 using (var db = new Database(payload.SourceFile.Path, OpenDatabase.ReadOnly))
73 {
74 using (var view = db.OpenExecuteView("SELECT `Regid`, `TagId` FROM `SoftwareIdentificationTag`"))
75 {
76 foreach (var record in view.Records)
77 {
78 tags.Add(new SoftwareTag { Regid = record.GetString(1), Id = record.GetString(2) });
79 }
80 }
81 }
82 }
83 }
84
85 return tags;
86 }
87
88 private static void CreateTagFile(Stream stream, string uniqueId, string name, string version, string regid, string manufacturer, string persistendId, IEnumerable<SoftwareTag> containedTags)
89 {
90 var versionScheme = Version.TryParse(version, out _) ? "multipartnumeric" : "alphanumeric";
91
92 using (var writer = XmlWriter.Create(stream, new XmlWriterSettings { Indent = true }))
93 {
94 writer.WriteStartDocument();
95 writer.WriteStartElement("SoftwareIdentity", "http://standards.iso.org/iso/19770/-2/2015/schema.xsd");
96 writer.WriteAttributeString("tagId", uniqueId);
97 writer.WriteAttributeString("name", name);
98 writer.WriteAttributeString("version", version);
99 writer.WriteAttributeString("versionScheme", versionScheme);
100
101 writer.WriteStartElement("Entity");
102 writer.WriteAttributeString("name", manufacturer);
103 writer.WriteAttributeString("regid", regid);
104 writer.WriteAttributeString("role", "softwareCreator tagCreator");
105 writer.WriteEndElement(); // </Entity>
106
107 if (!String.IsNullOrEmpty(persistendId))
108 {
109 writer.WriteStartElement("Meta");
110 writer.WriteAttributeString("persistentId", persistendId);
111 writer.WriteEndElement(); // </Meta>
112 }
113
114 foreach (var containedTag in containedTags)
115 {
116 writer.WriteStartElement("Link");
117 writer.WriteAttributeString("rel", "component");
118 writer.WriteAttributeString("href", String.Concat("swid:", containedTag.Id));
119 writer.WriteEndElement(); // </Link>
120 }
121
122 writer.WriteEndElement(); // </SoftwareIdentity>
123 }
124 }
125
126 private class SoftwareTag
127 {
128 public string Regid { get; set; }
129
130 public string Id { get; set; }
131 }
132 }
133}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using WixToolset.Data;
9 using WixToolset.Core.Burn.Bundles;
10 using WixToolset.Extensibility.Services;
11 using WixToolset.Data.Symbols;
12
13 internal class ProcessDependencyProvidersCommand
14 {
15 public ProcessDependencyProvidersCommand(IMessaging messaging, IntermediateSection section, IDictionary<string, PackageFacade> facades)
16 {
17 this.Messaging = messaging;
18 this.Section = section;
19
20 this.Facades = facades;
21 }
22
23 public string BundleProviderKey { get; private set; }
24
25 public Dictionary<string, WixDependencyProviderSymbol> DependencySymbolsByKey { get; private set; }
26
27 private IMessaging Messaging { get; }
28
29 private IntermediateSection Section { get; }
30
31 private IDictionary<string, PackageFacade> Facades { get; }
32
33 /// <summary>
34 /// Sets the explicitly provided bundle provider key, if provided. And...
35 /// Imports authored dependency providers for each package in the manifest,
36 /// and generates dependency providers for certain package types that do not
37 /// have a provider defined.
38 /// </summary>
39 public void Execute()
40 {
41 var dependencySymbols = this.Section.Symbols.OfType<WixDependencyProviderSymbol>();
42
43 foreach (var dependency in dependencySymbols)
44 {
45 // Sets the provider key for the bundle, if it is not set already.
46 if (String.IsNullOrEmpty(this.BundleProviderKey))
47 {
48 if (dependency.Bundle)
49 {
50 this.BundleProviderKey = dependency.ProviderKey;
51 }
52 }
53
54 // Import any authored dependencies. These may merge with imported provides from MSI packages.
55 var packageId = dependency.ParentRef;
56
57 if (this.Facades.TryGetValue(packageId, out var facade))
58 {
59 if (String.IsNullOrEmpty(dependency.ProviderKey))
60 {
61 switch (facade.SpecificPackageSymbol)
62 {
63 // The WixDependencyExtension allows an empty Key for MSIs and MSPs.
64 case WixBundleMsiPackageSymbol msiPackage:
65 dependency.ProviderKey = msiPackage.ProductCode;
66 break;
67 case WixBundleMspPackageSymbol mspPackage:
68 dependency.ProviderKey = mspPackage.PatchCode;
69 break;
70 }
71 }
72
73 if (String.IsNullOrEmpty(dependency.Version))
74 {
75 dependency.Version = facade.PackageSymbol.Version;
76 }
77
78 // If the version is still missing, a version could not be gathered from the package and was not authored.
79 if (String.IsNullOrEmpty(dependency.Version))
80 {
81 this.Messaging.Write(ErrorMessages.MissingDependencyVersion(facade.PackageId));
82 }
83
84 if (String.IsNullOrEmpty(dependency.DisplayName))
85 {
86 dependency.DisplayName = facade.PackageSymbol.DisplayName;
87 }
88 }
89 }
90
91 this.DependencySymbolsByKey = this.GetDependencySymbolsByKey(dependencySymbols);
92
93 // Generate providers for MSI and MSP packages that still do not have providers.
94 foreach (var facade in this.Facades.Values)
95 {
96 string key = null;
97
98 if (facade.SpecificPackageSymbol is WixBundleMsiPackageSymbol msiPackage)
99 {
100 key = msiPackage.ProductCode;
101 }
102 else if (facade.SpecificPackageSymbol is WixBundleMspPackageSymbol mspPackage)
103 {
104 key = mspPackage.PatchCode;
105 }
106
107 if (!String.IsNullOrEmpty(key) && !this.DependencySymbolsByKey.ContainsKey(key))
108 {
109 var dependency = this.Section.AddSymbol(new WixDependencyProviderSymbol(facade.PackageSymbol.SourceLineNumbers, facade.PackageSymbol.Id)
110 {
111 ParentRef = facade.PackageId,
112 ProviderKey = key,
113 Version = facade.PackageSymbol.Version,
114 DisplayName = facade.PackageSymbol.DisplayName
115 });
116
117 this.DependencySymbolsByKey.Add(dependency.ProviderKey, dependency);
118 }
119 }
120 }
121
122 private Dictionary<string, WixDependencyProviderSymbol> GetDependencySymbolsByKey(IEnumerable<WixDependencyProviderSymbol> dependencySymbols)
123 {
124 var dependencySymbolsByKey = new Dictionary<string, WixDependencyProviderSymbol>();
125
126 foreach (var dependency in dependencySymbols)
127 {
128 if (dependencySymbolsByKey.TryGetValue(dependency.ProviderKey, out var collision))
129 {
130 // If not a perfect dependency collision, display an error.
131 if (dependency.ProviderKey != collision.ProviderKey ||
132 dependency.Version != collision.Version ||
133 dependency.DisplayName != collision.DisplayName)
134 {
135 this.Messaging.Write(ErrorMessages.DuplicateProviderDependencyKey(dependency.ProviderKey, dependency.ParentRef));
136 }
137 }
138 else
139 {
140 dependencySymbolsByKey.Add(dependency.ProviderKey, dependency);
141 }
142 }
143
144 return dependencySymbolsByKey;
145 }
146 }
147}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using WixToolset.Data;
8 using WixToolset.Data.Burn;
9 using WixToolset.Data.Symbols;
10 using WixToolset.Extensibility;
11 using WixToolset.Extensibility.Services;
12
13 internal class ResolveDownloadUrlsCommand
14 {
15 public ResolveDownloadUrlsCommand(IMessaging messaging, IEnumerable<IBurnBackendBinderExtension> backendExtensions, IEnumerable<WixBundleContainerSymbol> containers, Dictionary<string, WixBundlePayloadSymbol> payloadsById)
16 {
17 this.Messaging = messaging;
18 this.BackendExtensions = backendExtensions;
19 this.Containers = containers;
20 this.PayloadsById = payloadsById;
21 }
22
23 private IMessaging Messaging { get; }
24
25 private IEnumerable<IBurnBackendBinderExtension> BackendExtensions { get; }
26
27 private IEnumerable<WixBundleContainerSymbol> Containers { get; }
28
29 private Dictionary<string, WixBundlePayloadSymbol> PayloadsById { get; }
30
31 public void Execute()
32 {
33 this.ResolveContainerUrls();
34
35 this.ResolvePayloadUrls();
36 }
37
38 private void ResolveContainerUrls()
39 {
40 foreach (var container in this.Containers)
41 {
42 if (container.Type == ContainerType.Detached)
43 {
44 var resolvedUrl = this.ResolveUrl(container.DownloadUrl, null, null, container.Id.Id, container.Name);
45 if (!String.IsNullOrEmpty(resolvedUrl))
46 {
47 container.DownloadUrl = resolvedUrl;
48 }
49 }
50 else if (container.Type == ContainerType.Attached)
51 {
52 if (!String.IsNullOrEmpty(container.DownloadUrl))
53 {
54 this.Messaging.Write(WarningMessages.DownloadUrlNotSupportedForAttachedContainers(container.SourceLineNumbers, container.Id.Id));
55 }
56 }
57 }
58 }
59
60 private void ResolvePayloadUrls()
61 {
62 foreach (var payload in this.PayloadsById.Values)
63 {
64 if (payload.Packaging == PackagingType.Embedded && payload.ContainerRef == BurnConstants.BurnUXContainerName)
65 {
66 if (!String.IsNullOrEmpty(payload.DownloadUrl))
67 {
68 this.Messaging.Write(WarningMessages.DownloadUrlNotSupportedForBAPayloads(payload.SourceLineNumbers, payload.Id.Id));
69 }
70 }
71 else
72 {
73 var packageId = payload.ParentPackagePayloadRef;
74 var parentUrl = payload.ParentPackagePayloadRef == null ? null : this.PayloadsById[payload.ParentPackagePayloadRef].DownloadUrl;
75 var resolvedUrl = this.ResolveUrl(payload.DownloadUrl, parentUrl, packageId, payload.Id.Id, payload.Name);
76 if (!String.IsNullOrEmpty(resolvedUrl))
77 {
78 payload.DownloadUrl = resolvedUrl;
79 }
80 }
81 }
82 }
83
84 private string ResolveUrl(string url, string fallbackUrl, string packageId, string payloadId, string fileName)
85 {
86 string resolvedUrl = null;
87
88 foreach (var extension in this.BackendExtensions)
89 {
90 resolvedUrl = extension.ResolveUrl(url, fallbackUrl, packageId, payloadId, fileName);
91 if (!String.IsNullOrEmpty(resolvedUrl))
92 {
93 break;
94 }
95 }
96
97 if (String.IsNullOrEmpty(resolvedUrl))
98 {
99 // If a URL was not specified but there is a fallback URL that has a format specifier in it
100 // then use the fallback URL formatter for this URL.
101 if (String.IsNullOrEmpty(url) && !String.IsNullOrEmpty(fallbackUrl))
102 {
103 var formattedFallbackUrl = String.Format(fallbackUrl, packageId, payloadId, fileName);
104 if (!String.Equals(fallbackUrl, formattedFallbackUrl, StringComparison.OrdinalIgnoreCase))
105 {
106 url = fallbackUrl;
107 }
108 }
109
110 if (!String.IsNullOrEmpty(url))
111 {
112 var formattedUrl = String.Format(url, packageId, payloadId, fileName);
113
114 if (Uri.TryCreate(formattedUrl, UriKind.Absolute, out var canonicalUri))
115 {
116 resolvedUrl = canonicalUri.AbsoluteUri;
117 }
118 else
119 {
120 resolvedUrl = null;
121 }
122 }
123 }
124
125 return resolvedUrl;
126 }
127 }
128}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn
4{
5 using System.Xml;
6 using WixToolset.Data.Symbols;
7
8 internal class SetVariableSearchFacade : BaseSearchFacade
9 {
10 public SetVariableSearchFacade(WixSearchSymbol searchSymbol, WixSetVariableSymbol setVariableSymbol)
11 {
12 this.SearchSymbol = searchSymbol;
13 this.SetVariableSymbol = setVariableSymbol;
14 }
15
16 private WixSetVariableSymbol SetVariableSymbol { get; }
17
18 public override void WriteXml(XmlTextWriter writer)
19 {
20 writer.WriteStartElement("SetVariable");
21
22 base.WriteXml(writer);
23
24 if (this.SetVariableSymbol.Type != WixBundleVariableType.Unknown)
25 {
26 writer.WriteAttributeString("Value", this.SetVariableSymbol.Value);
27
28 switch (this.SetVariableSymbol.Type)
29 {
30 case WixBundleVariableType.Formatted:
31 writer.WriteAttributeString("Type", "formatted");
32 break;
33 case WixBundleVariableType.Numeric:
34 writer.WriteAttributeString("Type", "numeric");
35 break;
36 case WixBundleVariableType.String:
37 writer.WriteAttributeString("Type", "string");
38 break;
39 case WixBundleVariableType.Version:
40 writer.WriteAttributeString("Type", "version");
41 break;
42 }
43 }
44
45 writer.WriteEndElement();
46 }
47 }
48}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn
4{
5 using System;
6 using System.IO;
7 using WixToolset.Core.Burn.Bundles;
8 using WixToolset.Core.Burn.Inscribe;
9 using WixToolset.Data;
10 using WixToolset.Extensibility;
11 using WixToolset.Extensibility.Data;
12 using WixToolset.Extensibility.Services;
13
14 internal class BundleBackend : IBackend
15 {
16 public IBindResult Bind(IBindContext context)
17 {
18 var extensionManager = context.ServiceProvider.GetService<IExtensionManager>();
19
20 var backendExtensions = extensionManager.GetServices<IBurnBackendBinderExtension>();
21
22 foreach (var extension in backendExtensions)
23 {
24 extension.PreBackendBind(context);
25 }
26
27 var command = new BindBundleCommand(context, backendExtensions);
28 command.Execute();
29
30 var result = context.ServiceProvider.GetService<IBindResult>();
31 result.FileTransfers = command.FileTransfers;
32 result.TrackedFiles = command.TrackedFiles;
33 result.Wixout = command.Wixout;
34
35 foreach (var extension in backendExtensions)
36 {
37 extension.PostBackendBind(result);
38 }
39
40 return result;
41 }
42
43 public IDecompileResult Decompile(IDecompileContext context)
44 {
45 throw new NotImplementedException();
46 }
47
48 public bool Inscribe(IInscribeContext context)
49 {
50 if (String.IsNullOrEmpty(context.SignedEngineFile))
51 {
52 var command = new InscribeBundleCommand(context);
53 return command.Execute();
54 }
55 else
56 {
57 var command = new InscribeBundleEngineCommand(context);
58 return command.Execute();
59 }
60 }
61
62 public Intermediate Unbind(IUnbindContext context)
63 {
64 var uxExtractPath = Path.Combine(context.ExportBasePath, "UX");
65 var acExtractPath = Path.Combine(context.ExportBasePath, "AttachedContainer");
66 var messaging = context.ServiceProvider.GetService<IMessaging>();
67
68 using (var reader = BurnReader.Open(messaging, context.InputFilePath))
69 {
70 reader.ExtractUXContainer(uxExtractPath, context.IntermediateFolder);
71 reader.ExtractAttachedContainer(acExtractPath, context.IntermediateFolder);
72 }
73
74 return null;
75 }
76 }
77}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn.Bundles
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.Linq;
9 using WixToolset.Data;
10 using WixToolset.Data.Symbols;
11
12 internal class AutomaticallySlipstreamPatchesCommand
13 {
14 public AutomaticallySlipstreamPatchesCommand(IntermediateSection section, ICollection<PackageFacade> packageFacades)
15 {
16 this.Section = section;
17 this.PackageFacades = packageFacades;
18 }
19
20 private IntermediateSection Section { get; }
21
22 private IEnumerable<PackageFacade> PackageFacades { get; }
23
24 public void Execute()
25 {
26 var msiPackages = new List<WixBundleMsiPackageSymbol>();
27 var targetsProductCode = new Dictionary<string, List<WixBundlePatchTargetCodeSymbol>>();
28 var targetsUpgradeCode = new Dictionary<string, List<WixBundlePatchTargetCodeSymbol>>();
29
30 foreach (var facade in this.PackageFacades)
31 {
32 // Keep track of all MSI packages.
33 if (facade.SpecificPackageSymbol is WixBundleMsiPackageSymbol msiPackage)
34 {
35 msiPackages.Add(msiPackage);
36 }
37 else if (facade.SpecificPackageSymbol is WixBundleMspPackageSymbol mspPackage && mspPackage.Slipstream)
38 {
39 var patchTargetCodeSymbols = this.Section.Symbols
40 .OfType<WixBundlePatchTargetCodeSymbol>()
41 .Where(r => r.PackageRef == facade.PackageId);
42
43 // Index target ProductCodes and UpgradeCodes for slipstreamed MSPs.
44 foreach (var symbol in patchTargetCodeSymbols)
45 {
46 if (symbol.TargetsProductCode)
47 {
48 if (!targetsProductCode.TryGetValue(symbol.TargetCode, out var symbols))
49 {
50 symbols = new List<WixBundlePatchTargetCodeSymbol>();
51 targetsProductCode.Add(symbol.TargetCode, symbols);
52 }
53
54 symbols.Add(symbol);
55 }
56 else if (symbol.TargetsUpgradeCode)
57 {
58 if (!targetsUpgradeCode.TryGetValue(symbol.TargetCode, out var symbols))
59 {
60 symbols = new List<WixBundlePatchTargetCodeSymbol>();
61 targetsUpgradeCode.Add(symbol.TargetCode, symbols);
62 }
63 }
64 }
65 }
66 }
67
68 var slipstreamMspIds = new HashSet<string>();
69
70 // Loop through the MSI and slipstream patches targeting it.
71 foreach (var msi in msiPackages)
72 {
73 if (targetsProductCode.TryGetValue(msi.ProductCode, out var symbols))
74 {
75 foreach (var symbol in symbols)
76 {
77 Debug.Assert(symbol.TargetsProductCode);
78 Debug.Assert(!symbol.TargetsUpgradeCode);
79
80 this.TryAddSlipstreamSymbol(slipstreamMspIds, msi, symbol);
81 }
82 }
83
84 if (!String.IsNullOrEmpty(msi.UpgradeCode) && targetsUpgradeCode.TryGetValue(msi.UpgradeCode, out symbols))
85 {
86 foreach (var symbol in symbols)
87 {
88 Debug.Assert(!symbol.TargetsProductCode);
89 Debug.Assert(symbol.TargetsUpgradeCode);
90
91 this.TryAddSlipstreamSymbol(slipstreamMspIds, msi, symbol);
92 }
93
94 symbols = null;
95 }
96 }
97 }
98
99 private bool TryAddSlipstreamSymbol(HashSet<string> slipstreamMspIds, WixBundleMsiPackageSymbol msiPackage, WixBundlePatchTargetCodeSymbol patchTargetCode)
100 {
101 var id = new Identifier(AccessModifier.Section, msiPackage.Id.Id, patchTargetCode.PackageRef);
102
103 if (slipstreamMspIds.Add(id.Id))
104 {
105 this.Section.AddSymbol(new WixBundleSlipstreamMspSymbol(patchTargetCode.SourceLineNumbers)
106 {
107 TargetPackageRef = msiPackage.Id.Id,
108 MspPackageRef = patchTargetCode.PackageRef,
109 });
110
111 return true;
112 }
113
114 return false;
115 }
116 }
117}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn.Bundles
4{
5 using System.IO;
6 using System.Security.Cryptography;
7 using System.Text;
8
9 internal static class BundleHashAlgorithm
10 {
11 public static string Hash(FileInfo fileInfo)
12 {
13 byte[] hashBytes;
14
15 using (var managed = new SHA512CryptoServiceProvider())
16 using (var stream = fileInfo.OpenRead())
17 {
18 hashBytes = managed.ComputeHash(stream);
19 }
20
21 var sb = new StringBuilder(hashBytes.Length * 2);
22 for (var i = 0; i < hashBytes.Length; i++)
23 {
24 sb.AppendFormat("{0:X2}", hashBytes[i]);
25 }
26
27 return sb.ToString();
28 }
29 }
30}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn.Bundles
4{
5 using System;
6 using System.Diagnostics;
7 using System.IO;
8 using WixToolset.Data;
9 using WixToolset.Extensibility.Services;
10
11 /// <summary>
12 /// Common functionality for Burn PE Writer &amp; Reader for the WiX toolset.
13 /// </summary>
14 /// <remarks>This class encapsulates common functionality related to
15 /// bundled/chained setup packages.</remarks>
16 /// <example>
17 /// </example>
18 internal abstract class BurnCommon : IDisposable
19 {
20 public const string BurnNamespace = "http://wixtoolset.org/schemas/v4/2008/Burn";
21 public const string BurnUXContainerEmbeddedIdFormat = "u{0}";
22 public const string BurnAuthoredContainerEmbeddedIdFormat = "a{0}";
23
24 public const string BADataFileName = "BootstrapperApplicationData.xml";
25 public const string BADataNamespace = "http://wixtoolset.org/schemas/v4/BootstrapperApplicationData";
26
27 public const string BundleExtensionDataFileName = "BundleExtensionData.xml";
28 public const string BundleExtensionDataNamespace = "http://wixtoolset.org/schemas/v4/BundleExtensionData";
29
30 // See WinNT.h for details about the PE format, including the
31 // structure and offsets for IMAGE_DOS_HEADER, IMAGE_NT_HEADERS32,
32 // IMAGE_FILE_HEADER, etc.
33 protected const UInt32 IMAGE_DOS_HEADER_SIZE = 64;
34 protected const UInt32 IMAGE_DOS_HEADER_OFFSET_MAGIC = 0;
35 protected const UInt32 IMAGE_DOS_HEADER_OFFSET_NTHEADER = 60;
36
37 protected const UInt32 IMAGE_NT_HEADER_SIZE = 24; // signature DWORD (4) + IMAGE_FILE_HEADER (20)
38 protected const UInt32 IMAGE_NT_HEADER_OFFSET_SIGNATURE = 0;
39 protected const UInt32 IMAGE_NT_HEADER_OFFSET_NUMBEROFSECTIONS = 6;
40 protected const UInt32 IMAGE_NT_HEADER_OFFSET_SIZEOFOPTIONALHEADER = 20;
41
42 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.
43 protected const UInt32 IMAGE_OPTIONAL_NEGATIVE_OFFSET_CERTIFICATETABLE = (IMAGE_DATA_DIRECTORY_SIZE * (IMAGE_NUMBEROF_DIRECTORY_ENTRIES - IMAGE_DIRECTORY_ENTRY_SECURITY));
44
45 protected const UInt32 IMAGE_SECTION_HEADER_SIZE = 40;
46 protected const UInt32 IMAGE_SECTION_HEADER_OFFSET_NAME = 0;
47 protected const UInt32 IMAGE_SECTION_HEADER_OFFSET_VIRTUALSIZE = 8;
48 protected const UInt32 IMAGE_SECTION_HEADER_OFFSET_SIZEOFRAWDATA = 16;
49 protected const UInt32 IMAGE_SECTION_HEADER_OFFSET_POINTERTORAWDATA = 20;
50
51 protected const UInt32 IMAGE_DATA_DIRECTORY_SIZE = 8; // struct of two DWORDs.
52 protected const UInt32 IMAGE_DIRECTORY_ENTRY_SECURITY = 4;
53 protected const UInt32 IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16;
54
55 protected const UInt16 IMAGE_DOS_SIGNATURE = 0x5A4D;
56 protected const UInt32 IMAGE_NT_SIGNATURE = 0x00004550;
57 protected const UInt64 IMAGE_SECTION_WIXBURN_NAME = 0x6E7275627869772E; // ".wixburn", as a qword.
58
59 // The ".wixburn" section contains:
60 // 0- 3: magic number
61 // 4- 7: version
62 // 8-23: bundle GUID
63 // 24-27: engine (stub) size
64 // 28-31: original checksum
65 // 32-35: original signature offset
66 // 36-39: original signature size
67 // 40-43: container type (1 = CAB)
68 // 44-47: container count
69 // 48-51: byte count of manifest + UX container
70 // 52-55: byte count of attached container
71 protected const UInt32 BURN_SECTION_OFFSET_MAGIC = 0;
72 protected const UInt32 BURN_SECTION_OFFSET_VERSION = 4;
73 protected const UInt32 BURN_SECTION_OFFSET_BUNDLEGUID = 8;
74 protected const UInt32 BURN_SECTION_OFFSET_STUBSIZE = 24;
75 protected const UInt32 BURN_SECTION_OFFSET_ORIGINALCHECKSUM = 28;
76 protected const UInt32 BURN_SECTION_OFFSET_ORIGINALSIGNATUREOFFSET = 32;
77 protected const UInt32 BURN_SECTION_OFFSET_ORIGINALSIGNATURESIZE = 36;
78 protected const UInt32 BURN_SECTION_OFFSET_FORMAT = 40;
79 protected const UInt32 BURN_SECTION_OFFSET_COUNT = 44;
80 protected const UInt32 BURN_SECTION_OFFSET_UXSIZE = 48;
81 protected const UInt32 BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE = 52;
82 protected const UInt32 BURN_SECTION_SIZE = BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE + 4; // last field + sizeof(DWORD)
83
84 protected const UInt32 BURN_SECTION_MAGIC = 0x00f14300;
85 protected const UInt32 BURN_SECTION_VERSION = 0x00000002;
86 protected string fileExe;
87 protected UInt32 peOffset = UInt32.MaxValue;
88 protected UInt16 sections = UInt16.MaxValue;
89 protected UInt32 firstSectionOffset = UInt32.MaxValue;
90 protected UInt32 checksumOffset;
91 protected UInt32 certificateTableSignatureOffset;
92 protected UInt32 certificateTableSignatureSize;
93 protected UInt32 wixburnDataOffset = UInt32.MaxValue;
94
95 // TODO: does this enum exist in another form somewhere?
96 /// <summary>
97 /// The types of attached containers that BurnWriter supports.
98 /// </summary>
99 public enum Container
100 {
101 Nothing = 0,
102 UX,
103 Attached
104 }
105
106 /// <summary>
107 /// Creates a BurnCommon for re-writing a PE file.
108 /// </summary>
109 /// <param name="messaging"></param>
110 /// <param name="fileExe">File to modify in-place.</param>
111 public BurnCommon(IMessaging messaging, string fileExe)
112 {
113 this.Messaging = messaging;
114 this.fileExe = fileExe;
115 }
116
117 public UInt32 Checksum { get; protected set; }
118 public UInt32 SignatureOffset { get; protected set; }
119 public UInt32 SignatureSize { get; protected set; }
120 public UInt32 Version { get; protected set; }
121 public UInt32 StubSize { get; protected set; }
122 public UInt32 OriginalChecksum { get; protected set; }
123 public UInt32 OriginalSignatureOffset { get; protected set; }
124 public UInt32 OriginalSignatureSize { get; protected set; }
125 public UInt32 EngineSize { get; protected set; }
126 public UInt32 ContainerCount { get; protected set; }
127 public UInt32 UXAddress { get; protected set; }
128 public UInt32 UXSize { get; protected set; }
129 public UInt32 AttachedContainerAddress { get; protected set; }
130 public UInt32 AttachedContainerSize { get; protected set; }
131
132 protected IMessaging Messaging { get; }
133
134 public void Dispose()
135 {
136 this.Dispose(true);
137
138 GC.SuppressFinalize(this);
139 }
140
141 /// <summary>
142 /// Copies one stream to another.
143 /// </summary>
144 /// <param name="input">Input stream.</param>
145 /// <param name="output">Output stream.</param>
146 /// <param name="size">Optional count of bytes to copy. 0 indicates whole input stream from current should be copied.</param>
147 protected static int CopyStream(Stream input, Stream output, int size)
148 {
149 var bytes = new byte[4096];
150 var total = 0;
151 do
152 {
153 var read = Math.Min(bytes.Length, size - total);
154 read = input.Read(bytes, 0, read);
155 if (0 == read)
156 {
157 break;
158 }
159
160 output.Write(bytes, 0, read);
161 total += read;
162 } while (0 == size || total < size);
163
164 return total;
165 }
166
167 /// <summary>
168 /// Initialize the common information about a Burn engine.
169 /// </summary>
170 /// <param name="reader">Binary reader open against a Burn engine.</param>
171 /// <returns>True if initialized.</returns>
172 protected bool Initialize(BinaryReader reader)
173 {
174 if (!this.GetWixburnSectionInfo(reader))
175 {
176 return false;
177 }
178
179 reader.BaseStream.Seek(this.wixburnDataOffset, SeekOrigin.Begin);
180 byte[] bytes = reader.ReadBytes((int)BURN_SECTION_SIZE);
181 UInt32 uint32 = 0;
182
183 uint32 = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_MAGIC);
184 if (BURN_SECTION_MAGIC != uint32)
185 {
186 this.Messaging.Write(ErrorMessages.InvalidBundle(this.fileExe));
187 return false;
188 }
189
190 this.Version = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_VERSION);
191 if (BURN_SECTION_VERSION != this.Version)
192 {
193 this.Messaging.Write(ErrorMessages.BundleTooNew(this.fileExe, this.Version));
194 return false;
195 }
196
197 uint32 = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_FORMAT); // We only know how to deal with CABs right now
198 if (1 != uint32)
199 {
200 this.Messaging.Write(ErrorMessages.InvalidBundle(this.fileExe));
201 return false;
202 }
203
204 this.StubSize = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_STUBSIZE);
205 this.OriginalChecksum = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_ORIGINALCHECKSUM);
206 this.OriginalSignatureOffset = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_ORIGINALSIGNATUREOFFSET);
207 this.OriginalSignatureSize = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_ORIGINALSIGNATURESIZE);
208
209 this.ContainerCount = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_COUNT);
210 this.UXAddress = this.StubSize;
211 this.UXSize = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_UXSIZE);
212
213 // If there is an original signature use that to determine the engine size.
214 if (0 < this.OriginalSignatureOffset)
215 {
216 this.EngineSize = this.OriginalSignatureOffset + this.OriginalSignatureSize;
217 }
218 else if (0 < this.SignatureOffset && 2 > this.ContainerCount) // if there is a signature and no attached containers, use the current signature.
219 {
220 this.EngineSize = this.SignatureOffset + this.SignatureSize;
221 }
222 else // just use the stub and UX container as the size of the engine.
223 {
224 this.EngineSize = this.StubSize + this.UXSize;
225 }
226
227 this.AttachedContainerAddress = this.ContainerCount > 1 ? this.EngineSize : 0;
228 this.AttachedContainerSize = this.ContainerCount > 1 ? BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE) : 0;
229
230 return true;
231 }
232
233 protected virtual void Dispose(bool disposing)
234 {
235 }
236
237 /// <summary>
238 /// Finds the ".wixburn" section in the current exe.
239 /// </summary>
240 /// <returns>true if the ".wixburn" section is successfully found; false otherwise</returns>
241 private bool GetWixburnSectionInfo(BinaryReader reader)
242 {
243 if (UInt32.MaxValue == this.wixburnDataOffset)
244 {
245 if (!this.EnsureNTHeader(reader))
246 {
247 return false;
248 }
249
250 UInt32 wixburnSectionOffset = UInt32.MaxValue;
251 byte[] bytes = new byte[IMAGE_SECTION_HEADER_SIZE];
252
253 reader.BaseStream.Seek(this.firstSectionOffset, SeekOrigin.Begin);
254 for (UInt16 sectionIndex = 0; sectionIndex < this.sections; ++sectionIndex)
255 {
256 reader.Read(bytes, 0, bytes.Length);
257
258 if (IMAGE_SECTION_WIXBURN_NAME == BurnCommon.ReadUInt64(bytes, IMAGE_SECTION_HEADER_OFFSET_NAME))
259 {
260 wixburnSectionOffset = this.firstSectionOffset + (IMAGE_SECTION_HEADER_SIZE * sectionIndex);
261 break;
262 }
263 }
264
265 if (UInt32.MaxValue == wixburnSectionOffset)
266 {
267 this.Messaging.Write(ErrorMessages.StubMissingWixburnSection(this.fileExe));
268 return false;
269 }
270
271 // we need 56 bytes for the manifest header, which is always going to fit in
272 // the smallest alignment (512 bytes), but just to be paranoid...
273 if (BURN_SECTION_SIZE > BurnCommon.ReadUInt32(bytes, IMAGE_SECTION_HEADER_OFFSET_SIZEOFRAWDATA))
274 {
275 this.Messaging.Write(ErrorMessages.StubWixburnSectionTooSmall(this.fileExe));
276 return false;
277 }
278
279 this.wixburnDataOffset = BurnCommon.ReadUInt32(bytes, IMAGE_SECTION_HEADER_OFFSET_POINTERTORAWDATA);
280 }
281
282 return true;
283 }
284
285 /// <summary>
286 /// Checks for a valid Windows PE signature (IMAGE_NT_SIGNATURE) in the current exe.
287 /// </summary>
288 /// <returns>true if the exe is a Windows executable; false otherwise</returns>
289 private bool EnsureNTHeader(BinaryReader reader)
290 {
291 if (UInt32.MaxValue == this.firstSectionOffset)
292 {
293 if (!this.EnsureDosHeader(reader))
294 {
295 return false;
296 }
297
298 reader.BaseStream.Seek(this.peOffset, SeekOrigin.Begin);
299 byte[] bytes = reader.ReadBytes((int)IMAGE_NT_HEADER_SIZE);
300
301 // Verify the NT signature...
302 if (IMAGE_NT_SIGNATURE != BurnCommon.ReadUInt32(bytes, IMAGE_NT_HEADER_OFFSET_SIGNATURE))
303 {
304 this.Messaging.Write(ErrorMessages.InvalidStubExe(this.fileExe));
305 return false;
306 }
307
308 ushort sizeOptionalHeader = BurnCommon.ReadUInt16(bytes, IMAGE_NT_HEADER_OFFSET_SIZEOFOPTIONALHEADER);
309
310 this.sections = BurnCommon.ReadUInt16(bytes, IMAGE_NT_HEADER_OFFSET_NUMBEROFSECTIONS);
311 this.firstSectionOffset = this.peOffset + IMAGE_NT_HEADER_SIZE + sizeOptionalHeader;
312
313 this.checksumOffset = this.peOffset + IMAGE_NT_HEADER_SIZE + IMAGE_OPTIONAL_OFFSET_CHECKSUM;
314 this.certificateTableSignatureOffset = this.peOffset + IMAGE_NT_HEADER_SIZE + sizeOptionalHeader - IMAGE_OPTIONAL_NEGATIVE_OFFSET_CERTIFICATETABLE;
315 this.certificateTableSignatureSize = this.certificateTableSignatureOffset + 4; // size is in the DWORD after the offset.
316
317 bytes = reader.ReadBytes(sizeOptionalHeader);
318 this.Checksum = BurnCommon.ReadUInt32(bytes, IMAGE_OPTIONAL_OFFSET_CHECKSUM);
319 this.SignatureOffset = BurnCommon.ReadUInt32(bytes, sizeOptionalHeader - IMAGE_OPTIONAL_NEGATIVE_OFFSET_CERTIFICATETABLE);
320 this.SignatureSize = BurnCommon.ReadUInt32(bytes, sizeOptionalHeader - IMAGE_OPTIONAL_NEGATIVE_OFFSET_CERTIFICATETABLE + 4);
321 }
322
323 return true;
324 }
325
326 /// <summary>
327 /// Checks for a valid DOS header in the current exe.
328 /// </summary>
329 /// <returns>true if the exe starts with a DOS stub; false otherwise</returns>
330 private bool EnsureDosHeader(BinaryReader reader)
331 {
332 if (UInt32.MaxValue == this.peOffset)
333 {
334 byte[] bytes = reader.ReadBytes((int)IMAGE_DOS_HEADER_SIZE);
335
336 // Verify the DOS 'MZ' signature.
337 if (IMAGE_DOS_SIGNATURE != BurnCommon.ReadUInt16(bytes, IMAGE_DOS_HEADER_OFFSET_MAGIC))
338 {
339 this.Messaging.Write(ErrorMessages.InvalidStubExe(this.fileExe));
340 return false;
341 }
342
343 this.peOffset = BurnCommon.ReadUInt32(bytes, IMAGE_DOS_HEADER_OFFSET_NTHEADER);
344 }
345
346 return true;
347 }
348
349 /// <summary>
350 /// Reads a UInt16 value in little-endian format from an offset in an array of bytes.
351 /// </summary>
352 /// <param name="bytes">Array from which to read.</param>
353 /// <param name="offset">Beginning offset from which to read.</param>
354 /// <returns>value at offset</returns>
355 internal static UInt16 ReadUInt16(byte[] bytes, UInt32 offset)
356 {
357 Debug.Assert(offset + 2 <= bytes.Length);
358 return (UInt16)(bytes[offset] + (bytes[offset + 1] << 8));
359 }
360
361 /// <summary>
362 /// Reads a UInt32 value in little-endian format from an offset in an array of bytes.
363 /// </summary>
364 /// <param name="bytes">Array from which to read.</param>
365 /// <param name="offset">Beginning offset from which to read.</param>
366 /// <returns>value at offset</returns>
367 internal static UInt32 ReadUInt32(byte[] bytes, UInt32 offset)
368 {
369 Debug.Assert(offset + 4 <= bytes.Length);
370 return BurnCommon.ReadUInt16(bytes, offset) + ((UInt32)BurnCommon.ReadUInt16(bytes, offset + 2) << 16);
371 }
372
373 /// <summary>
374 /// Reads a UInt64 value in little-endian format from an offset in an array of bytes.
375 /// </summary>
376 /// <param name="bytes">Array from which to read.</param>
377 /// <param name="offset">Beginning offset from which to read.</param>
378 /// <returns>value at offset</returns>
379 internal static UInt64 ReadUInt64(byte[] bytes, UInt32 offset)
380 {
381 Debug.Assert(offset + 8 <= bytes.Length);
382 return BurnCommon.ReadUInt32(bytes, offset) + ((UInt64)BurnCommon.ReadUInt32(bytes, offset + 4) << 32);
383 }
384 }
385}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn.Bundles
4{
5 using System;
6 using System.Collections;
7 using System.Collections.Generic;
8 using System.IO;
9 using System.Xml;
10 using WixToolset.Core.Native;
11 using WixToolset.Extensibility.Services;
12
13 /// <summary>
14 /// Burn PE reader for the WiX toolset.
15 /// </summary>
16 /// <remarks>This class encapsulates reading from a stub EXE with containers attached
17 /// for dissecting bundled/chained setup packages.</remarks>
18 /// <example>
19 /// using (BurnReader reader = BurnReader.Open(fileExe, this.core, guid))
20 /// {
21 /// reader.ExtractUXContainer(file1, tempFolder);
22 /// }
23 /// </example>
24 internal class BurnReader : BurnCommon
25 {
26 private bool disposed;
27
28 private bool invalidBundle;
29 private BinaryReader binaryReader;
30 private readonly List<DictionaryEntry> attachedContainerPayloadNames;
31
32 /// <summary>
33 /// Creates a BurnReader for reading a PE file.
34 /// </summary>
35 /// <param name="messaging"></param>
36 /// <param name="fileExe">File to read.</param>
37 private BurnReader(IMessaging messaging, string fileExe)
38 : base(messaging, fileExe)
39 {
40 this.attachedContainerPayloadNames = new List<DictionaryEntry>();
41 }
42
43 /// <summary>
44 /// Gets the underlying stream.
45 /// </summary>
46 public Stream Stream => this.binaryReader?.BaseStream;
47
48 internal static BurnReader Open(object inputFilePath)
49 {
50 throw new NotImplementedException();
51 }
52
53 /// <summary>
54 /// Opens a Burn reader.
55 /// </summary>
56 /// <param name="messaging"></param>
57 /// <param name="fileExe">Path to file.</param>
58 /// <returns>Burn reader.</returns>
59 public static BurnReader Open(IMessaging messaging, string fileExe)
60 {
61 var reader = new BurnReader(messaging, fileExe);
62
63 reader.binaryReader = new BinaryReader(File.Open(fileExe, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete));
64 if (!reader.Initialize(reader.binaryReader))
65 {
66 reader.invalidBundle = true;
67 }
68
69 return reader;
70 }
71
72 /// <summary>
73 /// Gets the UX container from the exe and extracts its contents to the output directory.
74 /// </summary>
75 /// <param name="outputDirectory">Directory to write extracted files to.</param>
76 /// <param name="tempDirectory">Scratch directory.</param>
77 /// <returns>True if successful, false otherwise</returns>
78 public bool ExtractUXContainer(string outputDirectory, string tempDirectory)
79 {
80 // No UX container to extract
81 if (this.UXAddress == 0 || this.UXSize == 0)
82 {
83 return false;
84 }
85
86 if (this.invalidBundle)
87 {
88 return false;
89 }
90
91 Directory.CreateDirectory(outputDirectory);
92 string tempCabPath = Path.Combine(tempDirectory, "ux.cab");
93 string manifestOriginalPath = Path.Combine(outputDirectory, "0");
94 string manifestPath = Path.Combine(outputDirectory, "manifest.xml");
95
96 this.binaryReader.BaseStream.Seek(this.UXAddress, SeekOrigin.Begin);
97 using (Stream tempCab = File.Open(tempCabPath, FileMode.Create, FileAccess.Write))
98 {
99 BurnCommon.CopyStream(this.binaryReader.BaseStream, tempCab, (int)this.UXSize);
100 }
101
102 var cabinet = new Cabinet(tempCabPath);
103 cabinet.Extract(outputDirectory);
104
105 Directory.CreateDirectory(Path.GetDirectoryName(manifestPath));
106 FileSystem.MoveFile(manifestOriginalPath, manifestPath);
107
108 XmlDocument document = new XmlDocument();
109 document.Load(manifestPath);
110 XmlNamespaceManager namespaceManager = new XmlNamespaceManager(document.NameTable);
111 namespaceManager.AddNamespace("burn", BurnCommon.BurnNamespace);
112 XmlNodeList uxPayloads = document.SelectNodes("/burn:BurnManifest/burn:UX/burn:Payload", namespaceManager);
113 XmlNodeList payloads = document.SelectNodes("/burn:BurnManifest/burn:Payload", namespaceManager);
114
115 foreach (XmlNode uxPayload in uxPayloads)
116 {
117 XmlNode sourcePathNode = uxPayload.Attributes.GetNamedItem("SourcePath");
118 XmlNode filePathNode = uxPayload.Attributes.GetNamedItem("FilePath");
119
120 string sourcePath = Path.Combine(outputDirectory, sourcePathNode.Value);
121 string destinationPath = Path.Combine(outputDirectory, filePathNode.Value);
122
123 Directory.CreateDirectory(Path.GetDirectoryName(destinationPath));
124 FileSystem.MoveFile(sourcePath, destinationPath);
125 }
126
127 foreach (XmlNode payload in payloads)
128 {
129 XmlNode sourcePathNode = payload.Attributes.GetNamedItem("SourcePath");
130 XmlNode filePathNode = payload.Attributes.GetNamedItem("FilePath");
131 XmlNode packagingNode = payload.Attributes.GetNamedItem("Packaging");
132
133 string sourcePath = sourcePathNode.Value;
134 string destinationPath = filePathNode.Value;
135 string packaging = packagingNode.Value;
136
137 if (packaging.Equals("embedded", StringComparison.OrdinalIgnoreCase))
138 {
139 this.attachedContainerPayloadNames.Add(new DictionaryEntry(sourcePath, destinationPath));
140 }
141 }
142
143 return true;
144 }
145
146 internal void ExtractUXContainer(string uxExtractPath, object intermediateFolder)
147 {
148 throw new NotImplementedException();
149 }
150
151 /// <summary>
152 /// Gets the attached container from the exe and extracts its contents to the output directory.
153 /// </summary>
154 /// <param name="outputDirectory">Directory to write extracted files to.</param>
155 /// <param name="tempDirectory">Scratch directory.</param>
156 /// <returns>True if successful, false otherwise</returns>
157 public bool ExtractAttachedContainer(string outputDirectory, string tempDirectory)
158 {
159 // No attached container to extract
160 if (this.AttachedContainerAddress == 0 || this.AttachedContainerSize == 0)
161 {
162 return false;
163 }
164
165 if (this.invalidBundle)
166 {
167 return false;
168 }
169
170 Directory.CreateDirectory(outputDirectory);
171 string tempCabPath = Path.Combine(tempDirectory, "attached.cab");
172
173 this.binaryReader.BaseStream.Seek(this.AttachedContainerAddress, SeekOrigin.Begin);
174 using (Stream tempCab = File.Open(tempCabPath, FileMode.Create, FileAccess.Write))
175 {
176 BurnCommon.CopyStream(this.binaryReader.BaseStream, tempCab, (int)this.AttachedContainerSize);
177 }
178
179 var cabinet = new Cabinet(tempCabPath);
180 cabinet.Extract(outputDirectory);
181
182 foreach (DictionaryEntry entry in this.attachedContainerPayloadNames)
183 {
184 string sourcePath = Path.Combine(outputDirectory, (string)entry.Key);
185 string destinationPath = Path.Combine(outputDirectory, (string)entry.Value);
186
187 Directory.CreateDirectory(Path.GetDirectoryName(destinationPath));
188 FileSystem.MoveFile(sourcePath, destinationPath);
189 }
190
191 return true;
192 }
193
194 /// <summary>
195 /// Dispose object.
196 /// </summary>
197 /// <param name="disposing">True when releasing managed objects.</param>
198 protected override void Dispose(bool disposing)
199 {
200 if (!this.disposed)
201 {
202 if (disposing && this.binaryReader != null)
203 {
204 this.binaryReader.Close();
205 this.binaryReader = null;
206 }
207
208 this.disposed = true;
209 }
210 }
211 }
212}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn.Bundles
4{
5 using System;
6 using System.Diagnostics;
7 using System.IO;
8 using WixToolset.Data;
9 using WixToolset.Extensibility.Services;
10
11 /// <summary>
12 /// Burn PE writer for the WiX toolset.
13 /// </summary>
14 /// <remarks>This class encapsulates reading/writing to a stub EXE for
15 /// creating bundled/chained setup packages.</remarks>
16 /// <example>
17 /// using (BurnWriter writer = new BurnWriter(fileExe, this.core, guid))
18 /// {
19 /// writer.AppendContainer(file1, BurnWriter.Container.UX);
20 /// writer.AppendContainer(file2, BurnWriter.Container.Attached);
21 /// }
22 /// </example>
23 internal class BurnWriter : BurnCommon
24 {
25 private bool disposed;
26 private bool invalidBundle;
27 private BinaryWriter binaryWriter;
28
29 /// <summary>
30 /// Creates a BurnWriter for re-writing a PE file.
31 /// </summary>
32 /// <param name="messaging"></param>
33 /// <param name="fileExe">File to modify in-place.</param>
34 private BurnWriter(IMessaging messaging, string fileExe)
35 : base(messaging, fileExe)
36 {
37 }
38
39 /// <summary>
40 /// Opens a Burn writer.
41 /// </summary>
42 /// <param name="messaging"></param>
43 /// <param name="fileExe">Path to file.</param>
44 /// <returns>Burn writer.</returns>
45 public static BurnWriter Open(IMessaging messaging, string fileExe)
46 {
47 BurnWriter writer = new BurnWriter(messaging, fileExe);
48
49 using (BinaryReader binaryReader = new BinaryReader(File.Open(fileExe, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete)))
50 {
51 if (!writer.Initialize(binaryReader))
52 {
53 writer.invalidBundle = true;
54 }
55 }
56
57 if (!writer.invalidBundle)
58 {
59 writer.binaryWriter = new BinaryWriter(File.Open(fileExe, FileMode.Open, FileAccess.ReadWrite, FileShare.Read | FileShare.Delete));
60 }
61
62 return writer;
63 }
64
65 /// <summary>
66 /// Update the ".wixburn" section data.
67 /// </summary>
68 /// <param name="stubSize">Size of the stub engine "burn.exe".</param>
69 /// <param name="bundleId">Unique identifier for this bundle.</param>
70 /// <returns></returns>
71 public bool InitializeBundleSectionData(long stubSize, string bundleId)
72 {
73 if (this.invalidBundle)
74 {
75 return false;
76 }
77
78 var bundleGuid = Guid.Parse(bundleId);
79
80 this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_MAGIC, BURN_SECTION_MAGIC);
81 this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_VERSION, BURN_SECTION_VERSION);
82
83 this.Messaging.Write(VerboseMessages.BundleGuid(bundleId));
84 this.binaryWriter.BaseStream.Seek(this.wixburnDataOffset + BURN_SECTION_OFFSET_BUNDLEGUID, SeekOrigin.Begin);
85 this.binaryWriter.Write(bundleGuid.ToByteArray());
86
87 this.StubSize = (uint)stubSize;
88
89 this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_STUBSIZE, this.StubSize);
90 this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALCHECKSUM, 0);
91 this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALSIGNATUREOFFSET, 0);
92 this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALSIGNATURESIZE, 0);
93 this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_FORMAT, 1); // Hard-coded to CAB for now.
94 this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_COUNT, 0);
95 this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_UXSIZE, 0);
96 this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE, 0);
97 this.binaryWriter.BaseStream.Flush();
98
99 this.EngineSize = this.StubSize;
100
101 return true;
102 }
103
104 /// <summary>
105 /// Appends a UX or Attached container to the exe and updates the ".wixburn" section data to point to it.
106 /// </summary>
107 /// <param name="fileContainer">File path to append to the current exe.</param>
108 /// <param name="container">Container section represented by the fileContainer.</param>
109 /// <returns>true if the container data is successfully appended; false otherwise</returns>
110 public bool AppendContainer(string fileContainer, BurnCommon.Container container)
111 {
112 using (FileStream reader = File.OpenRead(fileContainer))
113 {
114 return this.AppendContainer(reader, reader.Length, container);
115 }
116 }
117
118 /// <summary>
119 /// Appends a UX or Attached container to the exe and updates the ".wixburn" section data to point to it.
120 /// </summary>
121 /// <param name="containerStream">File stream to append to the current exe.</param>
122 /// <param name="containerSize">Size of container to append.</param>
123 /// <param name="container">Container section represented by the fileContainer.</param>
124 /// <returns>true if the container data is successfully appended; false otherwise</returns>
125 public bool AppendContainer(Stream containerStream, long containerSize, BurnCommon.Container container)
126 {
127 UInt32 burnSectionCount = 0;
128 UInt32 burnSectionOffsetSize = 0;
129
130 switch (container)
131 {
132 case Container.UX:
133 burnSectionCount = 1;
134 burnSectionOffsetSize = BURN_SECTION_OFFSET_UXSIZE;
135 // TODO: verify that the size in the section data is 0 or the same size.
136 this.EngineSize += (uint)containerSize;
137 this.UXSize = (uint)containerSize;
138 break;
139
140 case Container.Attached:
141 burnSectionCount = 2;
142 burnSectionOffsetSize = BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE;
143 // TODO: verify that the size in the section data is 0 or the same size.
144 this.AttachedContainerSize = (uint)containerSize;
145 break;
146
147 default:
148 Debug.Assert(false);
149 return false;
150 }
151
152 return this.AppendContainer(containerStream, (UInt32)containerSize, burnSectionOffsetSize, burnSectionCount);
153 }
154
155 public void RememberThenResetSignature()
156 {
157 if (this.invalidBundle)
158 {
159 return;
160 }
161
162 this.OriginalChecksum = this.Checksum;
163 this.OriginalSignatureOffset = this.SignatureOffset;
164 this.OriginalSignatureSize = this.SignatureSize;
165
166 this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALCHECKSUM, this.OriginalChecksum);
167 this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALSIGNATUREOFFSET, this.OriginalSignatureOffset);
168 this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALSIGNATURESIZE, this.OriginalSignatureSize);
169
170 this.Checksum = 0;
171 this.SignatureOffset = 0;
172 this.SignatureSize = 0;
173
174 this.WriteToOffset(this.checksumOffset, this.Checksum);
175 this.WriteToOffset(this.certificateTableSignatureOffset, this.SignatureOffset);
176 this.WriteToOffset(this.certificateTableSignatureSize, this.SignatureSize);
177 }
178
179 /// <summary>
180 /// Dispose object.
181 /// </summary>
182 /// <param name="disposing">True when releasing managed objects.</param>
183 protected override void Dispose(bool disposing)
184 {
185 if (!this.disposed)
186 {
187 if (disposing && this.binaryWriter != null)
188 {
189 this.binaryWriter.Close();
190 this.binaryWriter = null;
191 }
192
193 this.disposed = true;
194 }
195 }
196
197 /// <summary>
198 /// Appends a container to the exe and updates the ".wixburn" section data to point to it.
199 /// </summary>
200 /// <param name="containerStream">File stream to append to the current exe.</param>
201 /// <param name="containerSize">Size of the container.</param>
202 /// <param name="burnSectionOffsetSize">Offset of size field for this container in ".wixburn" section data.</param>
203 /// <param name="burnSectionCount">Number of Burn sections.</param>
204 /// <returns>true if the container data is successfully appended; false otherwise</returns>
205 private bool AppendContainer(Stream containerStream, UInt32 containerSize, UInt32 burnSectionOffsetSize, UInt32 burnSectionCount)
206 {
207 if (this.invalidBundle)
208 {
209 return false;
210 }
211
212 // Update the ".wixburn" section data
213 this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_COUNT, burnSectionCount);
214 this.WriteToBurnSectionOffset(burnSectionOffsetSize, containerSize);
215
216 // Append the container to the end of the existing bits.
217 this.binaryWriter.BaseStream.Seek(0, SeekOrigin.End);
218 BurnCommon.CopyStream(containerStream, this.binaryWriter.BaseStream, (int)containerSize);
219 this.binaryWriter.BaseStream.Flush();
220
221 return true;
222 }
223
224 /// <summary>
225 /// Writes the value to an offset in the Burn section data.
226 /// </summary>
227 /// <param name="offset">Offset in to the Burn section data.</param>
228 /// <param name="value">Value to write.</param>
229 private void WriteToBurnSectionOffset(uint offset, uint value)
230 {
231 this.WriteToOffset(this.wixburnDataOffset + offset, value);
232 }
233
234 /// <summary>
235 /// Writes the value to an offset in the Burn stub.
236 /// </summary>
237 /// <param name="offset">Offset in to the Burn stub.</param>
238 /// <param name="value">Value to write.</param>
239 private void WriteToOffset(uint offset, uint value)
240 {
241 this.binaryWriter.BaseStream.Seek((int)offset, SeekOrigin.Begin);
242 this.binaryWriter.Write(value);
243 }
244 }
245}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn.Bundles
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.IO;
9 using System.Linq;
10 using System.Text;
11 using System.Xml;
12 using WixToolset.Data;
13 using WixToolset.Data.Burn;
14 using WixToolset.Data.Symbols;
15
16 internal class CreateBootstrapperApplicationManifestCommand
17 {
18 public CreateBootstrapperApplicationManifestCommand(IntermediateSection section, WixBundleSymbol bundleSymbol, IEnumerable<PackageFacade> chainPackages, int lastUXPayloadIndex, Dictionary<string, WixBundlePayloadSymbol> payloadSymbols, Dictionary<string, Dictionary<string, WixBundlePayloadSymbol>> packagesPayloads, string intermediateFolder, IInternalBurnBackendHelper internalBurnBackendHelper)
19 {
20 this.Section = section;
21 this.BundleSymbol = bundleSymbol;
22 this.ChainPackages = chainPackages;
23 this.LastUXPayloadIndex = lastUXPayloadIndex;
24 this.Payloads = payloadSymbols;
25 this.PackagesPayloads = packagesPayloads;
26 this.IntermediateFolder = intermediateFolder;
27 this.InternalBurnBackendHelper = internalBurnBackendHelper;
28 }
29
30 private IntermediateSection Section { get; }
31
32 private WixBundleSymbol BundleSymbol { get; }
33
34 private IEnumerable<PackageFacade> ChainPackages { get; }
35
36 private IInternalBurnBackendHelper InternalBurnBackendHelper { get; }
37
38 private int LastUXPayloadIndex { get; }
39
40 private Dictionary<string, WixBundlePayloadSymbol> Payloads { get; }
41
42 private Dictionary<string, Dictionary<string, WixBundlePayloadSymbol>> PackagesPayloads { get; }
43
44 private string IntermediateFolder { get; }
45
46 public WixBundlePayloadSymbol BootstrapperApplicationManifestPayloadRow { get; private set; }
47
48 public string OutputPath { get; private set; }
49
50 public void Execute()
51 {
52 this.OutputPath = this.CreateBootstrapperApplicationManifest();
53
54 this.BootstrapperApplicationManifestPayloadRow = this.CreateBootstrapperApplicationManifestPayloadRow(this.OutputPath);
55 }
56
57 private string CreateBootstrapperApplicationManifest()
58 {
59 var path = Path.Combine(this.IntermediateFolder, "wix-badata.xml");
60
61 Directory.CreateDirectory(Path.GetDirectoryName(path));
62
63 using (var writer = new XmlTextWriter(path, Encoding.Unicode))
64 {
65 writer.Formatting = Formatting.Indented;
66 writer.WriteStartDocument();
67 writer.WriteStartElement("BootstrapperApplicationData", BurnCommon.BADataNamespace);
68
69 this.WriteBundleInfo(writer);
70
71 this.WritePackageInfo(writer);
72
73 this.WriteFeatureInfo(writer);
74
75 this.WritePayloadInfo(writer);
76
77 this.InternalBurnBackendHelper.WriteBootstrapperApplicationData(writer);
78
79 writer.WriteEndElement();
80 writer.WriteEndDocument();
81 }
82
83 return path;
84 }
85
86 private void WriteBundleInfo(XmlTextWriter writer)
87 {
88 writer.WriteStartElement("WixBundleProperties");
89
90 writer.WriteAttributeString("DisplayName", this.BundleSymbol.Name);
91 writer.WriteAttributeString("LogPathVariable", this.BundleSymbol.LogPathVariable);
92 writer.WriteAttributeString("Compressed", this.BundleSymbol.Compressed == true ? "yes" : "no");
93 writer.WriteAttributeString("Id", this.BundleSymbol.BundleId.ToUpperInvariant());
94 writer.WriteAttributeString("UpgradeCode", this.BundleSymbol.UpgradeCode);
95 writer.WriteAttributeString("PerMachine", this.BundleSymbol.PerMachine ? "yes" : "no");
96
97 writer.WriteEndElement();
98 }
99
100 private void WritePackageInfo(XmlTextWriter writer)
101 {
102 foreach (var package in this.ChainPackages)
103 {
104 if (!this.PackagesPayloads.TryGetValue(package.PackageId, out var payloads))
105 {
106 continue;
107 }
108
109 var packagePayload = payloads[package.PackageSymbol.PayloadRef];
110
111 var size = package.PackageSymbol.Size.ToString(CultureInfo.InvariantCulture);
112
113 writer.WriteStartElement("WixPackageProperties");
114
115 writer.WriteAttributeString("Package", package.PackageId);
116 writer.WriteAttributeString("Vital", package.PackageSymbol.Vital == true ? "yes" : "no");
117
118 if (!String.IsNullOrEmpty(package.PackageSymbol.DisplayName))
119 {
120 writer.WriteAttributeString("DisplayName", package.PackageSymbol.DisplayName);
121 }
122
123 if (!String.IsNullOrEmpty(package.PackageSymbol.Description))
124 {
125 writer.WriteAttributeString("Description", package.PackageSymbol.Description);
126 }
127
128 writer.WriteAttributeString("DownloadSize", size);
129 writer.WriteAttributeString("PackageSize", size);
130 writer.WriteAttributeString("InstalledSize", package.PackageSymbol.InstallSize?.ToString(CultureInfo.InvariantCulture) ?? size);
131 writer.WriteAttributeString("PackageType", package.PackageSymbol.Type.ToString());
132 writer.WriteAttributeString("Permanent", package.PackageSymbol.Permanent ? "yes" : "no");
133 writer.WriteAttributeString("LogPathVariable", package.PackageSymbol.LogPathVariable);
134 writer.WriteAttributeString("RollbackLogPathVariable", package.PackageSymbol.RollbackLogPathVariable);
135 writer.WriteAttributeString("Compressed", packagePayload.Packaging == PackagingType.Embedded ? "yes" : "no");
136
137 if (package.SpecificPackageSymbol is WixBundleMsiPackageSymbol msiPackage)
138 {
139 if (!String.IsNullOrEmpty(msiPackage.ProductCode))
140 {
141 writer.WriteAttributeString("ProductCode", msiPackage.ProductCode);
142 }
143
144 if (!String.IsNullOrEmpty(msiPackage.UpgradeCode))
145 {
146 writer.WriteAttributeString("UpgradeCode", msiPackage.UpgradeCode);
147 }
148 }
149 else if (package.SpecificPackageSymbol is WixBundleMspPackageSymbol mspPackage)
150 {
151 if (!String.IsNullOrEmpty(mspPackage.PatchCode))
152 {
153 writer.WriteAttributeString("ProductCode", mspPackage.PatchCode);
154 }
155 }
156
157 if (!String.IsNullOrEmpty(package.PackageSymbol.Version))
158 {
159 writer.WriteAttributeString("Version", package.PackageSymbol.Version);
160 }
161
162 if (!String.IsNullOrEmpty(package.PackageSymbol.InstallCondition))
163 {
164 writer.WriteAttributeString("InstallCondition", package.PackageSymbol.InstallCondition);
165 }
166
167 switch (package.PackageSymbol.Cache)
168 {
169 case YesNoAlwaysType.No:
170 writer.WriteAttributeString("Cache", "remove");
171 break;
172 case YesNoAlwaysType.Yes:
173 writer.WriteAttributeString("Cache", "keep");
174 break;
175 case YesNoAlwaysType.Always:
176 writer.WriteAttributeString("Cache", "force");
177 break;
178 }
179
180 writer.WriteEndElement();
181 }
182 }
183
184 private void WriteFeatureInfo(XmlTextWriter writer)
185 {
186 var featureSymbols = this.Section.Symbols.OfType<WixBundleMsiFeatureSymbol>();
187
188 foreach (var featureSymbol in featureSymbols)
189 {
190 writer.WriteStartElement("WixPackageFeatureInfo");
191
192 writer.WriteAttributeString("Package", featureSymbol.PackageRef);
193 writer.WriteAttributeString("Feature", featureSymbol.Name);
194 writer.WriteAttributeString("Size", featureSymbol.Size.ToString(CultureInfo.InvariantCulture));
195
196 if (!String.IsNullOrEmpty(featureSymbol.Parent))
197 {
198 writer.WriteAttributeString("Parent", featureSymbol.Parent);
199 }
200
201 if (!String.IsNullOrEmpty(featureSymbol.Title))
202 {
203 writer.WriteAttributeString("Title", featureSymbol.Title);
204 }
205
206 if (!String.IsNullOrEmpty(featureSymbol.Description))
207 {
208 writer.WriteAttributeString("Description", featureSymbol.Description);
209 }
210
211 writer.WriteAttributeString("Display", featureSymbol.Display.ToString(CultureInfo.InvariantCulture));
212 writer.WriteAttributeString("Level", featureSymbol.Level.ToString(CultureInfo.InvariantCulture));
213 writer.WriteAttributeString("Directory", featureSymbol.Directory);
214 writer.WriteAttributeString("Attributes", featureSymbol.Attributes.ToString(CultureInfo.InvariantCulture));
215
216 writer.WriteEndElement();
217 }
218 }
219
220 private void WritePayloadInfo(XmlTextWriter writer)
221 {
222 foreach (var kvp in this.PackagesPayloads.OrderBy(kvp => kvp.Key, StringComparer.Ordinal))
223 {
224 var packageId = kvp.Key;
225 var payloadsById = kvp.Value;
226
227 foreach (var payloadSymbol in payloadsById.Values.OrderBy(p => p.Id.Id, StringComparer.Ordinal))
228 {
229 this.WritePayloadInfo(writer, payloadSymbol, packageId);
230 }
231 }
232
233 foreach (var payloadSymbol in this.Payloads.Values.Where(p => p.LayoutOnly).OrderBy(p => p.Id.Id, StringComparer.Ordinal))
234 {
235 this.WritePayloadInfo(writer, payloadSymbol, null);
236 }
237 }
238
239 private void WritePayloadInfo(XmlTextWriter writer, WixBundlePayloadSymbol payloadSymbol, string packageId)
240 {
241 writer.WriteStartElement("WixPayloadProperties");
242
243 if (!String.IsNullOrEmpty(packageId))
244 {
245 writer.WriteAttributeString("Package", packageId);
246 }
247
248 writer.WriteAttributeString("Payload", payloadSymbol.Id.Id);
249
250 if (!String.IsNullOrEmpty(payloadSymbol.ContainerRef))
251 {
252 writer.WriteAttributeString("Container", payloadSymbol.ContainerRef);
253 }
254
255 writer.WriteAttributeString("Name", payloadSymbol.Name);
256 writer.WriteAttributeString("Size", payloadSymbol.FileSize.Value.ToString(CultureInfo.InvariantCulture));
257
258 if (!String.IsNullOrEmpty(payloadSymbol.DownloadUrl))
259 {
260 writer.WriteAttributeString("DownloadUrl", payloadSymbol.DownloadUrl);
261 }
262
263 writer.WriteEndElement();
264 }
265
266 private WixBundlePayloadSymbol CreateBootstrapperApplicationManifestPayloadRow(string baManifestPath)
267 {
268 var generatedId = this.InternalBurnBackendHelper.GenerateIdentifier("ux", BurnCommon.BADataFileName);
269
270 var symbol = this.Section.AddSymbol(new WixBundlePayloadSymbol(this.BundleSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, generatedId))
271 {
272 Name = BurnCommon.BADataFileName,
273 SourceFile = new IntermediateFieldPathValue { Path = baManifestPath },
274 Compressed = true,
275 UnresolvedSourceFile = baManifestPath,
276 ContainerRef = BurnConstants.BurnUXContainerName,
277 EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnUXContainerEmbeddedIdFormat, this.LastUXPayloadIndex),
278 Packaging = PackagingType.Embedded,
279 });
280
281 var fileInfo = new FileInfo(baManifestPath);
282
283 symbol.FileSize = (int)fileInfo.Length;
284
285 symbol.Hash = BundleHashAlgorithm.Hash(fileInfo);
286
287 return symbol;
288 }
289 }
290}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn.Bundles
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Reflection;
9 using System.Text;
10 using System.Xml;
11 using WixToolset.Core.Native;
12 using WixToolset.Data;
13 using WixToolset.Data.Burn;
14 using WixToolset.Data.Symbols;
15 using WixToolset.Dtf.Resources;
16 using WixToolset.Extensibility.Data;
17 using WixToolset.Extensibility.Services;
18
19 internal class CreateBundleExeCommand
20 {
21 public CreateBundleExeCommand(IMessaging messaging, IBackendHelper backendHelper, string intermediateFolder, string outputPath, WixBootstrapperApplicationDllSymbol bootstrapperApplicationDllSymbol, WixBundleSymbol bundleSymbol, WixBundleContainerSymbol uxContainer, IEnumerable<WixBundleContainerSymbol> containers)
22 {
23 this.Messaging = messaging;
24 this.BackendHelper = backendHelper;
25 this.IntermediateFolder = intermediateFolder;
26 this.OutputPath = outputPath;
27 this.BootstrapperApplicationDllSymbol = bootstrapperApplicationDllSymbol;
28 this.BundleSymbol = bundleSymbol;
29 this.UXContainer = uxContainer;
30 this.Containers = containers;
31 }
32
33 public IFileTransfer Transfer { get; private set; }
34
35 private IMessaging Messaging { get; }
36
37 private IBackendHelper BackendHelper { get; }
38
39 private string IntermediateFolder { get; }
40
41 private string OutputPath { get; }
42
43 private WixBootstrapperApplicationDllSymbol BootstrapperApplicationDllSymbol { get; }
44
45 private WixBundleSymbol BundleSymbol { get; }
46
47 private WixBundleContainerSymbol UXContainer { get; }
48
49 private IEnumerable<WixBundleContainerSymbol> Containers { get; }
50
51 public void Execute()
52 {
53 var bundleFilename = Path.GetFileName(this.OutputPath);
54
55 // Copy the burn.exe to a writable location then mark it to be moved to its final build location.
56
57 var stubPlatform = this.BundleSymbol.Platform.ToString();
58 var stubFile = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), stubPlatform, "burn.exe");
59
60 if (stubPlatform != "X86")
61 {
62 this.Messaging.Write(WarningMessages.ExperimentalBundlePlatform(stubPlatform));
63 }
64
65 var bundleTempPath = Path.Combine(this.IntermediateFolder, bundleFilename);
66
67 this.Messaging.Write(VerboseMessages.GeneratingBundle(bundleTempPath, stubFile));
68
69 if ("setup.exe".Equals(bundleFilename, StringComparison.OrdinalIgnoreCase))
70 {
71 this.Messaging.Write(ErrorMessages.InsecureBundleFilename(bundleFilename));
72 }
73
74 this.Transfer = this.BackendHelper.CreateFileTransfer(bundleTempPath, this.OutputPath, true, this.BundleSymbol.SourceLineNumbers);
75
76 FileSystem.CopyFile(stubFile, bundleTempPath, allowHardlink: false);
77 File.SetAttributes(bundleTempPath, FileAttributes.Normal);
78
79 var windowsAssemblyVersion = GetWindowsAssemblyVersion(this.BundleSymbol);
80
81 var applicationManifestData = GenerateApplicationManifest(this.BundleSymbol, this.BootstrapperApplicationDllSymbol, this.OutputPath, windowsAssemblyVersion);
82
83 UpdateBurnResources(bundleTempPath, this.OutputPath, this.BundleSymbol, windowsAssemblyVersion, applicationManifestData);
84
85 // Update the .wixburn section to point to at the UX and attached container(s) then attach the containers
86 // if they should be attached.
87 using (var writer = BurnWriter.Open(this.Messaging, bundleTempPath))
88 {
89 var burnStubFile = new FileInfo(bundleTempPath);
90 writer.InitializeBundleSectionData(burnStubFile.Length, this.BundleSymbol.BundleId);
91
92 // Always attach the UX container first
93 writer.AppendContainer(this.UXContainer.WorkingPath, BurnWriter.Container.UX);
94
95 // Now append all other attached containers
96 foreach (var container in this.Containers)
97 {
98 if (ContainerType.Attached == container.Type)
99 {
100 // The container was only created if it had payloads.
101 if (!String.IsNullOrEmpty(container.WorkingPath) && BurnConstants.BurnUXContainerName != container.Id.Id)
102 {
103 writer.AppendContainer(container.WorkingPath, BurnWriter.Container.Attached);
104 }
105 }
106 }
107 }
108 }
109
110 private static byte[] GenerateApplicationManifest(WixBundleSymbol bundleSymbol, WixBootstrapperApplicationDllSymbol bootstrapperApplicationSymbol, string outputPath, Version windowsAssemblyVersion)
111 {
112 const string asmv1Namespace = "urn:schemas-microsoft-com:asm.v1";
113 const string asmv3Namespace = "urn:schemas-microsoft-com:asm.v3";
114 const string compatv1Namespace = "urn:schemas-microsoft-com:compatibility.v1";
115 const string ws2005Namespace = "http://schemas.microsoft.com/SMI/2005/WindowsSettings";
116 const string ws2016Namespace = "http://schemas.microsoft.com/SMI/2016/WindowsSettings";
117 const string ws2017Namespace = "http://schemas.microsoft.com/SMI/2017/WindowsSettings";
118
119 var bundleFileName = Path.GetFileName(outputPath);
120 var bundleAssemblyVersion = windowsAssemblyVersion.ToString();
121 var bundlePlatform = bundleSymbol.Platform == Platform.X64 ? "amd64" : bundleSymbol.Platform.ToString().ToLower();
122 var bundleDescription = bundleSymbol.Name;
123
124 using (var memoryStream = new MemoryStream())
125 using (var writer = new XmlTextWriter(memoryStream, Encoding.UTF8))
126 {
127 writer.WriteStartDocument();
128
129 writer.WriteStartElement("assembly", asmv1Namespace);
130 writer.WriteAttributeString("manifestVersion", "1.0");
131
132 writer.WriteStartElement("assemblyIdentity");
133 writer.WriteAttributeString("name", bundleFileName);
134 writer.WriteAttributeString("version", bundleAssemblyVersion);
135 writer.WriteAttributeString("processorArchitecture", bundlePlatform);
136 writer.WriteAttributeString("type", "win32");
137 writer.WriteEndElement(); // </assemblyIdentity>
138
139 if (!String.IsNullOrEmpty(bundleDescription))
140 {
141 writer.WriteStartElement("description");
142 writer.WriteString(bundleDescription);
143 writer.WriteEndElement();
144 }
145
146 writer.WriteStartElement("dependency");
147 writer.WriteStartElement("dependentAssembly");
148 writer.WriteStartElement("assemblyIdentity");
149 writer.WriteAttributeString("name", "Microsoft.Windows.Common-Controls");
150 writer.WriteAttributeString("version", "6.0.0.0");
151 writer.WriteAttributeString("processorArchitecture", bundlePlatform);
152 writer.WriteAttributeString("publicKeyToken", "6595b64144ccf1df");
153 writer.WriteAttributeString("language", "*");
154 writer.WriteAttributeString("type", "win32");
155 writer.WriteEndElement(); // </assemblyIdentity>
156 writer.WriteEndElement(); // </dependentAssembly>
157 writer.WriteEndElement(); // </dependency>
158
159 writer.WriteStartElement("compatibility", compatv1Namespace);
160 writer.WriteStartElement("application");
161
162 writer.WriteStartElement("supportedOS");
163 writer.WriteAttributeString("Id", "{e2011457-1546-43c5-a5fe-008deee3d3f0}"); // Windows Vista
164 writer.WriteEndElement();
165 writer.WriteStartElement("supportedOS");
166 writer.WriteAttributeString("Id", "{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"); // Windows 7
167 writer.WriteEndElement();
168 writer.WriteStartElement("supportedOS");
169 writer.WriteAttributeString("Id", "{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"); // Windows 8
170 writer.WriteEndElement();
171 writer.WriteStartElement("supportedOS");
172 writer.WriteAttributeString("Id", "{1f676c76-80e1-4239-95bb-83d0f6d0da78}"); // Windows 8.1
173 writer.WriteEndElement();
174 writer.WriteStartElement("supportedOS");
175 writer.WriteAttributeString("Id", "{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"); // Windows 10
176 writer.WriteEndElement();
177
178 writer.WriteEndElement(); // </application>
179 writer.WriteEndElement(); // </compatibility>
180
181 writer.WriteStartElement("trustInfo", asmv3Namespace);
182 writer.WriteStartElement("security");
183 writer.WriteStartElement("requestedPrivileges");
184 writer.WriteStartElement("requestedExecutionLevel");
185 writer.WriteAttributeString("level", "asInvoker");
186 writer.WriteAttributeString("uiAccess", "false");
187 writer.WriteEndElement(); // </requestedExecutionLevel>
188 writer.WriteEndElement(); // </requestedPrivileges>
189 writer.WriteEndElement(); // </security>
190 writer.WriteEndElement(); // </trustInfo>
191
192 if (bootstrapperApplicationSymbol.DpiAwareness != WixBootstrapperApplicationDpiAwarenessType.Unaware)
193 {
194 string dpiAwareValue = null;
195 string dpiAwarenessValue = null;
196 string gdiScalingValue = null;
197
198 switch(bootstrapperApplicationSymbol.DpiAwareness)
199 {
200 case WixBootstrapperApplicationDpiAwarenessType.GdiScaled:
201 gdiScalingValue = "true";
202 break;
203 case WixBootstrapperApplicationDpiAwarenessType.PerMonitor:
204 dpiAwareValue = "true/pm";
205 break;
206 case WixBootstrapperApplicationDpiAwarenessType.PerMonitorV2:
207 dpiAwareValue = "true/pm";
208 dpiAwarenessValue = "PerMonitorV2, PerMonitor";
209 break;
210 case WixBootstrapperApplicationDpiAwarenessType.System:
211 dpiAwareValue = "true";
212 break;
213 }
214
215 writer.WriteStartElement("application", asmv3Namespace);
216 writer.WriteStartElement("windowsSettings");
217
218 if (dpiAwareValue != null)
219 {
220 writer.WriteStartElement("dpiAware", ws2005Namespace);
221 writer.WriteString(dpiAwareValue);
222 writer.WriteEndElement();
223 }
224
225 if (dpiAwarenessValue != null)
226 {
227 writer.WriteStartElement("dpiAwareness", ws2016Namespace);
228 writer.WriteString(dpiAwarenessValue);
229 writer.WriteEndElement();
230 }
231
232 if (gdiScalingValue != null)
233 {
234 writer.WriteStartElement("gdiScaling", ws2017Namespace);
235 writer.WriteString(gdiScalingValue);
236 writer.WriteEndElement();
237 }
238
239 writer.WriteEndElement(); // </windowSettings>
240 writer.WriteEndElement(); // </application>
241 }
242
243 writer.WriteEndDocument(); // </assembly>
244 writer.Close();
245
246 return memoryStream.ToArray();
247 }
248 }
249
250 private static Version GetWindowsAssemblyVersion(WixBundleSymbol bundleSymbol)
251 {
252 // Ensure the bundle info provides a full four part version.
253 var fourPartVersion = new Version(bundleSymbol.Version);
254 var major = (fourPartVersion.Major < 0) ? 0 : fourPartVersion.Major;
255 var minor = (fourPartVersion.Minor < 0) ? 0 : fourPartVersion.Minor;
256 var build = (fourPartVersion.Build < 0) ? 0 : fourPartVersion.Build;
257 var revision = (fourPartVersion.Revision < 0) ? 0 : fourPartVersion.Revision;
258
259 if (UInt16.MaxValue < major || UInt16.MaxValue < minor || UInt16.MaxValue < build || UInt16.MaxValue < revision)
260 {
261 throw new WixException(ErrorMessages.InvalidModuleOrBundleVersion(bundleSymbol.SourceLineNumbers, "Bundle", bundleSymbol.Version));
262 }
263
264 return new Version(major, minor, build, revision);
265 }
266
267 private static void UpdateBurnResources(string bundleTempPath, string outputPath, WixBundleSymbol bundleInfo, Version windowsAssemblyVersion, byte[] applicationManifestData)
268 {
269 const int burnLocale = 1033;
270 var resources = new Dtf.Resources.ResourceCollection();
271 var version = new Dtf.Resources.VersionResource("#1", burnLocale);
272
273 version.Load(bundleTempPath);
274 resources.Add(version);
275
276 version.FileVersion = windowsAssemblyVersion;
277 version.ProductVersion = windowsAssemblyVersion;
278
279 var strings = version[burnLocale] ?? version.Add(burnLocale);
280 strings["LegalCopyright"] = bundleInfo.Copyright;
281 strings["OriginalFilename"] = Path.GetFileName(outputPath);
282 strings["FileVersion"] = bundleInfo.Version; // string versions do not have to be four parts.
283 strings["ProductVersion"] = bundleInfo.Version; // string versions do not have to be four parts.
284
285 if (!String.IsNullOrEmpty(bundleInfo.Name))
286 {
287 strings["ProductName"] = bundleInfo.Name;
288 strings["FileDescription"] = bundleInfo.Name;
289 }
290
291 if (!String.IsNullOrEmpty(bundleInfo.Manufacturer))
292 {
293 strings["CompanyName"] = bundleInfo.Manufacturer;
294 }
295 else
296 {
297 strings["CompanyName"] = String.Empty;
298 }
299
300 if (!String.IsNullOrEmpty(bundleInfo.IconSourceFile))
301 {
302 var iconGroup = new Dtf.Resources.GroupIconResource("#1", burnLocale);
303 iconGroup.ReadFromFile(bundleInfo.IconSourceFile);
304 resources.Add(iconGroup);
305
306 foreach (var icon in iconGroup.Icons)
307 {
308 resources.Add(icon);
309 }
310 }
311
312 if (!String.IsNullOrEmpty(bundleInfo.SplashScreenSourceFile))
313 {
314 var bitmap = new Dtf.Resources.BitmapResource("#1", burnLocale);
315 bitmap.ReadFromFile(bundleInfo.SplashScreenSourceFile);
316 resources.Add(bitmap);
317 }
318
319 var manifestResource = new Resource(ResourceType.Manifest, "#1", burnLocale, applicationManifestData);
320 resources.Add(manifestResource);
321
322 resources.Save(bundleTempPath);
323 }
324 }
325}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn.Bundles
4{
5 using System;
6 using System.Globalization;
7 using System.IO;
8 using System.Text;
9 using System.Xml;
10 using WixToolset.Data;
11 using WixToolset.Data.Burn;
12 using WixToolset.Data.Symbols;
13
14 internal class CreateBundleExtensionManifestCommand
15 {
16 public CreateBundleExtensionManifestCommand(IntermediateSection section, WixBundleSymbol bundleSymbol, int lastUXPayloadIndex, string intermediateFolder, IInternalBurnBackendHelper internalBurnBackendHelper)
17 {
18 this.Section = section;
19 this.BundleSymbol = bundleSymbol;
20 this.LastUXPayloadIndex = lastUXPayloadIndex;
21 this.IntermediateFolder = intermediateFolder;
22 this.InternalBurnBackendHelper = internalBurnBackendHelper;
23 }
24
25 private IntermediateSection Section { get; }
26
27 private WixBundleSymbol BundleSymbol { get; }
28
29 private IInternalBurnBackendHelper InternalBurnBackendHelper { get; }
30
31 private int LastUXPayloadIndex { get; }
32
33 private string IntermediateFolder { get; }
34
35 public WixBundlePayloadSymbol BundleExtensionManifestPayloadRow { get; private set; }
36
37 public string OutputPath { get; private set; }
38
39 public void Execute()
40 {
41 this.OutputPath = this.CreateBundleExtensionManifest();
42
43 this.BundleExtensionManifestPayloadRow = this.CreateBundleExtensionManifestPayloadRow(this.OutputPath);
44 }
45
46 private string CreateBundleExtensionManifest()
47 {
48 var path = Path.Combine(this.IntermediateFolder, "wix-bextdata.xml");
49
50 Directory.CreateDirectory(Path.GetDirectoryName(path));
51
52 using (var writer = new XmlTextWriter(path, Encoding.Unicode))
53 {
54 writer.Formatting = Formatting.Indented;
55 writer.WriteStartDocument();
56 writer.WriteStartElement("BundleExtensionData", BurnCommon.BundleExtensionDataNamespace);
57
58 this.InternalBurnBackendHelper.WriteBundleExtensionData(writer);
59
60 writer.WriteEndElement();
61 writer.WriteEndDocument();
62 }
63
64 return path;
65 }
66
67 private WixBundlePayloadSymbol CreateBundleExtensionManifestPayloadRow(string bextManifestPath)
68 {
69 var generatedId = this.InternalBurnBackendHelper.GenerateIdentifier("ux", BurnCommon.BundleExtensionDataFileName);
70
71 this.Section.AddSymbol(new WixGroupSymbol(this.BundleSymbol.SourceLineNumbers)
72 {
73 ParentType = ComplexReferenceParentType.Container,
74 ParentId = BurnConstants.BurnUXContainerName,
75 ChildType = ComplexReferenceChildType.Payload,
76 ChildId = generatedId
77 });
78
79 var symbol = this.Section.AddSymbol(new WixBundlePayloadSymbol(this.BundleSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, generatedId))
80 {
81 Name = BurnCommon.BundleExtensionDataFileName,
82 SourceFile = new IntermediateFieldPathValue { Path = bextManifestPath },
83 Compressed = true,
84 UnresolvedSourceFile = bextManifestPath,
85 ContainerRef = BurnConstants.BurnUXContainerName,
86 EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnUXContainerEmbeddedIdFormat, this.LastUXPayloadIndex),
87 Packaging = PackagingType.Embedded,
88 });
89
90 var fileInfo = new FileInfo(bextManifestPath);
91
92 symbol.FileSize = (int)fileInfo.Length;
93
94 symbol.Hash = BundleHashAlgorithm.Hash(fileInfo);
95
96 return symbol;
97 }
98 }
99}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn.Bundles
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.Globalization;
9 using System.IO;
10 using System.Linq;
11 using System.Text;
12 using System.Xml;
13 using WixToolset.Data;
14 using WixToolset.Data.Burn;
15 using WixToolset.Data.Symbols;
16 using WixToolset.Extensibility;
17 using WixToolset.Extensibility.Services;
18
19 internal class CreateBurnManifestCommand
20 {
21 public CreateBurnManifestCommand(string executableName, IntermediateSection section, WixBundleSymbol bundleSymbol, IEnumerable<WixBundleContainerSymbol> containers, WixChainSymbol chainSymbol, IEnumerable<PackageFacade> orderedPackages, IEnumerable<WixBundleRollbackBoundarySymbol> boundaries, IEnumerable<WixBundlePayloadSymbol> uxPayloads, Dictionary<string, WixBundlePayloadSymbol> allPayloadsById, Dictionary<string, Dictionary<string, WixBundlePayloadSymbol>> packagesPayloads, IEnumerable<ISearchFacade> orderedSearches, string intermediateFolder)
22 {
23 this.ExecutableName = executableName;
24 this.Section = section;
25 this.BundleSymbol = bundleSymbol;
26 this.Chain = chainSymbol;
27 this.Containers = containers;
28 this.OrderedPackages = orderedPackages;
29 this.RollbackBoundaries = boundaries;
30 this.UXContainerPayloads = uxPayloads;
31 this.Payloads = allPayloadsById;
32 this.PackagesPayloads = packagesPayloads;
33 this.OrderedSearches = orderedSearches;
34 this.IntermediateFolder = intermediateFolder;
35 }
36
37 public string OutputPath { get; private set; }
38
39 private string ExecutableName { get; }
40
41 private IntermediateSection Section { get; }
42
43 private WixBundleSymbol BundleSymbol { get; }
44
45 private WixChainSymbol Chain { get; }
46
47 private IEnumerable<WixBundleRollbackBoundarySymbol> RollbackBoundaries { get; }
48
49 private IEnumerable<PackageFacade> OrderedPackages { get; }
50
51 private IEnumerable<ISearchFacade> OrderedSearches { get; }
52
53 private Dictionary<string, WixBundlePayloadSymbol> Payloads { get; }
54
55 private Dictionary<string, Dictionary<string, WixBundlePayloadSymbol>> PackagesPayloads { get; }
56
57 private IEnumerable<WixBundleContainerSymbol> Containers { get; }
58
59 private IEnumerable<WixBundlePayloadSymbol> UXContainerPayloads { get; }
60
61 private string IntermediateFolder { get; }
62
63 public void Execute()
64 {
65 this.OutputPath = Path.Combine(this.IntermediateFolder, "bundle-manifest.xml");
66
67 using (var writer = new XmlTextWriter(this.OutputPath, Encoding.UTF8))
68 {
69 writer.WriteStartDocument();
70
71 writer.WriteStartElement("BurnManifest", BurnCommon.BurnNamespace);
72
73 // Write the condition, if there is one
74 if (null != this.BundleSymbol.Condition)
75 {
76 writer.WriteElementString("Condition", this.BundleSymbol.Condition);
77 }
78
79 // Write the log element if default logging wasn't disabled.
80 if (!String.IsNullOrEmpty(this.BundleSymbol.LogPrefix))
81 {
82 writer.WriteStartElement("Log");
83 if (!String.IsNullOrEmpty(this.BundleSymbol.LogPathVariable))
84 {
85 writer.WriteAttributeString("PathVariable", this.BundleSymbol.LogPathVariable);
86 }
87 writer.WriteAttributeString("Prefix", this.BundleSymbol.LogPrefix);
88 writer.WriteAttributeString("Extension", this.BundleSymbol.LogExtension);
89 writer.WriteEndElement();
90 }
91
92
93 // Get update if specified.
94 var updateSymbol = this.Section.Symbols.OfType<WixBundleUpdateSymbol>().FirstOrDefault();
95
96 if (null != updateSymbol)
97 {
98 writer.WriteStartElement("Update");
99 writer.WriteAttributeString("Location", updateSymbol.Location);
100 writer.WriteEndElement(); // </Update>
101 }
102
103 // Write the RelatedBundle elements
104
105 // For the related bundles with duplicated identifiers the second instance is ignored (i.e. the Duplicates
106 // enumeration in the index row list is not used).
107 var relatedBundles = this.Section.Symbols.OfType<WixRelatedBundleSymbol>();
108 var distinctRelatedBundles = new HashSet<string>();
109
110 foreach (var relatedBundle in relatedBundles)
111 {
112 if (distinctRelatedBundles.Add(relatedBundle.BundleId))
113 {
114 writer.WriteStartElement("RelatedBundle");
115 writer.WriteAttributeString("Id", relatedBundle.BundleId);
116 writer.WriteAttributeString("Action", relatedBundle.Action.ToString());
117 writer.WriteEndElement();
118 }
119 }
120
121 // Write the variables
122 var variables = this.Section.Symbols.OfType<WixBundleVariableSymbol>();
123
124 foreach (var variable in variables)
125 {
126 writer.WriteStartElement("Variable");
127 writer.WriteAttributeString("Id", variable.Id.Id);
128 if (variable.Type != WixBundleVariableType.Unknown)
129 {
130 writer.WriteAttributeString("Value", variable.Value);
131
132 switch (variable.Type)
133 {
134 case WixBundleVariableType.Formatted:
135 writer.WriteAttributeString("Type", "formatted");
136 break;
137 case WixBundleVariableType.Numeric:
138 writer.WriteAttributeString("Type", "numeric");
139 break;
140 case WixBundleVariableType.String:
141 writer.WriteAttributeString("Type", "string");
142 break;
143 case WixBundleVariableType.Version:
144 writer.WriteAttributeString("Type", "version");
145 break;
146 }
147 }
148 writer.WriteAttributeString("Hidden", variable.Hidden ? "yes" : "no");
149 writer.WriteAttributeString("Persisted", variable.Persisted ? "yes" : "no");
150 writer.WriteEndElement();
151 }
152
153 // Write the searches
154 foreach (var searchinfo in this.OrderedSearches)
155 {
156 searchinfo.WriteXml(writer);
157 }
158
159 // write the UX element
160 writer.WriteStartElement("UX");
161 if (!String.IsNullOrEmpty(this.BundleSymbol.SplashScreenSourceFile))
162 {
163 writer.WriteAttributeString("SplashScreen", "yes");
164 }
165
166 // write the UX allPayloads...
167 foreach (var payload in this.UXContainerPayloads)
168 {
169 this.WriteBurnManifestUXPayload(writer, payload);
170 }
171
172 writer.WriteEndElement(); // </UX>
173
174 foreach (var container in this.Containers)
175 {
176 if (!String.IsNullOrEmpty(container.WorkingPath) && BurnConstants.BurnUXContainerName != container.Id.Id)
177 {
178 writer.WriteStartElement("Container");
179 this.WriteBurnManifestContainerAttributes(writer, this.ExecutableName, container);
180 writer.WriteEndElement();
181 }
182 }
183
184 foreach (var payload in this.Payloads.Values.Where(p => p.ContainerRef != BurnConstants.BurnUXContainerName))
185 {
186 this.WriteBurnManifestPayload(writer, payload);
187 }
188
189 foreach (var rollbackBoundary in this.RollbackBoundaries)
190 {
191 writer.WriteStartElement("RollbackBoundary");
192 writer.WriteAttributeString("Id", rollbackBoundary.Id.Id);
193 writer.WriteAttributeString("Vital", rollbackBoundary.Vital == false ? "no" : "yes");
194 writer.WriteAttributeString("Transaction", rollbackBoundary.Transaction == true ? "yes" : "no");
195 writer.WriteEndElement();
196 }
197
198 // Write the registration information...
199 writer.WriteStartElement("Registration");
200
201 writer.WriteAttributeString("Id", this.BundleSymbol.BundleId);
202 writer.WriteAttributeString("ExecutableName", this.ExecutableName);
203 writer.WriteAttributeString("PerMachine", this.BundleSymbol.PerMachine ? "yes" : "no");
204 writer.WriteAttributeString("Tag", this.BundleSymbol.Tag);
205 writer.WriteAttributeString("Version", this.BundleSymbol.Version);
206 writer.WriteAttributeString("ProviderKey", this.BundleSymbol.ProviderKey);
207
208 writer.WriteStartElement("Arp");
209 writer.WriteAttributeString("Register", (this.BundleSymbol.DisableModify || this.BundleSymbol.SingleChangeUninstallButton) && this.BundleSymbol.DisableRemove ? "no" : "yes"); // do not register if disabled modify and remove.
210 writer.WriteAttributeString("DisplayName", this.BundleSymbol.Name);
211 writer.WriteAttributeString("DisplayVersion", this.BundleSymbol.Version);
212
213 if (!String.IsNullOrEmpty(this.BundleSymbol.Manufacturer))
214 {
215 writer.WriteAttributeString("Publisher", this.BundleSymbol.Manufacturer);
216 }
217
218 if (!String.IsNullOrEmpty(this.BundleSymbol.HelpUrl))
219 {
220 writer.WriteAttributeString("HelpLink", this.BundleSymbol.HelpUrl);
221 }
222
223 if (!String.IsNullOrEmpty(this.BundleSymbol.HelpTelephone))
224 {
225 writer.WriteAttributeString("HelpTelephone", this.BundleSymbol.HelpTelephone);
226 }
227
228 if (!String.IsNullOrEmpty(this.BundleSymbol.AboutUrl))
229 {
230 writer.WriteAttributeString("AboutUrl", this.BundleSymbol.AboutUrl);
231 }
232
233 if (!String.IsNullOrEmpty(this.BundleSymbol.UpdateUrl))
234 {
235 writer.WriteAttributeString("UpdateUrl", this.BundleSymbol.UpdateUrl);
236 }
237
238 if (!String.IsNullOrEmpty(this.BundleSymbol.ParentName))
239 {
240 writer.WriteAttributeString("ParentDisplayName", this.BundleSymbol.ParentName);
241 }
242
243 if (this.BundleSymbol.DisableModify)
244 {
245 writer.WriteAttributeString("DisableModify", "yes");
246 }
247
248 if (this.BundleSymbol.DisableRemove)
249 {
250 writer.WriteAttributeString("DisableRemove", "yes");
251 }
252
253 if (this.BundleSymbol.SingleChangeUninstallButton)
254 {
255 writer.WriteAttributeString("DisableModify", "button");
256 }
257 writer.WriteEndElement(); // </Arp>
258
259 // Get update registration if specified.
260 var updateRegistrationInfo = this.Section.Symbols.OfType<WixUpdateRegistrationSymbol>().FirstOrDefault();
261
262 if (null != updateRegistrationInfo)
263 {
264 writer.WriteStartElement("Update"); // <Update>
265 writer.WriteAttributeString("Manufacturer", updateRegistrationInfo.Manufacturer);
266
267 if (!String.IsNullOrEmpty(updateRegistrationInfo.Department))
268 {
269 writer.WriteAttributeString("Department", updateRegistrationInfo.Department);
270 }
271
272 if (!String.IsNullOrEmpty(updateRegistrationInfo.ProductFamily))
273 {
274 writer.WriteAttributeString("ProductFamily", updateRegistrationInfo.ProductFamily);
275 }
276
277 writer.WriteAttributeString("Name", updateRegistrationInfo.Name);
278 writer.WriteAttributeString("Classification", updateRegistrationInfo.Classification);
279 writer.WriteEndElement(); // </Update>
280 }
281
282 foreach (var bundleTagSymbol in this.Section.Symbols.OfType<WixBundleTagSymbol>())
283 {
284 writer.WriteStartElement("SoftwareTag");
285 writer.WriteAttributeString("Filename", bundleTagSymbol.Filename);
286 writer.WriteAttributeString("Regid", bundleTagSymbol.Regid);
287 writer.WriteAttributeString("Path", bundleTagSymbol.InstallPath);
288 writer.WriteCData(bundleTagSymbol.Xml);
289 writer.WriteEndElement();
290 }
291
292 writer.WriteEndElement(); // </Register>
293
294 // write the Chain...
295 writer.WriteStartElement("Chain");
296 if (this.Chain.DisableRollback)
297 {
298 writer.WriteAttributeString("DisableRollback", "yes");
299 }
300
301 if (this.Chain.DisableSystemRestore)
302 {
303 writer.WriteAttributeString("DisableSystemRestore", "yes");
304 }
305
306 if (this.Chain.ParallelCache)
307 {
308 writer.WriteAttributeString("ParallelCache", "yes");
309 }
310
311 // Index a few tables by package.
312 var targetCodesByPatch = this.Section.Symbols.OfType<WixBundlePatchTargetCodeSymbol>().ToLookup(r => r.PackageRef);
313 var msiFeaturesByPackage = this.Section.Symbols.OfType<WixBundleMsiFeatureSymbol>().ToLookup(r => r.PackageRef);
314 var msiPropertiesByPackage = this.Section.Symbols.OfType<WixBundleMsiPropertySymbol>().ToLookup(r => r.PackageRef);
315 var relatedPackagesByPackage = this.Section.Symbols.OfType<WixBundleRelatedPackageSymbol>().ToLookup(r => r.PackageRef);
316 var slipstreamMspsByPackage = this.Section.Symbols.OfType<WixBundleSlipstreamMspSymbol>().ToLookup(r => r.TargetPackageRef);
317 var exitCodesByPackage = this.Section.Symbols.OfType<WixBundlePackageExitCodeSymbol>().ToLookup(r => r.ChainPackageId);
318 var commandLinesByPackage = this.Section.Symbols.OfType<WixBundlePackageCommandLineSymbol>().ToLookup(r => r.WixBundlePackageRef);
319
320 var dependenciesByPackage = this.Section.Symbols.OfType<WixDependencyProviderSymbol>().ToLookup(p => p.ParentRef);
321
322
323 // Build up the list of target codes from all the MSPs in the chain.
324 var targetCodes = new List<WixBundlePatchTargetCodeSymbol>();
325
326 foreach (var package in this.OrderedPackages)
327 {
328 writer.WriteStartElement(String.Format(CultureInfo.InvariantCulture, "{0}Package", package.PackageSymbol.Type));
329
330 writer.WriteAttributeString("Id", package.PackageId);
331
332 switch (package.PackageSymbol.Cache)
333 {
334 case YesNoAlwaysType.No:
335 writer.WriteAttributeString("Cache", "remove");
336 break;
337 case YesNoAlwaysType.Yes:
338 writer.WriteAttributeString("Cache", "keep");
339 break;
340 case YesNoAlwaysType.Always:
341 writer.WriteAttributeString("Cache", "force");
342 break;
343 }
344
345 writer.WriteAttributeString("CacheId", package.PackageSymbol.CacheId);
346 writer.WriteAttributeString("InstallSize", Convert.ToString(package.PackageSymbol.InstallSize));
347 writer.WriteAttributeString("Size", Convert.ToString(package.PackageSymbol.Size));
348 writer.WriteAttributeString("PerMachine", YesNoDefaultType.Yes == package.PackageSymbol.PerMachine ? "yes" : "no");
349 writer.WriteAttributeString("Permanent", package.PackageSymbol.Permanent ? "yes" : "no");
350 writer.WriteAttributeString("Vital", package.PackageSymbol.Vital == false ? "no" : "yes");
351
352 if (null != package.PackageSymbol.RollbackBoundaryRef)
353 {
354 writer.WriteAttributeString("RollbackBoundaryForward", package.PackageSymbol.RollbackBoundaryRef);
355 }
356
357 if (!String.IsNullOrEmpty(package.PackageSymbol.RollbackBoundaryBackwardRef))
358 {
359 writer.WriteAttributeString("RollbackBoundaryBackward", package.PackageSymbol.RollbackBoundaryBackwardRef);
360 }
361
362 if (!String.IsNullOrEmpty(package.PackageSymbol.LogPathVariable))
363 {
364 writer.WriteAttributeString("LogPathVariable", package.PackageSymbol.LogPathVariable);
365 }
366
367 if (!String.IsNullOrEmpty(package.PackageSymbol.RollbackLogPathVariable))
368 {
369 writer.WriteAttributeString("RollbackLogPathVariable", package.PackageSymbol.RollbackLogPathVariable);
370 }
371
372 if (!String.IsNullOrEmpty(package.PackageSymbol.InstallCondition))
373 {
374 writer.WriteAttributeString("InstallCondition", package.PackageSymbol.InstallCondition);
375 }
376
377 if (package.SpecificPackageSymbol is WixBundleExePackageSymbol exePackage) // EXE
378 {
379 writer.WriteAttributeString("DetectCondition", exePackage.DetectCondition);
380 writer.WriteAttributeString("InstallArguments", exePackage.InstallCommand);
381 writer.WriteAttributeString("UninstallArguments", exePackage.UninstallCommand);
382 writer.WriteAttributeString("RepairArguments", exePackage.RepairCommand);
383 writer.WriteAttributeString("Repairable", exePackage.Repairable ? "yes" : "no");
384 if (!String.IsNullOrEmpty(exePackage.ExeProtocol))
385 {
386 writer.WriteAttributeString("Protocol", exePackage.ExeProtocol);
387 }
388 }
389 else if (package.SpecificPackageSymbol is WixBundleMsiPackageSymbol msiPackage) // MSI
390 {
391 writer.WriteAttributeString("ProductCode", msiPackage.ProductCode);
392 writer.WriteAttributeString("Language", msiPackage.ProductLanguage.ToString(CultureInfo.InvariantCulture));
393 writer.WriteAttributeString("Version", msiPackage.ProductVersion);
394 if (!String.IsNullOrEmpty(msiPackage.UpgradeCode))
395 {
396 writer.WriteAttributeString("UpgradeCode", msiPackage.UpgradeCode);
397 }
398 }
399 else if (package.SpecificPackageSymbol is WixBundleMspPackageSymbol mspPackage) // MSP
400 {
401 writer.WriteAttributeString("PatchCode", mspPackage.PatchCode);
402 writer.WriteAttributeString("PatchXml", mspPackage.PatchXml);
403
404 // If there is still a chance that all of our patches will target a narrow set of
405 // product codes, add the patch list to the overall list.
406 if (null != targetCodes)
407 {
408 if (!mspPackage.TargetUnspecified)
409 {
410 var patchTargetCodes = targetCodesByPatch[mspPackage.Id.Id];
411
412 targetCodes.AddRange(patchTargetCodes);
413 }
414 else // we have a patch that targets the world, so throw the whole list away.
415 {
416 targetCodes = null;
417 }
418 }
419 }
420 else if (package.SpecificPackageSymbol is WixBundleMsuPackageSymbol msuPackage) // MSU
421 {
422 writer.WriteAttributeString("DetectCondition", msuPackage.DetectCondition);
423 writer.WriteAttributeString("KB", msuPackage.MsuKB);
424 }
425
426 var packageMsiFeatures = msiFeaturesByPackage[package.PackageId];
427
428 foreach (var feature in packageMsiFeatures)
429 {
430 writer.WriteStartElement("MsiFeature");
431 writer.WriteAttributeString("Id", feature.Name);
432 writer.WriteEndElement();
433 }
434
435 var packageMsiProperties = msiPropertiesByPackage[package.PackageId];
436
437 foreach (var msiProperty in packageMsiProperties)
438 {
439 writer.WriteStartElement("MsiProperty");
440 writer.WriteAttributeString("Id", msiProperty.Name);
441 writer.WriteAttributeString("Value", msiProperty.Value);
442 if (!String.IsNullOrEmpty(msiProperty.Condition))
443 {
444 writer.WriteAttributeString("Condition", msiProperty.Condition);
445 }
446 writer.WriteEndElement();
447 }
448
449 var packageSlipstreamMsps = slipstreamMspsByPackage[package.PackageId];
450
451 foreach (var slipstreamMsp in packageSlipstreamMsps)
452 {
453 writer.WriteStartElement("SlipstreamMsp");
454 writer.WriteAttributeString("Id", slipstreamMsp.MspPackageRef);
455 writer.WriteEndElement();
456 }
457
458 var packageExitCodes = exitCodesByPackage[package.PackageId];
459
460 foreach (var exitCode in packageExitCodes)
461 {
462 writer.WriteStartElement("ExitCode");
463
464 if (exitCode.Code.HasValue)
465 {
466 writer.WriteAttributeString("Code", unchecked((uint)exitCode.Code).ToString(CultureInfo.InvariantCulture));
467 }
468 else
469 {
470 writer.WriteAttributeString("Code", "*");
471 }
472
473 writer.WriteAttributeString("Type", ((int)exitCode.Behavior).ToString(CultureInfo.InvariantCulture));
474 writer.WriteEndElement();
475 }
476
477 var packageCommandLines = commandLinesByPackage[package.PackageId];
478
479 foreach (var commandLine in packageCommandLines)
480 {
481 writer.WriteStartElement("CommandLine");
482 writer.WriteAttributeString("InstallArgument", commandLine.InstallArgument);
483 writer.WriteAttributeString("UninstallArgument", commandLine.UninstallArgument);
484 writer.WriteAttributeString("RepairArgument", commandLine.RepairArgument);
485 writer.WriteAttributeString("Condition", commandLine.Condition);
486 writer.WriteEndElement();
487 }
488
489 // Output the dependency information.
490 var dependencies = dependenciesByPackage[package.PackageId];
491
492 foreach (var dependency in dependencies)
493 {
494 writer.WriteStartElement("Provides");
495 writer.WriteAttributeString("Key", dependency.ProviderKey);
496
497 if (!String.IsNullOrEmpty(dependency.Version))
498 {
499 writer.WriteAttributeString("Version", dependency.Version);
500 }
501
502 if (!String.IsNullOrEmpty(dependency.DisplayName))
503 {
504 writer.WriteAttributeString("DisplayName", dependency.DisplayName);
505 }
506
507 if (dependency.Imported)
508 {
509 // The package dependency was explicitly authored into the manifest.
510 writer.WriteAttributeString("Imported", "yes");
511 }
512
513 writer.WriteEndElement();
514 }
515
516 var packageRelatedPackages = relatedPackagesByPackage[package.PackageId];
517
518 foreach (var related in packageRelatedPackages)
519 {
520 writer.WriteStartElement("RelatedPackage");
521 writer.WriteAttributeString("Id", related.RelatedId);
522 if (!String.IsNullOrEmpty(related.MinVersion))
523 {
524 writer.WriteAttributeString("MinVersion", related.MinVersion);
525 writer.WriteAttributeString("MinInclusive", related.MinInclusive ? "yes" : "no");
526 }
527 if (!String.IsNullOrEmpty(related.MaxVersion))
528 {
529 writer.WriteAttributeString("MaxVersion", related.MaxVersion);
530 writer.WriteAttributeString("MaxInclusive", related.MaxInclusive ? "yes" : "no");
531 }
532 writer.WriteAttributeString("OnlyDetect", related.OnlyDetect ? "yes" : "no");
533
534 var relatedLanguages = related.Languages.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
535
536 if (0 < relatedLanguages.Length)
537 {
538 writer.WriteAttributeString("LangInclusive", related.LangInclusive ? "yes" : "no");
539 foreach (string language in relatedLanguages)
540 {
541 writer.WriteStartElement("Language");
542 writer.WriteAttributeString("Id", language);
543 writer.WriteEndElement();
544 }
545 }
546 writer.WriteEndElement();
547 }
548
549 // Write any contained Payloads with the PackagePayload being first
550 var packagePayloadId = package.PackageSymbol.PayloadRef;
551 writer.WriteStartElement("PayloadRef");
552 writer.WriteAttributeString("Id", packagePayloadId);
553 writer.WriteEndElement();
554
555 var packagePayloads = this.PackagesPayloads[package.PackageId];
556
557 foreach (var payload in packagePayloads.Values)
558 {
559 if (payload.Id.Id != packagePayloadId)
560 {
561 writer.WriteStartElement("PayloadRef");
562 writer.WriteAttributeString("Id", payload.Id.Id);
563 writer.WriteEndElement();
564 }
565 }
566
567 writer.WriteEndElement(); // </XxxPackage>
568 }
569 writer.WriteEndElement(); // </Chain>
570
571 if (null != targetCodes)
572 {
573 foreach (var targetCode in targetCodes)
574 {
575 writer.WriteStartElement("PatchTargetCode");
576 writer.WriteAttributeString("TargetCode", targetCode.TargetCode);
577 writer.WriteAttributeString("Product", targetCode.TargetsProductCode ? "yes" : "no");
578 writer.WriteEndElement();
579 }
580 }
581
582 // Write the ApprovedExeForElevation elements.
583 var approvedExesForElevation = this.Section.Symbols.OfType<WixApprovedExeForElevationSymbol>();
584
585 foreach (var approvedExeForElevation in approvedExesForElevation)
586 {
587 writer.WriteStartElement("ApprovedExeForElevation");
588 writer.WriteAttributeString("Id", approvedExeForElevation.Id.Id);
589 writer.WriteAttributeString("Key", approvedExeForElevation.Key);
590
591 if (!String.IsNullOrEmpty(approvedExeForElevation.ValueName))
592 {
593 writer.WriteAttributeString("ValueName", approvedExeForElevation.ValueName);
594 }
595
596 if (approvedExeForElevation.Win64)
597 {
598 writer.WriteAttributeString("Win64", "yes");
599 }
600
601 writer.WriteEndElement();
602 }
603
604 // Write the BundleExtension elements.
605 var bundleExtensions = this.Section.Symbols.OfType<WixBundleExtensionSymbol>();
606
607 foreach (var bundleExtension in bundleExtensions)
608 {
609 writer.WriteStartElement("BundleExtension");
610 writer.WriteAttributeString("Id", bundleExtension.Id.Id);
611 writer.WriteAttributeString("EntryPayloadId", bundleExtension.PayloadRef);
612
613 writer.WriteEndElement();
614 }
615
616 writer.WriteEndDocument(); // </BurnManifest>
617 }
618 }
619
620 private void WriteBurnManifestContainerAttributes(XmlTextWriter writer, string executableName, WixBundleContainerSymbol container)
621 {
622 writer.WriteAttributeString("Id", container.Id.Id);
623 writer.WriteAttributeString("FileSize", container.Size.Value.ToString(CultureInfo.InvariantCulture));
624 writer.WriteAttributeString("Hash", container.Hash);
625
626 if (ContainerType.Detached == container.Type)
627 {
628 if (!String.IsNullOrEmpty(container.DownloadUrl))
629 {
630 writer.WriteAttributeString("DownloadUrl", container.DownloadUrl);
631 }
632
633 writer.WriteAttributeString("FilePath", container.Name);
634 }
635 else if (ContainerType.Attached == container.Type)
636 {
637 writer.WriteAttributeString("FilePath", executableName); // attached containers use the name of the bundle since they are attached to the executable.
638 writer.WriteAttributeString("AttachedIndex", container.AttachedContainerIndex.Value.ToString(CultureInfo.InvariantCulture));
639 writer.WriteAttributeString("Attached", "yes");
640 writer.WriteAttributeString("Primary", "yes");
641 }
642 }
643
644 private void WriteBurnManifestPayload(XmlTextWriter writer, WixBundlePayloadSymbol payload)
645 {
646 writer.WriteStartElement("Payload");
647
648 writer.WriteAttributeString("Id", payload.Id.Id);
649 writer.WriteAttributeString("FilePath", payload.Name);
650 writer.WriteAttributeString("FileSize", payload.FileSize.Value.ToString(CultureInfo.InvariantCulture));
651 writer.WriteAttributeString("Hash", payload.Hash);
652
653 if (payload.LayoutOnly)
654 {
655 writer.WriteAttributeString("LayoutOnly", "yes");
656 }
657
658 if (!String.IsNullOrEmpty(payload.DownloadUrl))
659 {
660 writer.WriteAttributeString("DownloadUrl", payload.DownloadUrl);
661 }
662
663 switch (payload.Packaging)
664 {
665 case PackagingType.Embedded: // this means it's in a container.
666 Debug.Assert(BurnConstants.BurnUXContainerName != payload.ContainerRef);
667
668 writer.WriteAttributeString("Packaging", "embedded");
669 writer.WriteAttributeString("SourcePath", payload.EmbeddedId);
670 writer.WriteAttributeString("Container", payload.ContainerRef);
671 break;
672
673 case PackagingType.External:
674 writer.WriteAttributeString("Packaging", "external");
675 writer.WriteAttributeString("SourcePath", payload.Name);
676 break;
677 }
678
679 writer.WriteEndElement();
680 }
681
682 private void WriteBurnManifestUXPayload(XmlTextWriter writer, WixBundlePayloadSymbol payload)
683 {
684 Debug.Assert(PackagingType.Embedded == payload.Packaging);
685 Debug.Assert(BurnConstants.BurnUXContainerName == payload.ContainerRef);
686
687 writer.WriteStartElement("Payload");
688
689 // TODO: The engine should be updated to not require FileSize, Hash, or Packaging for UX payloads since the values are never used.
690 writer.WriteAttributeString("Id", payload.Id.Id);
691 writer.WriteAttributeString("FilePath", payload.Name);
692 writer.WriteAttributeString("FileSize", payload.FileSize.Value.ToString(CultureInfo.InvariantCulture));
693 writer.WriteAttributeString("Hash", payload.Hash);
694 writer.WriteAttributeString("Packaging", "embedded");
695 writer.WriteAttributeString("SourcePath", payload.EmbeddedId);
696
697 writer.WriteEndElement();
698 }
699 }
700}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn.Bundles
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 using WixToolset.Core.Native;
10 using WixToolset.Data;
11 using WixToolset.Data.Symbols;
12
13 /// <summary>
14 /// Creates cabinet files.
15 /// </summary>
16 internal class CreateContainerCommand
17 {
18 public CreateContainerCommand(IEnumerable<WixBundlePayloadSymbol> payloads, string outputPath, CompressionLevel? compressionLevel)
19 {
20 this.Payloads = payloads;
21 this.OutputPath = outputPath;
22 this.CompressionLevel = compressionLevel;
23 }
24
25 public CreateContainerCommand(string manifestPath, IEnumerable<WixBundlePayloadSymbol> payloads, string outputPath, CompressionLevel? compressionLevel)
26 {
27 this.ManifestFile = manifestPath;
28 this.Payloads = payloads;
29 this.OutputPath = outputPath;
30 this.CompressionLevel = compressionLevel;
31 }
32
33 private CompressionLevel? CompressionLevel { get; }
34
35 private string ManifestFile { get; }
36
37 private string OutputPath { get; }
38
39 private IEnumerable<WixBundlePayloadSymbol> Payloads { get; }
40
41 public string Hash { get; private set; }
42
43 public long Size { get; private set; }
44
45 public void Execute()
46 {
47 var cabinetPath = Path.GetFullPath(this.OutputPath);
48
49 var files = new List<CabinetCompressFile>();
50
51 // If a manifest was provided always add it as "payload 0" to the container.
52 if (!String.IsNullOrEmpty(this.ManifestFile))
53 {
54 files.Add(new CabinetCompressFile(this.ManifestFile, "0"));
55 }
56
57 files.AddRange(this.Payloads.Select(p => new CabinetCompressFile(p.SourceFile.Path, p.EmbeddedId)));
58
59 var cab = new Cabinet(cabinetPath);
60 cab.Compress(files, this.CompressionLevel ?? Data.CompressionLevel.Medium);
61
62 // Now that the container is created, set the outputs of the command.
63 var fileInfo = new FileInfo(cabinetPath);
64
65 this.Hash = BundleHashAlgorithm.Hash(fileInfo);
66
67 this.Size = fileInfo.Length;
68 }
69 }
70}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn.Bundles
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.IO;
9 using System.Linq;
10 using WixToolset.Data;
11 using WixToolset.Data.Burn;
12 using WixToolset.Data.Symbols;
13 using WixToolset.Extensibility.Data;
14 using WixToolset.Extensibility.Services;
15
16 internal class CreateNonUXContainers
17 {
18 public CreateNonUXContainers(IBackendHelper backendHelper, IMessaging messaging, WixBootstrapperApplicationDllSymbol bootstrapperApplicationDllSymbol, IEnumerable<WixBundleContainerSymbol> containerSymbols, Dictionary<string, WixBundlePayloadSymbol> payloadSymbols, string intermediateFolder, string layoutFolder, CompressionLevel? defaultCompressionLevel)
19 {
20 this.BackendHelper = backendHelper;
21 this.Messaging = messaging;
22 this.BootstrapperApplicationDllSymbol = bootstrapperApplicationDllSymbol;
23 this.Containers = containerSymbols;
24 this.PayloadSymbols = payloadSymbols;
25 this.IntermediateFolder = intermediateFolder;
26 this.LayoutFolder = layoutFolder;
27 this.DefaultCompressionLevel = defaultCompressionLevel;
28 }
29
30 public IEnumerable<IFileTransfer> FileTransfers { get; private set; }
31
32 public IEnumerable<ITrackedFile> TrackedFiles { get; private set; }
33
34 public WixBundleContainerSymbol UXContainer { get; set; }
35
36 public IEnumerable<WixBundlePayloadSymbol> UXContainerPayloads { get; private set; }
37
38 private IEnumerable<WixBundleContainerSymbol> Containers { get; }
39
40 private IBackendHelper BackendHelper { get; }
41
42 private IMessaging Messaging { get; }
43
44 private WixBootstrapperApplicationDllSymbol BootstrapperApplicationDllSymbol { get; }
45
46 private Dictionary<string, WixBundlePayloadSymbol> PayloadSymbols { get; }
47
48 private string IntermediateFolder { get; }
49
50 private string LayoutFolder { get; }
51
52 private CompressionLevel? DefaultCompressionLevel { get; }
53
54 public void Execute()
55 {
56 var fileTransfers = new List<IFileTransfer>();
57 var trackedFiles = new List<ITrackedFile>();
58 var uxPayloadSymbols = new List<WixBundlePayloadSymbol>();
59
60 var attachedContainerIndex = 1; // count starts at one because UX container is "0".
61
62 var payloadsByContainer = this.PayloadSymbols.Values.ToLookup(p => p.ContainerRef);
63
64 foreach (var container in this.Containers)
65 {
66 var containerId = container.Id.Id;
67
68 var containerPayloads = payloadsByContainer[containerId];
69
70 if (!containerPayloads.Any())
71 {
72 if (containerId != BurnConstants.BurnDefaultAttachedContainerName)
73 {
74 this.Messaging.Write(BurnBackendWarnings.EmptyContainer(container.SourceLineNumbers, containerId));
75 }
76 }
77 else if (BurnConstants.BurnUXContainerName == containerId)
78 {
79 this.UXContainer = container;
80
81 container.WorkingPath = Path.Combine(this.IntermediateFolder, container.Name);
82 container.AttachedContainerIndex = 0;
83
84 // Gather the list of UX payloads but ensure the BootstrapperApplicationDll Payload is the first
85 // in the list since that is the Payload that Burn attempts to load.
86 var baPayloadId = this.BootstrapperApplicationDllSymbol.Id.Id;
87
88 foreach (var uxPayload in containerPayloads)
89 {
90 if (uxPayload.Id.Id == baPayloadId)
91 {
92 uxPayloadSymbols.Insert(0, uxPayload);
93 }
94 else
95 {
96 uxPayloadSymbols.Add(uxPayload);
97 }
98 }
99 }
100 else
101 {
102 container.WorkingPath = Path.Combine(this.IntermediateFolder, container.Name);
103
104 // Add detached containers to the list of file transfers.
105 if (ContainerType.Detached == container.Type)
106 {
107 var transfer = this.BackendHelper.CreateFileTransfer(container.WorkingPath, Path.Combine(this.LayoutFolder, container.Name), true, container.SourceLineNumbers);
108 fileTransfers.Add(transfer);
109 }
110 else // update the attached container index.
111 {
112 Debug.Assert(ContainerType.Attached == container.Type);
113
114 container.AttachedContainerIndex = attachedContainerIndex;
115 ++attachedContainerIndex;
116 }
117 }
118 }
119
120 foreach (var container in this.Containers.Where(c => !String.IsNullOrEmpty(c.WorkingPath) && c.Id.Id != BurnConstants.BurnUXContainerName))
121 {
122 if (container.Type == ContainerType.Attached && attachedContainerIndex > 2 && container.Id.Id != BurnConstants.BurnDefaultAttachedContainerName)
123 {
124 this.Messaging.Write(BurnBackendErrors.MultipleAttachedContainersUnsupported(container.SourceLineNumbers, container.Id.Id));
125 }
126 }
127
128 if (!this.Messaging.EncounteredError)
129 {
130 foreach (var container in this.Containers.Where(c => !String.IsNullOrEmpty(c.WorkingPath) && c.Id.Id != BurnConstants.BurnUXContainerName))
131 {
132 this.CreateContainer(container, payloadsByContainer[container.Id.Id]);
133 trackedFiles.Add(this.BackendHelper.TrackFile(container.WorkingPath, TrackedFileType.Temporary, container.SourceLineNumbers));
134 }
135 }
136
137 this.UXContainerPayloads = uxPayloadSymbols;
138 this.FileTransfers = fileTransfers;
139 this.TrackedFiles = trackedFiles;
140 }
141
142 private void CreateContainer(WixBundleContainerSymbol container, IEnumerable<WixBundlePayloadSymbol> containerPayloads)
143 {
144 var command = new CreateContainerCommand(containerPayloads, container.WorkingPath, this.DefaultCompressionLevel);
145 command.Execute();
146
147 container.Hash = command.Hash;
148 container.Size = command.Size;
149 }
150 }
151}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn.Bundles
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using WixToolset.Data;
9 using WixToolset.Data.Burn;
10 using WixToolset.Data.Symbols;
11 using WixToolset.Extensibility.Services;
12
13 internal class DetectPayloadCollisionsCommand
14 {
15 public DetectPayloadCollisionsCommand(IMessaging messaging, Dictionary<string, WixBundleContainerSymbol> containerSymbols, IEnumerable<PackageFacade> packages, Dictionary<string, WixBundlePayloadSymbol> payloadSymbols, Dictionary<string, Dictionary<string, WixBundlePayloadSymbol>> packagePayloads)
16 {
17 this.Messaging = messaging;
18 this.Containers = containerSymbols;
19 this.Packages = packages;
20 this.PayloadSymbols = payloadSymbols;
21 this.PackagePayloads = packagePayloads;
22 }
23
24 private IMessaging Messaging { get; }
25
26 private Dictionary<string, WixBundleContainerSymbol> Containers { get; }
27
28 private IEnumerable<PackageFacade> Packages { get; }
29
30 private Dictionary<string, WixBundlePayloadSymbol> PayloadSymbols { get; }
31
32 private Dictionary<string, Dictionary<string, WixBundlePayloadSymbol>> PackagePayloads { get; }
33
34 public void Execute()
35 {
36 this.DetectAttachedContainerCollisions();
37 this.DetectExternalCollisions();
38 this.DetectPackageCacheCollisions();
39 }
40
41 public void DetectAttachedContainerCollisions()
42 {
43 var attachedContainerPayloadsByNameByContainer = new Dictionary<string, Dictionary<string, WixBundlePayloadSymbol>>();
44
45 foreach (var payload in this.PayloadSymbols.Values.Where(p => p.Packaging == PackagingType.Embedded))
46 {
47 var containerId = payload.ContainerRef;
48 var container = this.Containers[containerId];
49 if (container.Type == ContainerType.Attached)
50 {
51 if (!attachedContainerPayloadsByNameByContainer.TryGetValue(containerId, out var attachedContainerPayloadsByName))
52 {
53 attachedContainerPayloadsByName = new Dictionary<string, WixBundlePayloadSymbol>(StringComparer.OrdinalIgnoreCase);
54 attachedContainerPayloadsByNameByContainer.Add(containerId, attachedContainerPayloadsByName);
55 }
56
57 if (!attachedContainerPayloadsByName.TryGetValue(payload.Name, out var collisionPayload))
58 {
59 attachedContainerPayloadsByName.Add(payload.Name, payload);
60 }
61 else
62 {
63 if (containerId == BurnConstants.BurnUXContainerName)
64 {
65 this.Messaging.Write(BurnBackendErrors.BAContainerPayloadCollision(payload.SourceLineNumbers, payload.Id.Id, payload.Name));
66 this.Messaging.Write(BurnBackendErrors.BAContainerPayloadCollision2(collisionPayload.SourceLineNumbers));
67 }
68 else
69 {
70 this.Messaging.Write(BurnBackendWarnings.AttachedContainerPayloadCollision(payload.SourceLineNumbers, payload.Id.Id, payload.Name));
71 this.Messaging.Write(BurnBackendWarnings.AttachedContainerPayloadCollision2(collisionPayload.SourceLineNumbers));
72 }
73 }
74 }
75 }
76 }
77
78 public void DetectExternalCollisions()
79 {
80 var externalPayloadsByName = new Dictionary<string, IntermediateSymbol>(StringComparer.OrdinalIgnoreCase);
81
82 foreach (var payload in this.PayloadSymbols.Values.Where(p => p.Packaging == PackagingType.External))
83 {
84 if (!externalPayloadsByName.TryGetValue(payload.Name, out var collisionSymbol))
85 {
86 externalPayloadsByName.Add(payload.Name, payload);
87 }
88 else
89 {
90 this.Messaging.Write(BurnBackendErrors.ExternalPayloadCollision(payload.SourceLineNumbers, "Payload", payload.Id.Id, payload.Name));
91 this.Messaging.Write(BurnBackendErrors.ExternalPayloadCollision2(collisionSymbol.SourceLineNumbers));
92 }
93 }
94
95 foreach (var container in this.Containers.Values.Where(c => c.Type == ContainerType.Detached))
96 {
97 if (!externalPayloadsByName.TryGetValue(container.Name, out var collisionSymbol))
98 {
99 externalPayloadsByName.Add(container.Name, container);
100 }
101 else
102 {
103 this.Messaging.Write(BurnBackendErrors.ExternalPayloadCollision(container.SourceLineNumbers, "Container", container.Id.Id, container.Name));
104 this.Messaging.Write(BurnBackendErrors.ExternalPayloadCollision2(collisionSymbol.SourceLineNumbers));
105 }
106 }
107 }
108
109 public void DetectPackageCacheCollisions()
110 {
111 var packageCachePayloadsByNameByCacheId = new Dictionary<string, Dictionary<string, WixBundlePayloadSymbol>>();
112
113 foreach (var packageFacade in this.Packages)
114 {
115 var packagePayloads = this.PackagePayloads[packageFacade.PackageId];
116 if (!packageCachePayloadsByNameByCacheId.TryGetValue(packageFacade.PackageSymbol.CacheId, out var packageCachePayloadsByName))
117 {
118 packageCachePayloadsByName = new Dictionary<string, WixBundlePayloadSymbol>(StringComparer.OrdinalIgnoreCase);
119 packageCachePayloadsByNameByCacheId.Add(packageFacade.PackageSymbol.CacheId, packageCachePayloadsByName);
120 }
121
122 foreach (var payload in packagePayloads.Values)
123 {
124 if (!packageCachePayloadsByName.TryGetValue(payload.Name, out var collisionPayload))
125 {
126 packageCachePayloadsByName.Add(payload.Name, payload);
127 }
128 else
129 {
130 this.Messaging.Write(BurnBackendErrors.PackageCachePayloadCollision(payload.SourceLineNumbers, payload.Id.Id, payload.Name, packageFacade.PackageId));
131 this.Messaging.Write(BurnBackendErrors.PackageCachePayloadCollision2(collisionPayload.SourceLineNumbers));
132 }
133 }
134 }
135 }
136 }
137}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn.Bundles
4{
5 using System.Collections.Generic;
6 using System.Linq;
7 using WixToolset.Data;
8 using WixToolset.Data.Symbols;
9 using WixToolset.Extensibility.Services;
10
11 internal class GetPackageFacadesCommand
12 {
13 public GetPackageFacadesCommand(IMessaging messaging, IEnumerable<WixBundlePackageSymbol> chainPackageSymbols, IntermediateSection section)
14 {
15 this.Messaging = messaging;
16 this.ChainPackageSymbols = chainPackageSymbols;
17 this.Section = section;
18 }
19
20 private IEnumerable<WixBundlePackageSymbol> ChainPackageSymbols { get; }
21
22 private IMessaging Messaging { get; }
23
24 private IntermediateSection Section { get; }
25
26 public IDictionary<string, PackageFacade> PackageFacades { get; private set; }
27
28 public void Execute()
29 {
30 var wixGroupPackagesGroupedById = this.Section.Symbols.OfType<WixGroupSymbol>().Where(g => g.ParentType == ComplexReferenceParentType.Package).ToLookup(g => g.ParentId);
31 var exePackages = this.Section.Symbols.OfType<WixBundleExePackageSymbol>().ToDictionary(t => t.Id.Id);
32 var msiPackages = this.Section.Symbols.OfType<WixBundleMsiPackageSymbol>().ToDictionary(t => t.Id.Id);
33 var mspPackages = this.Section.Symbols.OfType<WixBundleMspPackageSymbol>().ToDictionary(t => t.Id.Id);
34 var msuPackages = this.Section.Symbols.OfType<WixBundleMsuPackageSymbol>().ToDictionary(t => t.Id.Id);
35 var exePackagePayloads = this.Section.Symbols.OfType<WixBundleExePackagePayloadSymbol>().ToDictionary(t => t.Id.Id);
36 var msiPackagePayloads = this.Section.Symbols.OfType<WixBundleMsiPackagePayloadSymbol>().ToDictionary(t => t.Id.Id);
37 var mspPackagePayloads = this.Section.Symbols.OfType<WixBundleMspPackagePayloadSymbol>().ToDictionary(t => t.Id.Id);
38 var msuPackagePayloads = this.Section.Symbols.OfType<WixBundleMsuPackagePayloadSymbol>().ToDictionary(t => t.Id.Id);
39
40 var facades = new Dictionary<string, PackageFacade>();
41
42 foreach (var package in this.ChainPackageSymbols)
43 {
44 var id = package.Id.Id;
45
46 IntermediateSymbol packagePayload = null;
47 foreach (var wixGroup in wixGroupPackagesGroupedById[id])
48 {
49 if (wixGroup.ChildType == ComplexReferenceChildType.PackagePayload)
50 {
51 IntermediateSymbol tempPackagePayload = null;
52 if (exePackagePayloads.TryGetValue(wixGroup.ChildId, out var exePackagePayload))
53 {
54 if (package.Type == WixBundlePackageType.Exe)
55 {
56 tempPackagePayload = exePackagePayload;
57 }
58 else
59 {
60 this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported(exePackagePayload.SourceLineNumbers, "Exe"));
61 this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported2(package.SourceLineNumbers));
62 }
63 }
64 else if (msiPackagePayloads.TryGetValue(wixGroup.ChildId, out var msiPackagePayload))
65 {
66 if (package.Type == WixBundlePackageType.Msi)
67 {
68 tempPackagePayload = msiPackagePayload;
69 }
70 else
71 {
72 this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported(msiPackagePayload.SourceLineNumbers, "Msi"));
73 this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported2(package.SourceLineNumbers));
74 }
75 }
76 else if (mspPackagePayloads.TryGetValue(wixGroup.ChildId, out var mspPackagePayload))
77 {
78 if (package.Type == WixBundlePackageType.Msp)
79 {
80 tempPackagePayload = mspPackagePayload;
81 }
82 else
83 {
84 this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported(mspPackagePayload.SourceLineNumbers, "Msp"));
85 this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported2(package.SourceLineNumbers));
86 }
87 }
88 else if (msuPackagePayloads.TryGetValue(wixGroup.ChildId, out var msuPackagePayload))
89 {
90 if (package.Type == WixBundlePackageType.Msu)
91 {
92 tempPackagePayload = msuPackagePayload;
93 }
94 else
95 {
96 this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported(msuPackagePayload.SourceLineNumbers, "Msu"));
97 this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported2(package.SourceLineNumbers));
98 }
99 }
100 else
101 {
102 this.Messaging.Write(ErrorMessages.IdentifierNotFound(package.Type + "PackagePayload", wixGroup.ChildId));
103 }
104
105 if (tempPackagePayload != null)
106 {
107 if (packagePayload == null)
108 {
109 packagePayload = tempPackagePayload;
110 }
111 else
112 {
113 this.Messaging.Write(ErrorMessages.MultiplePackagePayloads(tempPackagePayload.SourceLineNumbers, id, packagePayload.Id.Id, tempPackagePayload.Id.Id));
114 this.Messaging.Write(ErrorMessages.MultiplePackagePayloads2(packagePayload.SourceLineNumbers));
115 this.Messaging.Write(ErrorMessages.MultiplePackagePayloads3(package.SourceLineNumbers));
116 }
117 }
118 }
119 }
120
121 if (packagePayload == null)
122 {
123 this.Messaging.Write(ErrorMessages.MissingPackagePayload(package.SourceLineNumbers, id, package.Type.ToString()));
124 }
125 else
126 {
127 package.PayloadRef = packagePayload.Id.Id;
128 }
129
130 switch (package.Type)
131 {
132 case WixBundlePackageType.Exe:
133 if (exePackages.TryGetValue(id, out var exePackage))
134 {
135 facades.Add(id, new PackageFacade(package, exePackage));
136 }
137 else
138 {
139 this.Messaging.Write(ErrorMessages.IdentifierNotFound("WixBundleExePackage", id));
140 }
141 break;
142
143 case WixBundlePackageType.Msi:
144 if (msiPackages.TryGetValue(id, out var msiPackage))
145 {
146 facades.Add(id, new PackageFacade(package, msiPackage));
147 }
148 else
149 {
150 this.Messaging.Write(ErrorMessages.IdentifierNotFound("WixBundleMsiPackage", id));
151 }
152 break;
153
154 case WixBundlePackageType.Msp:
155 if (mspPackages.TryGetValue(id, out var mspPackage))
156 {
157 facades.Add(id, new PackageFacade(package, mspPackage));
158 }
159 else
160 {
161 this.Messaging.Write(ErrorMessages.IdentifierNotFound("WixBundleMspPackage", id));
162 }
163 break;
164
165 case WixBundlePackageType.Msu:
166 if (msuPackages.TryGetValue(id, out var msuPackage))
167 {
168 facades.Add(id, new PackageFacade(package, msuPackage));
169 }
170 else
171 {
172 this.Messaging.Write(ErrorMessages.IdentifierNotFound("WixBundleMsuPackage", id));
173 }
174 break;
175 }
176 }
177
178 this.PackageFacades = facades;
179 }
180 }
181}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn.Bundles
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using WixToolset.Data;
9 using WixToolset.Data.Burn;
10 using WixToolset.Data.Symbols;
11 using WixToolset.Extensibility.Services;
12
13 internal class OrderPackagesAndRollbackBoundariesCommand
14 {
15 public OrderPackagesAndRollbackBoundariesCommand(IMessaging messaging, IntermediateSection section, IDictionary<string, PackageFacade> packageFacades)
16 {
17 this.Messaging = messaging;
18 this.Section = section;
19 this.PackageFacades = packageFacades;
20 }
21
22 private IMessaging Messaging { get; }
23
24 private IntermediateSection Section { get; }
25
26 private IDictionary<string, PackageFacade> PackageFacades { get; }
27
28 public IEnumerable<PackageFacade> OrderedPackageFacades { get; private set; }
29
30 public IEnumerable<WixBundleRollbackBoundarySymbol> UsedRollbackBoundaries { get; private set; }
31
32 public void Execute()
33 {
34 var groupSymbols = this.Section.Symbols.OfType<WixGroupSymbol>().ToList();
35 var boundariesById = this.Section.Symbols.OfType<WixBundleRollbackBoundarySymbol>().ToDictionary(b => b.Id.Id);
36
37 var orderedFacades = new List<PackageFacade>();
38 var usedBoundaries = new List<WixBundleRollbackBoundarySymbol>();
39
40 // Process the chain of packages to add them in the correct order
41 // and assign the forward rollback boundaries as appropriate. Remember
42 // rollback boundaries are authored as elements in the chain which
43 // we re-interpret here to add them as attributes on the next available
44 // package in the chain. Essentially we mark some packages as being
45 // the start of a rollback boundary when installing and repairing.
46 // We handle uninstall (aka: backwards) rollback boundaries after
47 // we get these install/repair (aka: forward) rollback boundaries
48 // defined.
49 var pendingRollbackBoundary = new WixBundleRollbackBoundarySymbol(null, new Identifier(AccessModifier.Section, BurnConstants.BundleDefaultBoundaryId)) { Vital = true };
50 var lastRollbackBoundary = pendingRollbackBoundary;
51 var boundaryHadX86Package = false;
52 var warnedMsiTransaction = false;
53
54 foreach (var groupSymbol in groupSymbols)
55 {
56 if (ComplexReferenceChildType.Package == groupSymbol.ChildType && ComplexReferenceParentType.PackageGroup == groupSymbol.ParentType && BurnConstants.BundleChainPackageGroupId == groupSymbol.ParentId)
57 {
58 if (this.PackageFacades.TryGetValue(groupSymbol.ChildId, out var facade))
59 {
60 var insideMsiTransaction = lastRollbackBoundary?.Transaction ?? false;
61
62 if (null != pendingRollbackBoundary)
63 {
64 // If we used the default boundary, ensure the symbol is added to the section.
65 if (pendingRollbackBoundary.Id.Id == BurnConstants.BundleDefaultBoundaryId)
66 {
67 this.Section.AddSymbol(pendingRollbackBoundary);
68 }
69
70 if (insideMsiTransaction && !warnedMsiTransaction)
71 {
72 warnedMsiTransaction = true;
73 this.Messaging.Write(WarningMessages.MsiTransactionLimitations(pendingRollbackBoundary.SourceLineNumbers));
74 }
75
76 usedBoundaries.Add(pendingRollbackBoundary);
77 facade.PackageSymbol.RollbackBoundaryRef = pendingRollbackBoundary.Id.Id;
78 pendingRollbackBoundary = null;
79
80 boundaryHadX86Package = !facade.PackageSymbol.Win64;
81 }
82
83 // Error if MSI transaction has x86 package preceding x64 packages
84 if (insideMsiTransaction && boundaryHadX86Package && facade.PackageSymbol.Win64)
85 {
86 this.Messaging.Write(ErrorMessages.MsiTransactionX86BeforeX64(facade.PackageSymbol.SourceLineNumbers));
87 }
88
89 boundaryHadX86Package |= !facade.PackageSymbol.Win64;
90
91 orderedFacades.Add(facade);
92 }
93 else // must be a rollback boundary.
94 {
95 // Discard the next rollback boundary if we have a previously defined boundary.
96 var nextRollbackBoundary = boundariesById[groupSymbol.ChildId];
97 if (null != pendingRollbackBoundary)
98 {
99 if (pendingRollbackBoundary.Id.Id != BurnConstants.BundleDefaultBoundaryId)
100 {
101 this.Messaging.Write(WarningMessages.DiscardedRollbackBoundary(nextRollbackBoundary.SourceLineNumbers, nextRollbackBoundary.Id.Id));
102 }
103 }
104
105 lastRollbackBoundary = pendingRollbackBoundary = nextRollbackBoundary;
106 }
107 }
108 }
109
110 if (null != pendingRollbackBoundary)
111 {
112 this.Messaging.Write(WarningMessages.DiscardedRollbackBoundary(pendingRollbackBoundary.SourceLineNumbers, pendingRollbackBoundary.Id.Id));
113 }
114
115 // With the forward rollback boundaries assigned, we can now go
116 // through the packages with rollback boundaries and assign backward
117 // rollback boundaries. Backward rollback boundaries are used when
118 // the chain is going "backwards" which (AFAIK) only happens during
119 // uninstall.
120 //
121 // Consider the scenario with three packages: A, B and C. Packages A
122 // and C are marked as rollback boundary packages and package B is
123 // not. The naive implementation would execute the chain like this
124 // (numbers indicate where rollback boundaries would end up):
125 // install: 1 A B 2 C
126 // uninstall: 2 C B 1 A
127 //
128 // The uninstall chain is wrong, A and B should be grouped together
129 // not C and B. The fix is to label packages with a "backwards"
130 // rollback boundary used during uninstall. The backwards rollback
131 // boundaries are assigned to the package *before* the next rollback
132 // boundary. Using our example from above again, I'll mark the
133 // backwards rollback boundaries prime (aka: with ').
134 // install: 1 A B 1' 2 C 2'
135 // uninstall: 2' C 2 1' B A 1
136 //
137 // If the marked boundaries are ignored during install you get the
138 // same thing as above (good) and if the non-marked boundaries are
139 // ignored during uninstall then A and B are correctly grouped.
140 // Here's what it looks like without all the markers:
141 // install: 1 A B 2 C
142 // uninstall: 2 C 1 B A
143 // Woot!
144 string previousRollbackBoundaryId = null;
145 PackageFacade previousFacade = null;
146
147 foreach (var package in orderedFacades)
148 {
149 if (null != package.PackageSymbol.RollbackBoundaryRef)
150 {
151 if (null != previousFacade)
152 {
153 previousFacade.PackageSymbol.RollbackBoundaryBackwardRef = previousRollbackBoundaryId;
154 }
155
156 previousRollbackBoundaryId = package.PackageSymbol.RollbackBoundaryRef;
157 }
158
159 previousFacade = package;
160 }
161
162 if (!String.IsNullOrEmpty(previousRollbackBoundaryId) && null != previousFacade)
163 {
164 previousFacade.PackageSymbol.RollbackBoundaryBackwardRef = previousRollbackBoundaryId;
165 }
166
167 this.OrderedPackageFacades = orderedFacades;
168 this.UsedRollbackBoundaries = usedBoundaries;
169 }
170 }
171}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn.Bundles
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.Linq;
9 using WixToolset.Data;
10 using WixToolset.Data.Burn;
11 using WixToolset.Data.Symbols;
12 using WixToolset.Extensibility.Services;
13
14 internal class OrderSearchesCommand
15 {
16 public OrderSearchesCommand(IMessaging messaging, IntermediateSection section)
17 {
18 this.Messaging = messaging;
19 this.Section = section;
20 }
21
22 private IMessaging Messaging { get; }
23
24 private IntermediateSection Section { get; }
25
26 public IDictionary<string, IEnumerable<IntermediateSymbol>> ExtensionSearchSymbolsByExtensionId { get; private set; }
27
28 public IEnumerable<ISearchFacade> OrderedSearchFacades { get; private set; }
29
30 public void Execute()
31 {
32 this.ExtensionSearchSymbolsByExtensionId = new Dictionary<string, IEnumerable<IntermediateSymbol>>();
33 this.OrderedSearchFacades = Array.Empty<ISearchFacade>();
34
35 var searchSymbols = this.Section.Symbols.OfType<WixSearchSymbol>().ToDictionary(t => t.Id.Id);
36 if (searchSymbols.Count == 0)
37 {
38 // Nothing to do!
39 return;
40 }
41
42 var constraints = new Constraints();
43
44 // Add relational info to our data...
45 foreach (var searchRelationSymbol in this.Section.Symbols.OfType<WixSearchRelationSymbol>())
46 {
47 constraints.AddConstraint(searchRelationSymbol.Id.Id, searchRelationSymbol.ParentSearchRef);
48 }
49
50 this.FindCircularReference(constraints);
51
52 if (this.Messaging.EncounteredError)
53 {
54 return;
55 }
56
57 this.FlattenDependentReferences(constraints);
58
59 // Reorder by topographical sort (http://en.wikipedia.org/wiki/Topological_sorting)
60 // We use a variation of Kahn (1962) algorithm as described in
61 // Wikipedia, with the additional criteria that start nodes are sorted
62 // lexicographically at each step to ensure a deterministic ordering
63 // based on 'after' dependencies and ID.
64 var sorter = new TopologicalSort();
65 var sortedIds = sorter.Sort(searchSymbols.Keys, constraints);
66
67 // Now, create the search facades with the searches in order...
68 (var orderedSearchFacades, var extensionSearchSymbolsByExtensionId) = this.OrderSearches(sortedIds, searchSymbols);
69
70 this.OrderedSearchFacades = orderedSearchFacades;
71 this.ExtensionSearchSymbolsByExtensionId = extensionSearchSymbolsByExtensionId;
72 }
73
74 /// <summary>
75 /// A dictionary of constraints, mapping an id to a list of ids.
76 /// </summary>
77 private class Constraints : Dictionary<string, List<string>>
78 {
79 public void AddConstraint(string id, string afterId)
80 {
81 if (!this.ContainsKey(id))
82 {
83 this.Add(id, new List<string>());
84 }
85
86 // TODO: Show warning if a constraint is seen twice?
87 if (!this[id].Contains(afterId))
88 {
89 this[id].Add(afterId);
90 }
91 }
92
93 // TODO: Hide other Add methods?
94 }
95
96 /// <summary>
97 /// Finds circular references in the constraints.
98 /// </summary>
99 /// <param name="constraints">Constraints to check.</param>
100 /// <remarks>This is not particularly performant, but it works.</remarks>
101 private void FindCircularReference(Constraints constraints)
102 {
103 foreach (var id in constraints.Keys)
104 {
105 var seenIds = new List<string>();
106
107 if (this.FindCircularReference(constraints, id, id, seenIds, out var chain))
108 {
109 // We will show a separate message for every ID that's in
110 // the loop. We could bail after the first one, but then
111 // we wouldn't catch disjoint loops in a single run.
112 this.Messaging.Write(ErrorMessages.CircularSearchReference(chain));
113 }
114 }
115 }
116
117 /// <summary>
118 /// Recursive function that finds circular references in the constraints.
119 /// </summary>
120 /// <param name="constraints">Constraints to check.</param>
121 /// <param name="checkId">The identifier currently being looking for. (Fixed across a given run.)</param>
122 /// <param name="currentId">The idenifier curently being tested.</param>
123 /// <param name="seenIds">A list of identifiers seen, to ensure each identifier is only expanded once.</param>
124 /// <param name="chain">If a circular reference is found, will contain the chain of references.</param>
125 /// <returns>True if a circular reference is found, false otherwise.</returns>
126 private bool FindCircularReference(Constraints constraints, string checkId, string currentId, List<string> seenIds, out string chain)
127 {
128 chain = null;
129 if (constraints.TryGetValue(currentId, out var afterList))
130 {
131 foreach (string afterId in afterList)
132 {
133 if (afterId == checkId)
134 {
135 chain = String.Format(CultureInfo.InvariantCulture, "{0} -> {1}", currentId, afterId);
136 return true;
137 }
138
139 if (!seenIds.Contains(afterId))
140 {
141 seenIds.Add(afterId);
142 if (this.FindCircularReference(constraints, checkId, afterId, seenIds, out chain))
143 {
144 chain = String.Format(CultureInfo.InvariantCulture, "{0} -> {1}", currentId, chain);
145 return true;
146 }
147 }
148 }
149 }
150
151 return false;
152 }
153
154 /// <summary>
155 /// Flattens any dependency chains to simplify reordering.
156 /// </summary>
157 /// <param name="constraints"></param>
158 private void FlattenDependentReferences(Constraints constraints)
159 {
160 foreach (string id in constraints.Keys)
161 {
162 var flattenedIds = new List<string>();
163 this.AddDependentReferences(constraints, id, flattenedIds);
164 var constraintList = constraints[id];
165 foreach (var flattenedId in flattenedIds)
166 {
167 if (!constraintList.Contains(flattenedId))
168 {
169 constraintList.Add(flattenedId);
170 }
171 }
172 }
173 }
174
175 /// <summary>
176 /// Adds dependent references to a list.
177 /// </summary>
178 /// <param name="constraints"></param>
179 /// <param name="currentId"></param>
180 /// <param name="seenIds"></param>
181 private void AddDependentReferences(Constraints constraints, string currentId, List<string> seenIds)
182 {
183 if (constraints.TryGetValue(currentId, out var afterList))
184 {
185 foreach (var afterId in afterList)
186 {
187 if (!seenIds.Contains(afterId))
188 {
189 seenIds.Add(afterId);
190 this.AddDependentReferences(constraints, afterId, seenIds);
191 }
192 }
193 }
194 }
195
196 /// <summary>
197 /// Reorder by topological sort
198 /// </summary>
199 /// <remarks>
200 /// We use a variation of Kahn (1962) algorithm as described in
201 /// Wikipedia (http://en.wikipedia.org/wiki/Topological_sorting), with
202 /// the additional criteria that start nodes are sorted lexicographically
203 /// at each step to ensure a deterministic ordering based on 'after'
204 /// dependencies and ID.
205 /// </remarks>
206 private class TopologicalSort
207 {
208 private readonly List<string> startIds = new List<string>();
209 private Constraints constraints;
210
211 /// <summary>
212 /// Reorder by topological sort
213 /// </summary>
214 /// <param name="allIds">The complete list of IDs.</param>
215 /// <param name="constraints">Constraints to use.</param>
216 /// <returns>The topologically sorted list of IDs.</returns>
217 internal List<string> Sort(IEnumerable<string> allIds, Constraints constraints)
218 {
219 this.startIds.Clear();
220 this.CopyConstraints(constraints);
221
222 this.FindInitialStartIds(allIds);
223
224 // We always create a new sortedId list, because we return it
225 // to the caller and don't know what its lifetime may be.
226 var sortedIds = new List<string>();
227
228 while (this.startIds.Count > 0)
229 {
230 this.SortStartIds();
231
232 var currentId = this.startIds[0];
233 sortedIds.Add(currentId);
234 this.startIds.RemoveAt(0);
235
236 this.ResolveConstraint(currentId);
237 }
238
239 return sortedIds;
240 }
241
242 /// <summary>
243 /// Copies a Constraints set (to prevent modifying the incoming data).
244 /// </summary>
245 /// <param name="constraints">Constraints to copy.</param>
246 private void CopyConstraints(Constraints constraints)
247 {
248 this.constraints = new Constraints();
249 foreach (var id in constraints.Keys)
250 {
251 foreach (var afterId in constraints[id])
252 {
253 this.constraints.AddConstraint(id, afterId);
254 }
255 }
256 }
257
258 /// <summary>
259 /// Finds initial start IDs. (Those with no constraints.)
260 /// </summary>
261 /// <param name="allIds">The complete list of IDs.</param>
262 private void FindInitialStartIds(IEnumerable<string> allIds)
263 {
264 foreach (var id in allIds)
265 {
266 if (!this.constraints.ContainsKey(id))
267 {
268 this.startIds.Add(id);
269 }
270 }
271 }
272
273 /// <summary>
274 /// Sorts start IDs.
275 /// </summary>
276 private void SortStartIds()
277 {
278 this.startIds.Sort();
279 }
280
281 /// <summary>
282 /// Removes the resolved constraint and updates the list of startIds
283 /// with any now-valid (all constraints resolved) IDs.
284 /// </summary>
285 /// <param name="resolvedId">The ID to resolve from the set of constraints.</param>
286 private void ResolveConstraint(string resolvedId)
287 {
288 var newStartIds = new List<string>();
289
290 foreach (var id in this.constraints.Keys)
291 {
292 if (this.constraints[id].Contains(resolvedId))
293 {
294 this.constraints[id].Remove(resolvedId);
295
296 // If we just removed the last constraint for this
297 // ID, it is now a valid start ID.
298 if (this.constraints[id].Count == 0)
299 {
300 newStartIds.Add(id);
301 }
302 }
303 }
304
305 foreach (var id in newStartIds)
306 {
307 this.constraints.Remove(id);
308 }
309
310 this.startIds.AddRange(newStartIds);
311 }
312 }
313
314 private (IEnumerable<ISearchFacade>, Dictionary<string, IEnumerable<IntermediateSymbol>>) OrderSearches(IEnumerable<string> sortedIds, Dictionary<string, WixSearchSymbol> searchSymbolDictionary)
315 {
316 var orderedSearchFacades = new List<ISearchFacade>();
317 var extensionSearchSymbolsByExtensionId = new Dictionary<string, List<IntermediateSymbol>>();
318
319 // TODO: Although the WixSearch tables are defined in the Util extension,
320 // the Bundle Binder has to know all about them. We hope to revisit all
321 // of this in the 4.0 timeframe.
322 var legacySearchesById = this.Section.Symbols
323 .Where(t => t.Definition.Type == SymbolDefinitionType.WixComponentSearch ||
324 t.Definition.Type == SymbolDefinitionType.WixFileSearch ||
325 t.Definition.Type == SymbolDefinitionType.WixProductSearch ||
326 t.Definition.Type == SymbolDefinitionType.WixRegistrySearch)
327 .ToDictionary(t => t.Id.Id);
328 var setVariablesById = this.Section.Symbols
329 .OfType<WixSetVariableSymbol>()
330 .ToDictionary(t => t.Id.Id);
331 var extensionSearchesById = this.Section.Symbols
332 .Where(t => t.Definition.HasTag(BurnConstants.BundleExtensionSearchSymbolDefinitionTag))
333 .ToDictionary(t => t.Id.Id);
334
335 foreach (var searchId in sortedIds)
336 {
337 var searchSymbol = searchSymbolDictionary[searchId];
338
339 if (legacySearchesById.TryGetValue(searchId, out var specificSearchSymbol))
340 {
341 orderedSearchFacades.Add(new LegacySearchFacade(searchSymbol, specificSearchSymbol));
342 }
343 else if (setVariablesById.TryGetValue(searchId, out var setVariableSymbol))
344 {
345 orderedSearchFacades.Add(new SetVariableSearchFacade(searchSymbol, setVariableSymbol));
346 }
347 else if (extensionSearchesById.TryGetValue(searchId, out var extensionSearchSymbol))
348 {
349 orderedSearchFacades.Add(new ExtensionSearchFacade(searchSymbol));
350
351 if (!extensionSearchSymbolsByExtensionId.TryGetValue(searchSymbol.BundleExtensionRef, out var extensionSearchSymbols))
352 {
353 extensionSearchSymbols = new List<IntermediateSymbol>();
354 extensionSearchSymbolsByExtensionId[searchSymbol.BundleExtensionRef] = extensionSearchSymbols;
355 }
356 extensionSearchSymbols.Add(extensionSearchSymbol);
357 }
358 else
359 {
360 this.Messaging.Write(ErrorMessages.MissingBundleSearch(searchSymbol.SourceLineNumbers, searchId));
361 }
362 }
363
364 return (orderedSearchFacades, extensionSearchSymbolsByExtensionId.ToDictionary(kvp => kvp.Key, kvp => (IEnumerable<IntermediateSymbol>)kvp.Value));
365 }
366 }
367}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn.Bundles
4{
5 using System.Diagnostics;
6 using WixToolset.Data;
7 using WixToolset.Data.Symbols;
8
9 internal class PackageFacade
10 {
11 public PackageFacade(WixBundlePackageSymbol packageSymbol, IntermediateSymbol specificPackageSymbol)
12 {
13 Debug.Assert(packageSymbol.Id.Id == specificPackageSymbol.Id.Id);
14
15 this.PackageSymbol = packageSymbol;
16 this.SpecificPackageSymbol = specificPackageSymbol;
17 }
18
19 public string PackageId => this.PackageSymbol.Id.Id;
20
21 public WixBundlePackageSymbol PackageSymbol { get; }
22
23 public IntermediateSymbol SpecificPackageSymbol { get; }
24 }
25}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn.Bundles
4{
5 using System;
6 using System.Collections.Generic;
7 using WixToolset.Data.Symbols;
8
9 /// <summary>
10 /// Initializes package state from the Exe contents.
11 /// </summary>
12 internal class ProcessExePackageCommand
13 {
14 public ProcessExePackageCommand(PackageFacade facade, Dictionary<string, WixBundlePayloadSymbol> payloadSymbols)
15 {
16 this.AuthoredPayloads = payloadSymbols;
17 this.Facade = facade;
18 }
19
20 public Dictionary<string, WixBundlePayloadSymbol> AuthoredPayloads { get; }
21
22 public PackageFacade Facade { get; }
23
24 /// <summary>
25 /// Processes the Exe packages to add properties and payloads from the Exe packages.
26 /// </summary>
27 public void Execute()
28 {
29 var packagePayload = this.AuthoredPayloads[this.Facade.PackageSymbol.PayloadRef];
30
31 if (String.IsNullOrEmpty(this.Facade.PackageSymbol.CacheId))
32 {
33 this.Facade.PackageSymbol.CacheId = packagePayload.Hash;
34 }
35
36 this.Facade.PackageSymbol.Version = packagePayload.Version;
37 }
38 }
39}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn.Bundles
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.IO;
9 using System.Linq;
10 using WixToolset.Data;
11 using WixToolset.Extensibility;
12 using WixToolset.Extensibility.Services;
13 using WixToolset.Data.Symbols;
14 using WixToolset.Data.WindowsInstaller;
15 using WixToolset.Extensibility.Data;
16 using WixToolset.Core.Native.Msi;
17
18 /// <summary>
19 /// Initializes package state from the MSI contents.
20 /// </summary>
21 internal class ProcessMsiPackageCommand
22 {
23 private const string PropertySqlQuery = "SELECT `Value` FROM `Property` WHERE `Property` = ?";
24
25 public ProcessMsiPackageCommand(IServiceProvider serviceProvider, IEnumerable<IBurnBackendBinderExtension> backendExtensions, IntermediateSection section, PackageFacade facade, Dictionary<string, WixBundlePayloadSymbol> packagePayloads)
26 {
27 this.Messaging = serviceProvider.GetService<IMessaging>();
28 this.BackendHelper = serviceProvider.GetService<IBackendHelper>();
29 this.PathResolver = serviceProvider.GetService<IPathResolver>();
30
31 this.BackendExtensions = backendExtensions;
32
33 this.PackagePayloads = packagePayloads;
34 this.Section = section;
35 this.Facade = facade;
36 }
37
38 private IMessaging Messaging { get; }
39
40 private IBackendHelper BackendHelper { get; }
41
42 private IPathResolver PathResolver { get; }
43
44 private IEnumerable<IBurnBackendBinderExtension> BackendExtensions { get; }
45
46 private Dictionary<string, WixBundlePayloadSymbol> PackagePayloads { get; }
47
48 private PackageFacade Facade { get; }
49
50 private IntermediateSection Section { get; }
51
52 /// <summary>
53 /// Processes the MSI packages to add properties and payloads from the MSI packages.
54 /// </summary>
55 public void Execute()
56 {
57 var packagePayload = this.PackagePayloads[this.Facade.PackageSymbol.PayloadRef];
58
59 var msiPackage = (WixBundleMsiPackageSymbol)this.Facade.SpecificPackageSymbol;
60
61 var sourcePath = packagePayload.SourceFile.Path;
62 var longNamesInImage = false;
63 var compressed = false;
64 try
65 {
66 using (var db = new Database(sourcePath, OpenDatabase.ReadOnly))
67 {
68 // Read data out of the msi database...
69 using (var sumInfo = new SummaryInformation(db))
70 {
71 var fileAndElevateFlags = sumInfo.GetNumericProperty(SummaryInformation.Package.FileAndElevatedFlags);
72 var platformsAndLanguages = sumInfo.GetProperty(SummaryInformation.Package.PlatformsAndLanguages);
73
74 // 1 is the Word Count summary information stream bit that means
75 // the MSI uses short file names when set. We care about long file
76 // names so check when the bit is not set.
77
78 longNamesInImage = 0 == (fileAndElevateFlags & 1);
79
80 // 2 is the Word Count summary information stream bit that means
81 // files are compressed in the MSI by default when the bit is set.
82 compressed = 2 == (fileAndElevateFlags & 2);
83
84 // 8 is the Word Count summary information stream bit that means
85 // "Elevated privileges are not required to install this package."
86 // in MSI 4.5 and below, if this bit is 0, elevation is required.
87 var perMachine = (0 == (fileAndElevateFlags & 8));
88 var x64 = platformsAndLanguages.Contains("x64");
89
90 this.Facade.PackageSymbol.PerMachine = perMachine ? YesNoDefaultType.Yes : YesNoDefaultType.No;
91 this.Facade.PackageSymbol.Win64 = x64;
92 }
93
94 string packageName = null;
95 string packageDescription = null;
96 string allusers = null;
97 string fastInstall = null;
98 string systemComponent = null;
99
100 using (var view = db.OpenView(PropertySqlQuery))
101 {
102 packageName = ProcessMsiPackageCommand.GetProperty(view, "ProductName");
103 packageDescription = ProcessMsiPackageCommand.GetProperty(view, "ARPCOMMENTS");
104 allusers = ProcessMsiPackageCommand.GetProperty(view, "ALLUSERS");
105 fastInstall = ProcessMsiPackageCommand.GetProperty(view, "MSIFASTINSTALL");
106 systemComponent = ProcessMsiPackageCommand.GetProperty(view, "ARPSYSTEMCOMPONENT");
107
108 msiPackage.ProductCode = ProcessMsiPackageCommand.GetProperty(view, "ProductCode");
109 msiPackage.UpgradeCode = ProcessMsiPackageCommand.GetProperty(view, "UpgradeCode");
110 msiPackage.Manufacturer = ProcessMsiPackageCommand.GetProperty(view, "Manufacturer");
111 msiPackage.ProductLanguage = Convert.ToInt32(ProcessMsiPackageCommand.GetProperty(view, "ProductLanguage"), CultureInfo.InvariantCulture);
112 msiPackage.ProductVersion = ProcessMsiPackageCommand.GetProperty(view, "ProductVersion");
113 }
114
115 if (!this.BackendHelper.IsValidFourPartVersion(msiPackage.ProductVersion))
116 {
117 // not a proper .NET version (e.g., five fields); can we get a valid four-part version number?
118 string version = null;
119 var versionParts = msiPackage.ProductVersion.Split('.');
120 var count = versionParts.Length;
121 if (0 < count)
122 {
123 version = versionParts[0];
124 for (var i = 1; i < 4 && i < count; ++i)
125 {
126 version = String.Concat(version, ".", versionParts[i]);
127 }
128 }
129
130 if (!String.IsNullOrEmpty(version) && this.BackendHelper.IsValidFourPartVersion(version))
131 {
132 this.Messaging.Write(WarningMessages.VersionTruncated(this.Facade.PackageSymbol.SourceLineNumbers, msiPackage.ProductVersion, sourcePath, version));
133 msiPackage.ProductVersion = version;
134 }
135 else
136 {
137 this.Messaging.Write(ErrorMessages.InvalidProductVersion(this.Facade.PackageSymbol.SourceLineNumbers, msiPackage.ProductVersion, sourcePath));
138 }
139 }
140
141 if (String.IsNullOrEmpty(this.Facade.PackageSymbol.CacheId))
142 {
143 this.Facade.PackageSymbol.CacheId = String.Format("{0}v{1}", msiPackage.ProductCode, msiPackage.ProductVersion);
144 }
145
146 if (String.IsNullOrEmpty(this.Facade.PackageSymbol.DisplayName))
147 {
148 this.Facade.PackageSymbol.DisplayName = packageName;
149 }
150
151 if (String.IsNullOrEmpty(this.Facade.PackageSymbol.Description))
152 {
153 this.Facade.PackageSymbol.Description = packageDescription;
154 }
155
156 if (String.IsNullOrEmpty(this.Facade.PackageSymbol.Version))
157 {
158 this.Facade.PackageSymbol.Version = msiPackage.ProductVersion;
159 }
160
161 var payloadNames = this.GetPayloadTargetNames();
162
163 var msiPropertyNames = this.GetMsiPropertyNames(packagePayload.Id.Id);
164
165 this.SetPerMachineAppropriately(allusers, msiPackage, sourcePath);
166
167 // Ensure the MSI package is appropriately marked visible or not.
168 this.SetPackageVisibility(systemComponent, msiPackage, msiPropertyNames);
169
170 // Unless the MSI or setup code overrides the default, set MSIFASTINSTALL for best performance.
171 if (!String.IsNullOrEmpty(fastInstall))
172 {
173 this.AddMsiProperty(msiPackage, "MSIFASTINSTALL", "7");
174 }
175
176 this.CreateRelatedPackages(db);
177
178 // If feature selection is enabled, represent the Feature table in the manifest.
179 if ((msiPackage.Attributes & WixBundleMsiPackageAttributes.EnableFeatureSelection) == WixBundleMsiPackageAttributes.EnableFeatureSelection)
180 {
181 this.CreateMsiFeatures(db);
182 }
183
184 // Add all external cabinets as package payloads.
185 this.ImportExternalCabinetAsPayloads(db, packagePayload, payloadNames);
186
187 // Add all external files as package payloads and calculate the total install size as the rollup of
188 // File table's sizes.
189 this.Facade.PackageSymbol.InstallSize = this.ImportExternalFileAsPayloadsAndReturnInstallSize(db, packagePayload, longNamesInImage, compressed, payloadNames);
190
191 // Add all dependency providers from the MSI.
192 this.ImportDependencyProviders(db, msiPackage);
193 }
194 }
195 catch (MsiException e)
196 {
197 this.Messaging.Write(ErrorMessages.UnableToReadPackageInformation(this.Facade.PackageSymbol.SourceLineNumbers, sourcePath, e.Message));
198 }
199 }
200
201 private ISet<string> GetPayloadTargetNames()
202 {
203 var payloadNames = this.PackagePayloads.Values.Select(p => p.Name);
204
205 return new HashSet<string>(payloadNames, StringComparer.OrdinalIgnoreCase);
206 }
207
208 private ISet<string> GetMsiPropertyNames(string packageId)
209 {
210 var properties = this.Section.Symbols.OfType<WixBundleMsiPropertySymbol>()
211 .Where(p => p.PackageRef == packageId)
212 .Select(p => p.Name);
213
214 return new HashSet<string>(properties, StringComparer.Ordinal);
215 }
216
217 private void SetPerMachineAppropriately(string allusers, WixBundleMsiPackageSymbol msiPackage, string sourcePath)
218 {
219 if (msiPackage.ForcePerMachine)
220 {
221 if (YesNoDefaultType.No == this.Facade.PackageSymbol.PerMachine)
222 {
223 this.Messaging.Write(WarningMessages.PerUserButForcingPerMachine(this.Facade.PackageSymbol.SourceLineNumbers, sourcePath));
224 this.Facade.PackageSymbol.PerMachine = YesNoDefaultType.Yes; // ensure that we think the package is per-machine.
225 }
226
227 // Force ALLUSERS=1 via the MSI command-line.
228 this.AddMsiProperty(msiPackage, "ALLUSERS", "1");
229 }
230 else
231 {
232 if (String.IsNullOrEmpty(allusers))
233 {
234 // Not forced per-machine and no ALLUSERS property, flip back to per-user.
235 if (YesNoDefaultType.Yes == this.Facade.PackageSymbol.PerMachine)
236 {
237 this.Messaging.Write(WarningMessages.ImplicitlyPerUser(this.Facade.PackageSymbol.SourceLineNumbers, sourcePath));
238 this.Facade.PackageSymbol.PerMachine = YesNoDefaultType.No;
239 }
240 }
241 else if (allusers.Equals("1", StringComparison.Ordinal))
242 {
243 if (YesNoDefaultType.No == this.Facade.PackageSymbol.PerMachine)
244 {
245 this.Messaging.Write(ErrorMessages.PerUserButAllUsersEquals1(this.Facade.PackageSymbol.SourceLineNumbers, sourcePath));
246 }
247 }
248 else if (allusers.Equals("2", StringComparison.Ordinal))
249 {
250 this.Messaging.Write(WarningMessages.DiscouragedAllUsersValue(this.Facade.PackageSymbol.SourceLineNumbers, sourcePath, (YesNoDefaultType.Yes == this.Facade.PackageSymbol.PerMachine) ? "machine" : "user"));
251 }
252 else
253 {
254 this.Messaging.Write(ErrorMessages.UnsupportedAllUsersValue(this.Facade.PackageSymbol.SourceLineNumbers, sourcePath, allusers));
255 }
256 }
257 }
258
259 private void SetPackageVisibility(string systemComponent, WixBundleMsiPackageSymbol msiPackage, ISet<string> msiPropertyNames)
260 {
261 // If the authoring specifically added "ARPSYSTEMCOMPONENT", don't do it again.
262 if (!msiPropertyNames.Contains("ARPSYSTEMCOMPONENT"))
263 {
264 var alreadyVisible = String.IsNullOrEmpty(systemComponent);
265 var visible = (this.Facade.PackageSymbol.Attributes & WixBundlePackageAttributes.Visible) == WixBundlePackageAttributes.Visible;
266
267 // If not already set to the correct visibility.
268 if (alreadyVisible != visible)
269 {
270 this.AddMsiProperty(msiPackage, "ARPSYSTEMCOMPONENT", visible ? String.Empty : "1");
271 }
272 }
273 }
274
275 private void CreateRelatedPackages(Database db)
276 {
277 // Represent the Upgrade table as related packages.
278 if (db.TableExists("Upgrade"))
279 {
280 using (var view = db.OpenExecuteView("SELECT `UpgradeCode`, `VersionMin`, `VersionMax`, `Language`, `Attributes` FROM `Upgrade`"))
281 {
282 foreach (var record in view.Records)
283 {
284 var recordAttributes = record.GetInteger(5);
285
286 var attributes = WixBundleRelatedPackageAttributes.None;
287 attributes |= (recordAttributes & WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect) == WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect ? WixBundleRelatedPackageAttributes.OnlyDetect : 0;
288 attributes |= (recordAttributes & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive) == WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive ? WixBundleRelatedPackageAttributes.MinInclusive : 0;
289 attributes |= (recordAttributes & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive) == WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive ? WixBundleRelatedPackageAttributes.MaxInclusive : 0;
290 attributes |= (recordAttributes & WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive) == WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive ? WixBundleRelatedPackageAttributes.LangInclusive : 0;
291
292 this.Section.AddSymbol(new WixBundleRelatedPackageSymbol(this.Facade.PackageSymbol.SourceLineNumbers)
293 {
294 PackageRef = this.Facade.PackageId,
295 RelatedId = record.GetString(1),
296 MinVersion = record.GetString(2),
297 MaxVersion = record.GetString(3),
298 Languages = record.GetString(4),
299 Attributes = attributes,
300 });
301 }
302 }
303 }
304 }
305
306 private void CreateMsiFeatures(Database db)
307 {
308 if (db.TableExists("Feature"))
309 {
310 using (var allFeaturesView = db.OpenExecuteView("SELECT * FROM `Feature`"))
311 using (var featureView = db.OpenView("SELECT `Component_` FROM `FeatureComponents` WHERE `Feature_` = ?"))
312 using (var componentView = db.OpenView("SELECT `FileSize` FROM `File` WHERE `Component_` = ?"))
313 {
314 using (var featureRecord = new Record(1))
315 using (var componentRecord = new Record(1))
316 {
317 foreach (var allFeaturesResultRecord in allFeaturesView.Records)
318 {
319 var featureName = allFeaturesResultRecord.GetString(1);
320
321 // Calculate the Feature size.
322 featureRecord.SetString(1, featureName);
323 featureView.Execute(featureRecord);
324
325 // Loop over all the components for the feature to calculate the size of the feature.
326 long size = 0;
327 foreach (var componentResultRecord in featureView.Records)
328 {
329 var component = componentResultRecord.GetString(1);
330 componentRecord.SetString(1, component);
331 componentView.Execute(componentRecord);
332
333 foreach (var fileResultRecord in componentView.Records)
334 {
335 var fileSize = fileResultRecord.GetString(1);
336 size += Convert.ToInt32(fileSize, CultureInfo.InvariantCulture.NumberFormat);
337 }
338 }
339
340 this.Section.AddSymbol(new WixBundleMsiFeatureSymbol(this.Facade.PackageSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, this.Facade.PackageId, featureName))
341 {
342 PackageRef = this.Facade.PackageId,
343 Name = featureName,
344 Parent = allFeaturesResultRecord.GetString(2),
345 Title = allFeaturesResultRecord.GetString(3),
346 Description = allFeaturesResultRecord.GetString(4),
347 Display = allFeaturesResultRecord.GetInteger(5),
348 Level = allFeaturesResultRecord.GetInteger(6),
349 Directory = allFeaturesResultRecord.GetString(7),
350 Attributes = allFeaturesResultRecord.GetInteger(8),
351 Size = size
352 });
353 }
354 }
355 }
356 }
357 }
358
359 private void ImportExternalCabinetAsPayloads(Database db, WixBundlePayloadSymbol packagePayload, ISet<string> payloadNames)
360 {
361 if (db.TableExists("Media"))
362 {
363 using (var view = db.OpenExecuteView("SELECT `Cabinet` FROM `Media`"))
364 {
365 foreach (var cabinetRecord in view.Records)
366 {
367 var cabinet = cabinetRecord.GetString(1);
368
369 if (!String.IsNullOrEmpty(cabinet) && !cabinet.StartsWith("#", StringComparison.Ordinal))
370 {
371 // If we didn't find the Payload as an existing child of the package, we need to
372 // add it. We expect the file to exist on-disk in the same relative location as
373 // the MSI expects to find it...
374 var cabinetName = Path.Combine(Path.GetDirectoryName(packagePayload.Name), cabinet);
375
376 if (!payloadNames.Contains(cabinetName))
377 {
378 var generatedId = this.BackendHelper.GenerateIdentifier("cab", packagePayload.Id.Id, cabinet);
379 var payloadSourceFile = this.ResolveRelatedFile(packagePayload.SourceFile.Path, packagePayload.UnresolvedSourceFile, cabinet, "Cabinet", this.Facade.PackageSymbol.SourceLineNumbers);
380
381 this.Section.AddSymbol(new WixGroupSymbol(this.Facade.PackageSymbol.SourceLineNumbers)
382 {
383 ParentType = ComplexReferenceParentType.Package,
384 ParentId = this.Facade.PackageId,
385 ChildType = ComplexReferenceChildType.Payload,
386 ChildId = generatedId
387 });
388
389 this.Section.AddSymbol(new WixBundlePayloadSymbol(this.Facade.PackageSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, generatedId))
390 {
391 Name = cabinetName,
392 SourceFile = new IntermediateFieldPathValue { Path = payloadSourceFile },
393 Compressed = packagePayload.Compressed,
394 UnresolvedSourceFile = cabinetName,
395 ContainerRef = packagePayload.ContainerRef,
396 ContentFile = true,
397 Packaging = packagePayload.Packaging,
398 ParentPackagePayloadRef = packagePayload.Id.Id,
399 });
400 }
401 }
402 }
403 }
404 }
405 }
406
407 private long ImportExternalFileAsPayloadsAndReturnInstallSize(Database db, WixBundlePayloadSymbol packagePayload, bool longNamesInImage, bool compressed, ISet<string> payloadNames)
408 {
409 long size = 0;
410
411 if (db.TableExists("Component") && db.TableExists("Directory") && db.TableExists("File"))
412 {
413 var directories = new Dictionary<string, IResolvedDirectory>();
414
415 // Load up the directory hash table so we will be able to resolve source paths
416 // for files in the MSI database.
417 using (var view = db.OpenExecuteView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`"))
418 {
419 foreach (var record in view.Records)
420 {
421 var sourceName = this.BackendHelper.GetMsiFileName(record.GetString(3), true, longNamesInImage);
422
423 var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(record.GetString(2), sourceName);
424
425 directories.Add(record.GetString(1), resolvedDirectory);
426 }
427 }
428
429 // Resolve the source paths to external files and add each file size to the total
430 // install size of the package.
431 using (var view = db.OpenExecuteView("SELECT `Directory_`, `File`, `FileName`, `File`.`Attributes`, `FileSize` FROM `Component`, `File` WHERE `Component`.`Component`=`File`.`Component_`"))
432 {
433 foreach (var record in view.Records)
434 {
435 // If the file is explicitly uncompressed or the MSI is uncompressed and the file is not
436 // explicitly marked compressed then this is an external file.
437 var compressionBit = record.GetInteger(4);
438 if (WindowsInstallerConstants.MsidbFileAttributesNoncompressed == (compressionBit & WindowsInstallerConstants.MsidbFileAttributesNoncompressed) ||
439 (!compressed && 0 == (compressionBit & WindowsInstallerConstants.MsidbFileAttributesCompressed)))
440 {
441 var fileSourcePath = this.PathResolver.GetFileSourcePath(directories, record.GetString(1), record.GetString(3), compressed, longNamesInImage);
442 var name = Path.Combine(Path.GetDirectoryName(packagePayload.Name), fileSourcePath);
443
444 if (!payloadNames.Contains(name))
445 {
446 var generatedId = this.BackendHelper.GenerateIdentifier("f", packagePayload.Id.Id, record.GetString(2));
447 var payloadSourceFile = this.ResolveRelatedFile(packagePayload.SourceFile.Path, packagePayload.UnresolvedSourceFile, fileSourcePath, "File", this.Facade.PackageSymbol.SourceLineNumbers);
448
449 this.Section.AddSymbol(new WixGroupSymbol(this.Facade.PackageSymbol.SourceLineNumbers)
450 {
451 ParentType = ComplexReferenceParentType.Package,
452 ParentId = this.Facade.PackageId,
453 ChildType = ComplexReferenceChildType.Payload,
454 ChildId = generatedId
455 });
456
457 this.Section.AddSymbol(new WixBundlePayloadSymbol(this.Facade.PackageSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, generatedId))
458 {
459 Name = name,
460 SourceFile = new IntermediateFieldPathValue { Path = payloadSourceFile },
461 Compressed = packagePayload.Compressed,
462 UnresolvedSourceFile = name,
463 ContainerRef = packagePayload.ContainerRef,
464 ContentFile = true,
465 Packaging = packagePayload.Packaging,
466 ParentPackagePayloadRef = packagePayload.Id.Id,
467 });
468 }
469 }
470
471 size += record.GetInteger(5);
472 }
473 }
474 }
475
476 return size;
477 }
478
479 private void AddMsiProperty(WixBundleMsiPackageSymbol msiPackage, string name, string value)
480 {
481 this.Section.AddSymbol(new WixBundleMsiPropertySymbol(msiPackage.SourceLineNumbers, new Identifier(AccessModifier.Section, msiPackage.Id.Id, name))
482 {
483 PackageRef = msiPackage.Id.Id,
484 Name = name,
485 Value = value,
486 });
487 }
488
489 private void ImportDependencyProviders(Database db, WixBundleMsiPackageSymbol msiPackage)
490 {
491 if (db.TableExists("WixDependencyProvider"))
492 {
493 using (var view = db.OpenExecuteView("SELECT `WixDependencyProvider`, `ProviderKey`, `Version`, `DisplayName`, `Attributes` FROM `WixDependencyProvider`"))
494 {
495 foreach (var record in view.Records)
496 {
497 var id = new Identifier(AccessModifier.Section, this.BackendHelper.GenerateIdentifier("dep", msiPackage.Id.Id, record.GetString(1)));
498
499 // Import the provider key and attributes.
500 this.Section.AddSymbol(new WixDependencyProviderSymbol(msiPackage.SourceLineNumbers, id)
501 {
502 ParentRef = msiPackage.Id.Id,
503 ProviderKey = record.GetString(2),
504 Version = record.GetString(3) ?? msiPackage.ProductVersion,
505 DisplayName = record.GetString(4) ?? this.Facade.PackageSymbol.DisplayName,
506 Attributes = WixDependencyProviderAttributes.ProvidesAttributesImported | (WixDependencyProviderAttributes)record.GetInteger(5),
507 });
508 }
509 }
510 }
511 }
512
513 private string ResolveRelatedFile(string resolvedSource, string unresolvedSource, string relatedSource, string type, SourceLineNumber sourceLineNumbers)
514 {
515 var checkedPaths = new List<string>();
516
517 foreach (var extension in this.BackendExtensions)
518 {
519 var resolved = extension.ResolveRelatedFile(unresolvedSource, relatedSource, type, sourceLineNumbers);
520
521 if (resolved?.CheckedPaths != null)
522 {
523 checkedPaths.AddRange(resolved.CheckedPaths);
524 }
525
526 if (!String.IsNullOrEmpty(resolved?.Path))
527 {
528 return resolved?.Path;
529 }
530 }
531
532 var resolvedPath = Path.Combine(Path.GetDirectoryName(resolvedSource), relatedSource);
533
534 if (!File.Exists(resolvedPath))
535 {
536 checkedPaths.Add(resolvedPath);
537 this.Messaging.Write(ErrorMessages.FileNotFound(sourceLineNumbers, resolvedPath, type, checkedPaths));
538 }
539
540 return resolvedPath;
541 }
542
543 private static string GetProperty(View view, string property)
544 {
545 using (var queryRecord = new Record(1))
546 {
547 queryRecord[1] = property;
548
549 view.Execute(queryRecord);
550
551 using (var record = view.Fetch())
552 {
553 return record?.GetString(1);
554 }
555 }
556 }
557 }
558}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn.Bundles
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Text;
9 using System.Xml;
10 using WixToolset.Core.Native.Msi;
11 using WixToolset.Data;
12 using WixToolset.Data.Symbols;
13 using WixToolset.Extensibility.Services;
14
15 /// <summary>
16 /// Initializes package state from the Msp contents.
17 /// </summary>
18 internal class ProcessMspPackageCommand
19 {
20 private const string PatchMetadataQuery = "SELECT `Value` FROM `MsiPatchMetadata` WHERE `Property` = ?";
21 private static readonly XmlWriterSettings XmlSettings = new XmlWriterSettings()
22 {
23 Encoding = new UTF8Encoding(false),
24 Indent = false,
25 NewLineChars = String.Empty,
26 NewLineHandling = NewLineHandling.Replace,
27 };
28
29 public ProcessMspPackageCommand(IMessaging messaging, IntermediateSection section, PackageFacade facade, Dictionary<string, WixBundlePayloadSymbol> payloadSymbols)
30 {
31 this.Messaging = messaging;
32
33 this.AuthoredPayloads = payloadSymbols;
34 this.Section = section;
35 this.Facade = facade;
36 }
37
38 public IMessaging Messaging { get; }
39
40 public Dictionary<string, WixBundlePayloadSymbol> AuthoredPayloads { private get; set; }
41
42 public PackageFacade Facade { private get; set; }
43
44 public IntermediateSection Section { get; }
45
46 /// <summary>
47 /// Processes the Msp packages to add properties and payloads from the Msp packages.
48 /// </summary>
49 public void Execute()
50 {
51 var packagePayload = this.AuthoredPayloads[this.Facade.PackageSymbol.PayloadRef];
52
53 var mspPackage = (WixBundleMspPackageSymbol)this.Facade.SpecificPackageSymbol;
54
55 var sourcePath = packagePayload.SourceFile.Path;
56
57 try
58 {
59 using (var db = new Database(sourcePath, OpenDatabase.ReadOnly | OpenDatabase.OpenPatchFile))
60 {
61 // Read data out of the msp database...
62 using (var sumInfo = new SummaryInformation(db))
63 {
64 var patchCode = sumInfo.GetProperty(SummaryInformation.Patch.PatchCode);
65 mspPackage.PatchCode = patchCode.Substring(0, 38);
66 }
67
68 using (var view = db.OpenView(PatchMetadataQuery))
69 {
70 if (String.IsNullOrEmpty(this.Facade.PackageSymbol.DisplayName))
71 {
72 this.Facade.PackageSymbol.DisplayName = ProcessMspPackageCommand.GetPatchMetadataProperty(view, "DisplayName");
73 }
74
75 if (String.IsNullOrEmpty(this.Facade.PackageSymbol.Description))
76 {
77 this.Facade.PackageSymbol.Description = ProcessMspPackageCommand.GetPatchMetadataProperty(view, "Description");
78 }
79
80 mspPackage.Manufacturer = ProcessMspPackageCommand.GetPatchMetadataProperty(view, "ManufacturerName");
81 }
82 }
83
84 this.ProcessPatchXml(packagePayload, mspPackage, sourcePath);
85 }
86 catch (MsiException e)
87 {
88 this.Messaging.Write(ErrorMessages.UnableToReadPackageInformation(packagePayload.SourceLineNumbers, sourcePath, e.Message));
89 return;
90 }
91
92 if (String.IsNullOrEmpty(this.Facade.PackageSymbol.CacheId))
93 {
94 this.Facade.PackageSymbol.CacheId = mspPackage.PatchCode;
95 }
96 }
97
98 private void ProcessPatchXml(WixBundlePayloadSymbol packagePayload, WixBundleMspPackageSymbol mspPackage, string sourcePath)
99 {
100 var uniqueTargetCodes = new HashSet<string>();
101
102 var patchXml = Installer.ExtractPatchXml(sourcePath);
103
104 var doc = new XmlDocument();
105 doc.LoadXml(patchXml);
106
107 var nsmgr = new XmlNamespaceManager(doc.NameTable);
108 nsmgr.AddNamespace("p", "http://www.microsoft.com/msi/patch_applicability.xsd");
109
110 // Determine target ProductCodes and/or UpgradeCodes.
111 foreach (XmlNode node in doc.SelectNodes("/p:MsiPatch/p:TargetProduct", nsmgr))
112 {
113 // If this patch targets a product code, this is the best case.
114 var targetCodeElement = node.SelectSingleNode("p:TargetProductCode", nsmgr);
115 var attributes = WixBundlePatchTargetCodeAttributes.None;
116
117 if (ProcessMspPackageCommand.TargetsCode(targetCodeElement))
118 {
119 attributes = WixBundlePatchTargetCodeAttributes.TargetsProductCode;
120 }
121 else // maybe targets an upgrade code?
122 {
123 targetCodeElement = node.SelectSingleNode("p:UpgradeCode", nsmgr);
124 if (ProcessMspPackageCommand.TargetsCode(targetCodeElement))
125 {
126 attributes = WixBundlePatchTargetCodeAttributes.TargetsUpgradeCode;
127 }
128 else // this patch targets an unknown number of products
129 {
130 mspPackage.Attributes |= WixBundleMspPackageAttributes.TargetUnspecified;
131 }
132 }
133
134 var targetCode = targetCodeElement.InnerText;
135
136 if (uniqueTargetCodes.Add(targetCode))
137 {
138 this.Section.AddSymbol(new WixBundlePatchTargetCodeSymbol(packagePayload.SourceLineNumbers)
139 {
140 PackageRef = packagePayload.Id.Id,
141 TargetCode = targetCode,
142 Attributes = attributes
143 });
144 }
145 }
146
147 // Suppress patch sequence data for improved performance.
148 var root = doc.DocumentElement;
149 foreach (XmlNode node in root.SelectNodes("p:SequenceData", nsmgr))
150 {
151 root.RemoveChild(node);
152 }
153
154 // Save the XML as compact as possible.
155 using (var writer = new StringWriter())
156 {
157 using (var xmlWriter = XmlWriter.Create(writer, XmlSettings))
158 {
159 doc.WriteTo(xmlWriter);
160 }
161
162 mspPackage.PatchXml = writer.ToString();
163 }
164 }
165
166 private static string GetPatchMetadataProperty(View view, string property)
167 {
168 using (var queryRecord = new Record(1))
169 {
170 queryRecord[1] = property;
171
172 view.Execute(queryRecord);
173
174 using (var record = view.Fetch())
175 {
176 return record?.GetString(1);
177 }
178 }
179 }
180
181 private static bool TargetsCode(XmlNode node) => "true" == node?.Attributes["Validate"]?.Value;
182 }
183}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn.Bundles
4{
5 using System;
6 using System.Collections.Generic;
7 using WixToolset.Data;
8 using WixToolset.Data.Symbols;
9
10 /// <summary>
11 /// Processes the Msu packages to add properties and payloads from the Msu packages.
12 /// </summary>
13 internal class ProcessMsuPackageCommand
14 {
15 public ProcessMsuPackageCommand(PackageFacade facade, Dictionary<string, WixBundlePayloadSymbol> payloadSymbols)
16 {
17 this.AuthoredPayloads = payloadSymbols;
18 this.Facade = facade;
19 }
20
21 public Dictionary<string, WixBundlePayloadSymbol> AuthoredPayloads { private get; set; }
22
23 public PackageFacade Facade { private get; set; }
24
25 public void Execute()
26 {
27 var packagePayload = this.AuthoredPayloads[this.Facade.PackageSymbol.PayloadRef];
28
29 if (String.IsNullOrEmpty(this.Facade.PackageSymbol.CacheId))
30 {
31 this.Facade.PackageSymbol.CacheId = packagePayload.Hash;
32 }
33
34 this.Facade.PackageSymbol.PerMachine = YesNoDefaultType.Yes; // MSUs are always per-machine.
35 }
36 }
37}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn.Bundles
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.IO;
9 using WixToolset.Core.Burn.Interfaces;
10 using WixToolset.Data;
11 using WixToolset.Data.Burn;
12 using WixToolset.Data.Symbols;
13 using WixToolset.Extensibility.Data;
14 using WixToolset.Extensibility.Services;
15
16 internal class ProcessPayloadsCommand
17 {
18 public ProcessPayloadsCommand(IBackendHelper backendHelper, IPayloadHarvester payloadHarvester, IEnumerable<WixBundlePayloadSymbol> payloads, PackagingType defaultPackaging, string layoutDirectory)
19 {
20 this.BackendHelper = backendHelper;
21 this.PayloadHarvester = payloadHarvester;
22 this.Payloads = payloads;
23 this.DefaultPackaging = defaultPackaging;
24 this.LayoutDirectory = layoutDirectory;
25 }
26
27 public IEnumerable<IFileTransfer> FileTransfers { get; private set; }
28
29 public IEnumerable<ITrackedFile> TrackedFiles { get; private set; }
30
31 private IBackendHelper BackendHelper { get; }
32
33 private IPayloadHarvester PayloadHarvester { get; }
34
35 private IEnumerable<WixBundlePayloadSymbol> Payloads { get; }
36
37 private PackagingType DefaultPackaging { get; }
38
39 private string LayoutDirectory { get; }
40
41 public void Execute()
42 {
43 var fileTransfers = new List<IFileTransfer>();
44 var trackedFiles = new List<ITrackedFile>();
45
46 foreach (var payload in this.Payloads)
47 {
48 payload.Name = this.BackendHelper.GetCanonicalRelativePath(payload.SourceLineNumbers, "Payload", "Name", payload.Name);
49
50 // Embedded files (aka: files from binary .wixlibs) are not content files (because they are hidden
51 // in the .wixlib).
52 var sourceFile = payload.SourceFile;
53 payload.ContentFile = sourceFile != null && !sourceFile.Embed;
54
55 this.UpdatePayloadPackagingType(payload);
56
57 if (!this.PayloadHarvester.HarvestStandardInformation(payload))
58 {
59 // Remote payloads obviously cannot be embedded.
60 Debug.Assert(PackagingType.Embedded != payload.Packaging);
61 }
62 else // not a remote payload so we have a lot more to update.
63 {
64 // External payloads need to be transfered.
65 if (PackagingType.External == payload.Packaging)
66 {
67 var transfer = this.BackendHelper.CreateFileTransfer(sourceFile.Path, Path.Combine(this.LayoutDirectory, payload.Name), false, payload.SourceLineNumbers);
68 fileTransfers.Add(transfer);
69 }
70
71 if (payload.ContentFile)
72 {
73 trackedFiles.Add(this.BackendHelper.TrackFile(sourceFile.Path, TrackedFileType.Input, payload.SourceLineNumbers));
74 }
75 }
76 }
77
78 this.FileTransfers = fileTransfers;
79 this.TrackedFiles = trackedFiles;
80 }
81
82 private void UpdatePayloadPackagingType(WixBundlePayloadSymbol payload)
83 {
84 if (!payload.Packaging.HasValue || PackagingType.Unknown == payload.Packaging)
85 {
86 if (!payload.Compressed.HasValue)
87 {
88 payload.Packaging = this.DefaultPackaging;
89 }
90 else if (payload.Compressed.Value)
91 {
92 payload.Packaging = PackagingType.Embedded;
93 }
94 else
95 {
96 payload.Packaging = PackagingType.External;
97 }
98 }
99
100 // Embedded payloads that are not assigned a container already are placed in the default attached
101 // container.
102 if (PackagingType.Embedded == payload.Packaging && String.IsNullOrEmpty(payload.ContainerRef))
103 {
104 payload.ContainerRef = BurnConstants.BurnDefaultAttachedContainerName;
105 }
106 }
107 }
108}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn
4{
5 using WixToolset.Data;
6
7 internal static class BurnBackendErrors
8 {
9 public static Message BAContainerPayloadCollision(SourceLineNumber sourceLineNumbers, string payloadId, string payloadName)
10 {
11 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);
12 }
13
14 public static Message BAContainerPayloadCollision2(SourceLineNumber sourceLineNumbers)
15 {
16 return Message(sourceLineNumbers, Ids.BAContainerPayloadCollision2, "The location of the payload related to the previous error.");
17 }
18
19 public static Message DuplicateCacheIds(SourceLineNumber originalLineNumber, string cacheId, string packageId)
20 {
21 return Message(originalLineNumber, Ids.DuplicateCacheIds, "The CacheId '{0}' for package '{1}' is duplicated. Each package must have a unique CacheId.", cacheId, packageId);
22 }
23
24 public static Message DuplicateCacheIds2(SourceLineNumber duplicateLineNumber)
25 {
26 return Message(duplicateLineNumber, Ids.DuplicateCacheIds2, "The location of the package related to the previous error.");
27 }
28
29 public static Message ExternalPayloadCollision(SourceLineNumber sourceLineNumbers, string symbolName, string payloadId, string payloadName)
30 {
31 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);
32 }
33
34 public static Message ExternalPayloadCollision2(SourceLineNumber sourceLineNumbers)
35 {
36 return Message(sourceLineNumbers, Ids.ExternalPayloadCollision2, "The location of the symbol related to the previous error.");
37 }
38
39 public static Message MultipleAttachedContainersUnsupported(SourceLineNumber sourceLineNumbers, string containerId)
40 {
41 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);
42 }
43
44 public static Message PackageCachePayloadCollision(SourceLineNumber sourceLineNumbers, string payloadId, string payloadName, string packageId)
45 {
46 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);
47 }
48
49 public static Message PackageCachePayloadCollision2(SourceLineNumber sourceLineNumbers)
50 {
51 return Message(sourceLineNumbers, Ids.PackageCachePayloadCollision2, "The location of the payload related to the previous error.");
52 }
53
54 private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args)
55 {
56 return new Message(sourceLineNumber, MessageLevel.Error, (int)id, format, args);
57 }
58
59 public enum Ids
60 {
61 DuplicateCacheIds = 8000,
62 DuplicateCacheIds2 = 8001,
63 BAContainerPayloadCollision = 8002,
64 BAContainerPayloadCollision2 = 8003,
65 ExternalPayloadCollision = 8004,
66 ExternalPayloadCollision2 = 8005,
67 PackageCachePayloadCollision = 8006,
68 PackageCachePayloadCollision2 = 8007,
69 MultipleAttachedContainersUnsupported = 8008,
70 } // last available is 8499. 8500 is BurnBackendWarnings.
71 }
72}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn
4{
5 using System;
6 using System.IO;
7 using WixToolset.Extensibility;
8
9 internal class BurnBackendFactory : IBackendFactory
10 {
11 public bool TryCreateBackend(string outputType, string outputFile, out IBackend backend)
12 {
13 if (String.IsNullOrEmpty(outputType))
14 {
15 outputType = Path.GetExtension(outputFile);
16 }
17
18 switch (outputType.ToLowerInvariant())
19 {
20 case "bundle":
21 case ".exe":
22 backend = new BundleBackend();
23 return true;
24 }
25
26 backend = null;
27 return false;
28 }
29 }
30}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn
4{
5 using WixToolset.Data;
6
7 internal static class BurnBackendWarnings
8 {
9 public static Message AttachedContainerPayloadCollision(SourceLineNumber sourceLineNumbers, string payloadId, string payloadName)
10 {
11 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);
12 }
13
14 public static Message AttachedContainerPayloadCollision2(SourceLineNumber sourceLineNumbers)
15 {
16 return Message(sourceLineNumbers, Ids.AttachedContainerPayloadCollision2, "The location of the payload related to the previous error.");
17 }
18
19 public static Message EmptyContainer(SourceLineNumber sourceLineNumbers, string containerId)
20 {
21 return Message(sourceLineNumbers, Ids.EmptyContainer, "The Container '{0}' is being ignored because it doesn't have any payloads.", containerId);
22 }
23
24 private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args)
25 {
26 return new Message(sourceLineNumber, MessageLevel.Warning, (int)id, format, args);
27 }
28
29 public enum Ids
30 {
31 AttachedContainerPayloadCollision = 8500,
32 AttachedContainerPayloadCollision2 = 8501,
33 EmptyContainer = 8502,
34 } // last available is 8999. 9000 is VerboseMessages.
35 }
36}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn
4{
5 using System;
6 using WixToolset.Extensibility;
7
8 internal class BurnExtensionFactory : IExtensionFactory
9 {
10 public bool TryCreateExtension(Type extensionType, out object extension)
11 {
12 extension = null;
13
14 if (extensionType == typeof(IBackendFactory))
15 {
16 extension = new BurnBackendFactory();
17 }
18
19 return extension != null;
20 }
21 }
22}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn.ExtensibilityServices
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Text;
9 using System.Xml;
10 using WixToolset.Core.Burn.Bundles;
11 using WixToolset.Data;
12 using WixToolset.Data.Symbols;
13 using WixToolset.Data.WindowsInstaller.Rows;
14 using WixToolset.Extensibility.Data;
15 using WixToolset.Extensibility.Services;
16
17 internal class BurnBackendHelper : IInternalBurnBackendHelper
18 {
19 public static readonly XmlReaderSettings ReaderSettings = new XmlReaderSettings { ConformanceLevel = ConformanceLevel.Fragment };
20 public static readonly XmlWriterSettings WriterSettings = new XmlWriterSettings { ConformanceLevel = ConformanceLevel.Fragment };
21
22 private readonly IBackendHelper backendHelper;
23
24 private ManifestData BootstrapperApplicationManifestData { get; } = new ManifestData();
25
26 private Dictionary<string, ManifestData> BundleExtensionDataById { get; } = new Dictionary<string, ManifestData>();
27
28 public BurnBackendHelper(IServiceProvider serviceProvider)
29 {
30 this.backendHelper = serviceProvider.GetService<IBackendHelper>();
31 }
32
33 #region IBackendHelper interfaces
34
35 public IFileFacade CreateFileFacade(FileSymbol file, AssemblySymbol assembly) => this.backendHelper.CreateFileFacade(file, assembly);
36
37 public IFileFacade CreateFileFacade(FileRow fileRow) => this.backendHelper.CreateFileFacade(fileRow);
38
39 public IFileFacade CreateFileFacadeFromMergeModule(FileSymbol fileSymbol) => this.backendHelper.CreateFileFacadeFromMergeModule(fileSymbol);
40
41 public IFileTransfer CreateFileTransfer(string source, string destination, bool move, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.CreateFileTransfer(source, destination, move, sourceLineNumbers);
42
43 public string CreateGuid() => this.backendHelper.CreateGuid();
44
45 public string CreateGuid(Guid namespaceGuid, string value) => this.backendHelper.CreateGuid(namespaceGuid, value);
46
47 public IResolvedDirectory CreateResolvedDirectory(string directoryParent, string name) => this.backendHelper.CreateResolvedDirectory(directoryParent, name);
48
49 public IReadOnlyList<ITrackedFile> ExtractEmbeddedFiles(IEnumerable<IExpectedExtractFile> embeddedFiles) => this.backendHelper.ExtractEmbeddedFiles(embeddedFiles);
50
51 public string GenerateIdentifier(string prefix, params string[] args) => this.backendHelper.GenerateIdentifier(prefix, args);
52
53 public string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath) => this.backendHelper.GetCanonicalRelativePath(sourceLineNumbers, elementName, attributeName, relativePath);
54
55 public int GetValidCodePage(string value, bool allowNoChange, bool onlyAnsi = false, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.GetValidCodePage(value, allowNoChange, onlyAnsi, sourceLineNumbers);
56
57 public string GetMsiFileName(string value, bool source, bool longName) => this.backendHelper.GetMsiFileName(value, source, longName);
58
59 public bool IsValidBinderVariable(string variable) => this.backendHelper.IsValidBinderVariable(variable);
60
61 public bool IsValidFourPartVersion(string version) => this.backendHelper.IsValidFourPartVersion(version);
62
63 public bool IsValidIdentifier(string id) => this.backendHelper.IsValidIdentifier(id);
64
65 public bool IsValidLongFilename(string filename, bool allowWildcards, bool allowRelative) => this.backendHelper.IsValidLongFilename(filename, allowWildcards, allowRelative);
66
67 public bool IsValidShortFilename(string filename, bool allowWildcards) => this.backendHelper.IsValidShortFilename(filename, allowWildcards);
68
69 public void ResolveDelayedFields(IEnumerable<IDelayedField> delayedFields, Dictionary<string, string> variableCache) => this.backendHelper.ResolveDelayedFields(delayedFields, variableCache);
70
71 public string[] SplitMsiFileName(string value) => this.backendHelper.SplitMsiFileName(value);
72
73 public ITrackedFile TrackFile(string path, TrackedFileType type, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.TrackFile(path, type, sourceLineNumbers);
74
75 #endregion
76
77 #region IBurnBackendHelper interfaces
78
79 public void AddBootstrapperApplicationData(string xml)
80 {
81 this.BootstrapperApplicationManifestData.AddXml(xml);
82 }
83
84 public void AddBootstrapperApplicationData(IntermediateSymbol symbol, bool symbolIdIsIdAttribute = false)
85 {
86 this.BootstrapperApplicationManifestData.AddSymbol(symbol, symbolIdIsIdAttribute, BurnCommon.BADataNamespace);
87 }
88
89 public void AddBundleExtensionData(string extensionId, string xml)
90 {
91 var manifestData = this.GetBundleExtensionManifestData(extensionId);
92 manifestData.AddXml(xml);
93 }
94
95 public void AddBundleExtensionData(string extensionId, IntermediateSymbol symbol, bool symbolIdIsIdAttribute = false)
96 {
97 var manifestData = this.GetBundleExtensionManifestData(extensionId);
98 manifestData.AddSymbol(symbol, symbolIdIsIdAttribute, BurnCommon.BundleExtensionDataNamespace);
99 }
100
101 #endregion
102
103 #region IInternalBurnBackendHelper interfaces
104
105 public void WriteBootstrapperApplicationData(XmlWriter writer)
106 {
107 this.BootstrapperApplicationManifestData.Write(writer);
108 }
109
110 public void WriteBundleExtensionData(XmlWriter writer)
111 {
112 foreach (var kvp in this.BundleExtensionDataById)
113 {
114 this.WriteExtension(writer, kvp.Key, kvp.Value);
115 }
116 }
117
118 #endregion
119
120 private ManifestData GetBundleExtensionManifestData(string extensionId)
121 {
122 if (!this.backendHelper.IsValidIdentifier(extensionId))
123 {
124 throw new ArgumentException($"'{extensionId}' is not a valid extensionId");
125 }
126
127 if (!this.BundleExtensionDataById.TryGetValue(extensionId, out var manifestData))
128 {
129 manifestData = new ManifestData();
130 this.BundleExtensionDataById.Add(extensionId, manifestData);
131 }
132
133 return manifestData;
134 }
135
136 private void WriteExtension(XmlWriter writer, string extensionId, ManifestData manifestData)
137 {
138 writer.WriteStartElement("BundleExtension");
139
140 writer.WriteAttributeString("Id", extensionId);
141
142 manifestData.Write(writer);
143
144 writer.WriteEndElement();
145 }
146
147 private class ManifestData
148 {
149 public ManifestData()
150 {
151 this.Builder = new StringBuilder();
152 }
153
154 private StringBuilder Builder { get; }
155
156 public void AddSymbol(IntermediateSymbol symbol, bool symbolIdIsIdAttribute, string ns)
157 {
158 // There might be a more efficient way to do this,
159 // but this is an easy way to ensure we're creating valid XML.
160 var sb = new StringBuilder();
161 using (var writer = XmlWriter.Create(sb, WriterSettings))
162 {
163 writer.WriteStartElement(symbol.Definition.Name, ns);
164
165 if (symbolIdIsIdAttribute && symbol.Id != null)
166 {
167 writer.WriteAttributeString("Id", symbol.Id.Id);
168 }
169
170 foreach (var field in symbol.Fields)
171 {
172 if (!field.IsNull())
173 {
174 writer.WriteAttributeString(field.Definition.Name, field.AsString());
175 }
176 }
177
178 writer.WriteEndElement();
179 }
180
181 this.AddXml(sb.ToString());
182 }
183
184 public void AddXml(string xml)
185 {
186 // There might be a more efficient way to do this,
187 // but this is an easy way to ensure we're given valid XML.
188 var sb = new StringBuilder();
189 using (var xmlWriter = XmlWriter.Create(sb, WriterSettings))
190 {
191 AddManifestDataFromString(xmlWriter, xml);
192 }
193 this.Builder.Append(sb.ToString());
194 }
195
196 public void Write(XmlWriter writer)
197 {
198 AddManifestDataFromString(writer, this.Builder.ToString());
199 }
200
201 private static void AddManifestDataFromString(XmlWriter xmlWriter, string xml)
202 {
203 using (var stringReader = new StringReader(xml))
204 using (var xmlReader = XmlReader.Create(stringReader, ReaderSettings))
205 {
206 while (xmlReader.MoveToContent() != XmlNodeType.None)
207 {
208 xmlWriter.WriteNode(xmlReader, false);
209 }
210 }
211 }
212 }
213 }
214}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn.ExtensibilityServices
4{
5 using System;
6 using System.Diagnostics;
7 using System.IO;
8 using WixToolset.Core.Burn.Bundles;
9 using WixToolset.Core.Burn.Interfaces;
10 using WixToolset.Data.Symbols;
11
12 internal class PayloadHarvester : IPayloadHarvester
13 {
14 private static readonly Version EmptyVersion = new Version(0, 0, 0, 0);
15
16 /// <inheritdoc />
17 public bool HarvestStandardInformation(WixBundlePayloadSymbol payload)
18 {
19 var filePath = payload.SourceFile?.Path;
20
21 if (String.IsNullOrEmpty(filePath))
22 {
23 return false;
24 }
25
26 this.UpdatePayloadFileInformation(payload, filePath);
27
28 this.UpdatePayloadVersionInformation(payload, filePath);
29
30 return true;
31 }
32
33 private void UpdatePayloadFileInformation(WixBundlePayloadSymbol payload, string filePath)
34 {
35 var fileInfo = new FileInfo(filePath);
36
37 if (null != fileInfo)
38 {
39 payload.FileSize = fileInfo.Length;
40
41 payload.Hash = BundleHashAlgorithm.Hash(fileInfo);
42 }
43 else
44 {
45 payload.FileSize = 0;
46 }
47 }
48
49 private void UpdatePayloadVersionInformation(WixBundlePayloadSymbol payload, string filePath)
50 {
51 var versionInfo = FileVersionInfo.GetVersionInfo(filePath);
52
53 if (null != versionInfo)
54 {
55 // Use the fixed version info block for the file since the resource text may not be a dotted quad.
56 var version = new Version(versionInfo.ProductMajorPart, versionInfo.ProductMinorPart, versionInfo.ProductBuildPart, versionInfo.ProductPrivatePart);
57
58 if (PayloadHarvester.EmptyVersion != version)
59 {
60 payload.Version = version.ToString();
61 }
62
63 payload.Description = versionInfo.FileDescription;
64 payload.DisplayName = versionInfo.ProductName;
65 }
66 }
67 }
68}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn
4{
5 using System.Xml;
6 using WixToolset.Extensibility.Services;
7
8 internal interface IInternalBurnBackendHelper : IBurnBackendHelper
9 {
10 void WriteBootstrapperApplicationData(XmlWriter writer);
11
12 void WriteBundleExtensionData(XmlWriter writer);
13 }
14}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn
4{
5 using System.Xml;
6
7 internal interface ISearchFacade
8 {
9 /// <summary>
10 /// Writes the search to the Burn manifest.
11 /// </summary>
12 /// <param name="writer"></param>
13 void WriteXml(XmlTextWriter writer);
14 }
15}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn.Inscribe
4{
5 using System.IO;
6 using WixToolset.Core.Burn.Bundles;
7 using WixToolset.Core.Native;
8 using WixToolset.Extensibility.Data;
9 using WixToolset.Extensibility.Services;
10
11 internal class InscribeBundleCommand
12 {
13 public InscribeBundleCommand(IInscribeContext context)
14 {
15 this.Context = context;
16
17 this.Messaging = context.ServiceProvider.GetService<IMessaging>();
18 }
19
20 private IInscribeContext Context { get; }
21
22 public IMessaging Messaging { get; }
23
24 public bool Execute()
25 {
26 var inscribed = false;
27 var tempFile = Path.Combine(this.Context.IntermediateFolder, "bundle_engine_signed.exe");
28
29 using (var reader = BurnReader.Open(this.Context.InputFilePath))
30 {
31 FileSystem.CopyFile(this.Context.SignedEngineFile, tempFile, allowHardlink: false);
32
33 // If there was an attached container on the original (unsigned) bundle, put it back.
34 if (reader.AttachedContainerSize > 0)
35 {
36 reader.Stream.Seek(reader.AttachedContainerAddress, SeekOrigin.Begin);
37
38 using (var writer = BurnWriter.Open(this.Messaging, tempFile))
39 {
40 writer.RememberThenResetSignature();
41 writer.AppendContainer(reader.Stream, reader.AttachedContainerSize, BurnCommon.Container.Attached);
42 inscribed = true;
43 }
44 }
45 }
46
47 Directory.CreateDirectory(Path.GetDirectoryName(this.Context.OutputFile));
48
49 FileSystem.MoveFile(tempFile, this.Context.OutputFile);
50
51 return inscribed;
52 }
53 }
54}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn.Inscribe
4{
5 using System;
6 using System.IO;
7 using WixToolset.Core.Burn.Bundles;
8 using WixToolset.Core.Native;
9 using WixToolset.Extensibility.Data;
10
11 internal class InscribeBundleEngineCommand
12 {
13 public InscribeBundleEngineCommand(IInscribeContext context)
14 {
15 this.IntermediateFolder = context.IntermediateFolder;
16 this.InputFilePath = context.InputFilePath;
17 this.OutputFile = context.OutputFile;
18 }
19
20 private string IntermediateFolder { get; }
21
22 private string InputFilePath { get; }
23
24 private string OutputFile { get; }
25
26 public bool Execute()
27 {
28 var tempFile = Path.Combine(this.IntermediateFolder, "bundle_engine_unsigned.exe");
29
30 using (var reader = BurnReader.Open(this.InputFilePath))
31 using (var writer = File.Open(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read | FileShare.Delete))
32 {
33 reader.Stream.Seek(0, SeekOrigin.Begin);
34
35 var buffer = new byte[4 * 1024];
36 var total = 0;
37 var read = 0;
38 do
39 {
40 read = Math.Min(buffer.Length, (int)reader.EngineSize - total);
41
42 read = reader.Stream.Read(buffer, 0, read);
43 writer.Write(buffer, 0, read);
44
45 total += read;
46 } while (total < reader.EngineSize && 0 < read);
47
48 if (total != reader.EngineSize)
49 {
50 throw new InvalidOperationException("Failed to copy engine out of bundle.");
51 }
52
53 // TODO: update writer with detached container signatures.
54 }
55
56 Directory.CreateDirectory(Path.GetDirectoryName(this.OutputFile));
57
58 FileSystem.MoveFile(tempFile, this.OutputFile);
59
60 return true;
61 }
62 }
63}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn.Interfaces
4{
5 using System.Diagnostics;
6 using WixToolset.Data.Symbols;
7
8 /// <summary>
9 /// Service for harvesting payload information.
10 /// </summary>
11 public interface IPayloadHarvester
12 {
13 /// <summary>
14 /// Uses <see cref="WixBundlePayloadSymbol.SourceFile"/> to:
15 /// update <see cref="WixBundlePayloadSymbol.Hash"/> from file contents,
16 /// update <see cref="WixBundlePayloadSymbol.FileSize"/> from file size, and
17 /// update <see cref="WixBundlePayloadSymbol.Description"/>, <see cref="WixBundlePayloadSymbol.DisplayName"/>, and <see cref="WixBundlePayloadSymbol.Version"/> from <see cref="FileVersionInfo"/>.
18 /// </summary>
19 /// <param name="payload">The symbol to update.</param>
20 /// <returns>Whether the symbol had a source file specified.</returns>
21 bool HarvestStandardInformation(WixBundlePayloadSymbol payload);
22 }
23}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn
4{
5 using System;
6 using System.Collections.Generic;
7 using WixToolset.Data.WindowsInstaller;
8
9 /// <summary>
10 /// A list of rows indexed by their primary key. Unlike a RowDictionary
11 /// this indexed list will track rows in their added order and will allow rows with
12 /// duplicate keys to be added to the list, although only the first row will be indexed.
13 /// </summary>
14 internal sealed class RowIndexedList<T> : IList<T> where T : Row
15 {
16 private readonly Dictionary<string, T> index;
17 private readonly List<T> rows;
18 private readonly List<T> duplicates;
19
20 /// <summary>
21 /// Creates an empty <see cref="RowIndexedList{T}"/>.
22 /// </summary>
23 public RowIndexedList()
24 {
25 this.index = new Dictionary<string, T>(StringComparer.InvariantCulture);
26 this.rows = new List<T>();
27 this.duplicates = new List<T>();
28 }
29
30 /// <summary>
31 /// Creates and populates a <see cref="RowIndexedList{T}"/> with the rows from the given enumerator.
32 /// </summary>
33 /// <param name="rows">Rows to index.</param>
34 public RowIndexedList(IEnumerable<T> rows)
35 : this()
36 {
37 foreach (var row in rows)
38 {
39 this.Add(row);
40 }
41 }
42
43 /// <summary>
44 /// Creates and populates a <see cref="RowIndexedList{T}"/> with the rows from the given <see cref="Table"/>.
45 /// </summary>
46 /// <param name="table">The table to index.</param>
47 /// <remarks>
48 /// Rows added to the index are not automatically added to the given <paramref name="table"/>.
49 /// </remarks>
50 public RowIndexedList(Table table)
51 : this()
52 {
53 if (null != table)
54 {
55 foreach (T row in table.Rows)
56 {
57 this.Add(row);
58 }
59 }
60 }
61
62 /// <summary>
63 /// Gets the duplicates in the list.
64 /// </summary>
65 public IEnumerable<T> Duplicates { get { return this.duplicates; } }
66
67 /// <summary>
68 /// Gets the row by integer key.
69 /// </summary>
70 /// <param name="key">Integer key to look up.</param>
71 /// <returns>Row or null if key is not found.</returns>
72 public T Get(int key)
73 {
74 return this.Get(key.ToString());
75 }
76
77 /// <summary>
78 /// Gets the row by string key.
79 /// </summary>
80 /// <param name="key">String key to look up.</param>
81 /// <returns>Row or null if key is not found.</returns>
82 public T Get(string key)
83 {
84 return this.TryGet(key, out var result) ? result : null;
85 }
86
87 /// <summary>
88 /// Gets the row by string key if it exists.
89 /// </summary>
90 /// <param name="key">Key of row to get.</param>
91 /// <param name="row">Row found.</param>
92 /// <returns>True if key was found otherwise false.</returns>
93 public bool TryGet(string key, out T row)
94 {
95 return this.index.TryGetValue(key, out row);
96 }
97
98 /// <summary>
99 /// Tries to add a row as long as it would not create a duplicate.
100 /// </summary>
101 /// <param name="row">Row to add.</param>
102 /// <returns>True if the row as added otherwise false.</returns>
103 public bool TryAdd(T row)
104 {
105 try
106 {
107 this.index.Add(row.GetKey(), row);
108 }
109 catch (ArgumentException) // if the key already exists, bail.
110 {
111 return false;
112 }
113
114 this.rows.Add(row);
115 return true;
116 }
117
118 /// <summary>
119 /// Adds a row to the list. If a row with the same key is already index, the row is
120 /// is not in the index but will still be part of the list and added to the duplicates
121 /// list.
122 /// </summary>
123 /// <param name="row"></param>
124 public void Add(T row)
125 {
126 this.rows.Add(row);
127 try
128 {
129 this.index.Add(row.GetKey(), row);
130 }
131 catch (ArgumentException) // if the key already exists, we have a duplicate.
132 {
133 this.duplicates.Add(row);
134 }
135 }
136
137 /// <summary>
138 /// Gets the index of a row.
139 /// </summary>
140 /// <param name="row">Iterates through the list of rows to find the index of a particular row.</param>
141 /// <returns>Index of row or -1 if not found.</returns>
142 public int IndexOf(T row)
143 {
144 return this.rows.IndexOf(row);
145 }
146
147 /// <summary>
148 /// Inserts a row at a particular index of the list.
149 /// </summary>
150 /// <param name="index">Index to insert the row after.</param>
151 /// <param name="row">Row to insert.</param>
152 public void Insert(int index, T row)
153 {
154 this.rows.Insert(index, row);
155 try
156 {
157 this.index.Add(row.GetKey(), row);
158 }
159 catch (ArgumentException) // if the key already exists, we have a duplicate.
160 {
161 this.duplicates.Add(row);
162 }
163 }
164
165 /// <summary>
166 /// Removes a row from a particular index.
167 /// </summary>
168 /// <param name="index">Index to remove the row at.</param>
169 public void RemoveAt(int index)
170 {
171 var row = this.rows[index];
172
173 this.rows.RemoveAt(index);
174
175 if (this.index.TryGetValue(row.GetKey(), out var indexRow) && indexRow == row)
176 {
177 this.index.Remove(row.GetKey());
178 }
179 else // only try to remove from duplicates if the row was not indexed (if it was indexed, it wasn't a dupe).
180 {
181 this.duplicates.Remove(row);
182 }
183 }
184
185 /// <summary>
186 /// Gets or sets a row at the specified index.
187 /// </summary>
188 /// <param name="index">Index to get the row.</param>
189 /// <returns>Row at specified index.</returns>
190 public T this[int index]
191 {
192 get
193 {
194 return this.rows[index];
195 }
196 set
197 {
198 this.rows[index] = value;
199 try
200 {
201 this.index.Add(value.GetKey(), value);
202 }
203 catch (ArgumentException) // if the key already exists, we have a duplicate.
204 {
205 this.duplicates.Add(value);
206 }
207 }
208 }
209
210 /// <summary>
211 /// Empties the list and it's index.
212 /// </summary>
213 public void Clear()
214 {
215 this.index.Clear();
216 this.rows.Clear();
217 this.duplicates.Clear();
218 }
219
220 /// <summary>
221 /// Searches the list for a row without using the index.
222 /// </summary>
223 /// <param name="row">Row to look for in the list.</param>
224 /// <returns>True if the row is in the list, otherwise false.</returns>
225 public bool Contains(T row)
226 {
227 return this.rows.Contains(row);
228 }
229
230 /// <summary>
231 /// Copies the rows of the list to an array.
232 /// </summary>
233 /// <param name="array">Array to copy the list into.</param>
234 /// <param name="arrayIndex">Index to start copying at.</param>
235 public void CopyTo(T[] array, int arrayIndex)
236 {
237 this.rows.CopyTo(array, arrayIndex);
238 }
239
240 /// <summary>
241 /// Number of rows in the list.
242 /// </summary>
243 public int Count
244 {
245 get { return this.rows.Count; }
246 }
247
248 /// <summary>
249 /// Indicates whether the list is read-only. Always false.
250 /// </summary>
251 public bool IsReadOnly
252 {
253 get { return false; }
254 }
255
256 /// <summary>
257 /// Removes a row from the list. Indexed rows will be removed but the colleciton will NOT
258 /// promote duplicates to the index automatically. The duplicate would also need to be removed
259 /// and re-added to be indexed.
260 /// </summary>
261 /// <param name="row"></param>
262 /// <returns></returns>
263 public bool Remove(T row)
264 {
265 var removed = this.rows.Remove(row);
266 if (removed)
267 {
268 if (this.index.TryGetValue(row.GetKey(), out var indexRow) && indexRow == row)
269 {
270 this.index.Remove(row.GetKey());
271 }
272 else // only try to remove from duplicates if the row was not indexed (if it was indexed, it wasn't a dupe).
273 {
274 this.duplicates.Remove(row);
275 }
276 }
277
278 return removed;
279 }
280
281 /// <summary>
282 /// Gets an enumerator over the whole list.
283 /// </summary>
284 /// <returns>List enumerator.</returns>
285 public IEnumerator<T> GetEnumerator()
286 {
287 return this.rows.GetEnumerator();
288 }
289
290 /// <summary>
291 /// Gets an untyped enumerator over the whole list.
292 /// </summary>
293 /// <returns>Untyped list enumerator.</returns>
294 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
295 {
296 return this.rows.GetEnumerator();
297 }
298 }
299}
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project Sdk="Microsoft.NET.Sdk">
5 <PropertyGroup>
6 <TargetFrameworks>netstandard2.0</TargetFrameworks>
7 <TargetFrameworks Condition=" '$(Configuration)'=='Release' ">$(TargetFrameworks);net461;net472</TargetFrameworks>
8 <Description>Core Burn</Description>
9 <Title>WiX Toolset Core Burn</Title>
10 <DebugType>embedded</DebugType>
11 <PublishRepositoryUrl>true</PublishRepositoryUrl>
12 <CreateDocumentationFile>true</CreateDocumentationFile>
13 </PropertyGroup>
14
15 <ItemGroup>
16 <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
17 <_Parameter1>WixToolset.Core.TestPackage, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9967ec28982f42ee51a47dd5204315975a6ed69294b982146a99a70130a2fa13e226aaddde14c17d1bf3af69e8956d69a86585e74d208efcc5ac98a0686055327b2e87960d3c39bf3a6bc1e572863327d19dbf4fd2616dda124dbea260755a2d1d39d3cf1049ea526493eb2bf996b8ad985e3012308529e5b9b0f5cd5fa04bd</_Parameter1>
18 </AssemblyAttribute>
19 <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
20 <_Parameter1>WixToolsetTest.Core.Burn, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9967ec28982f42ee51a47dd5204315975a6ed69294b982146a99a70130a2fa13e226aaddde14c17d1bf3af69e8956d69a86585e74d208efcc5ac98a0686055327b2e87960d3c39bf3a6bc1e572863327d19dbf4fd2616dda124dbea260755a2d1d39d3cf1049ea526493eb2bf996b8ad985e3012308529e5b9b0f5cd5fa04bd</_Parameter1>
21 </AssemblyAttribute>
22 <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
23 <_Parameter1>WixToolsetTest.CoreIntegration, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9967ec28982f42ee51a47dd5204315975a6ed69294b982146a99a70130a2fa13e226aaddde14c17d1bf3af69e8956d69a86585e74d208efcc5ac98a0686055327b2e87960d3c39bf3a6bc1e572863327d19dbf4fd2616dda124dbea260755a2d1d39d3cf1049ea526493eb2bf996b8ad985e3012308529e5b9b0f5cd5fa04bd</_Parameter1>
24 </AssemblyAttribute>
25 </ItemGroup>
26
27 <ItemGroup>
28 <PackageReference Include="WixToolset.Burn" Version="4.0.*" />
29 <PackageReference Include="WixToolset.Core.Native" Version="4.0.*" />
30 <PackageReference Include="WixToolset.Data" Version="4.0.*" />
31 <PackageReference Include="WixToolset.Dtf.Resources" Version="4.0.*" />
32 <PackageReference Include="WixToolset.Extensibility" Version="4.0.*" />
33 </ItemGroup>
34
35 <ItemGroup>
36 <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
37 <PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" PrivateAssets="all" />
38 </ItemGroup>
39</Project>
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn
4{
5 using System;
6 using System.Collections.Generic;
7 using WixToolset.Core.Burn.ExtensibilityServices;
8 using WixToolset.Core.Burn.Interfaces;
9 using WixToolset.Extensibility.Services;
10
11 /// <summary>
12 /// Extensions methods for adding Burn services.
13 /// </summary>
14 public static class WixToolsetCoreServiceProviderExtensions
15 {
16 /// <summary>
17 /// Adds Burn Services.
18 /// </summary>
19 /// <param name="coreProvider"></param>
20 /// <returns></returns>
21 public static IWixToolsetCoreServiceProvider AddBundleBackend(this IWixToolsetCoreServiceProvider coreProvider)
22 {
23 AddServices(coreProvider);
24
25 var extensionManager = coreProvider.GetService<IExtensionManager>();
26 extensionManager.Add(typeof(BurnExtensionFactory).Assembly);
27
28 return coreProvider;
29 }
30
31 private static void AddServices(IWixToolsetCoreServiceProvider coreProvider)
32 {
33 // Singletons.
34 coreProvider.AddService((provider, singletons) => AddSingleton<IInternalBurnBackendHelper>(singletons, new BurnBackendHelper(provider)));
35 coreProvider.AddService((provider, singletons) => AddSingleton<IPayloadHarvester>(singletons, new PayloadHarvester()));
36 coreProvider.AddService((provider, singletons) => AddSingleton<IBurnBackendHelper>(singletons, provider.GetService<IInternalBurnBackendHelper>()));
37 }
38
39 private static T AddSingleton<T>(Dictionary<Type, object> singletons, T service) where T : class
40 {
41 singletons.Add(typeof(T), service);
42 return service;
43 }
44 }
45}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.ExtensionCache
4{
5 internal class CachedExtension
6 {
7 public CachedExtension(string id, string version, bool damaged)
8 {
9 this.Id = id;
10 this.Version = version;
11 this.Damaged = damaged;
12 }
13
14 public string Id { get; }
15
16 public string Version { get; }
17
18 public bool Damaged { get; }
19 }
20}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.ExtensionCache
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 using System.Threading;
10 using System.Threading.Tasks;
11 using NuGet.Common;
12 using NuGet.Configuration;
13 using NuGet.Credentials;
14 using NuGet.Packaging;
15 using NuGet.Protocol;
16 using NuGet.Protocol.Core.Types;
17 using NuGet.Versioning;
18
19 /// <summary>
20 /// Extension cache manager.
21 /// </summary>
22 internal class ExtensionCacheManager
23 {
24 public string CacheFolder(bool global) => global ? this.GlobalCacheFolder() : this.LocalCacheFolder();
25
26 public string LocalCacheFolder() => Path.Combine(Environment.CurrentDirectory, ".wix", "extensions");
27
28 public string GlobalCacheFolder()
29 {
30 var baseFolder = Environment.GetEnvironmentVariable("WIX_EXTENSIONS") ?? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
31 return Path.Combine(baseFolder, ".wix", "extensions");
32 }
33
34 public async Task<bool> AddAsync(bool global, string extension, CancellationToken cancellationToken)
35 {
36 if (String.IsNullOrEmpty(extension))
37 {
38 throw new ArgumentNullException(nameof(extension));
39 }
40
41 (var extensionId, var extensionVersion) = ParseExtensionReference(extension);
42
43 var result = await this.DownloadAndExtractAsync(global, extensionId, extensionVersion, cancellationToken);
44
45 return result;
46 }
47
48 public Task<bool> RemoveAsync(bool global, string extension, CancellationToken cancellationToken)
49 {
50 if (String.IsNullOrEmpty(extension))
51 {
52 throw new ArgumentNullException(nameof(extension));
53 }
54
55 (var extensionId, var extensionVersion) = ParseExtensionReference(extension);
56
57 var cacheFolder = this.CacheFolder(global);
58
59 cacheFolder = Path.Combine(cacheFolder, extensionId, extensionVersion);
60
61 if (Directory.Exists(cacheFolder))
62 {
63 cancellationToken.ThrowIfCancellationRequested();
64
65 Directory.Delete(cacheFolder, true);
66 return Task.FromResult(true);
67 }
68
69 return Task.FromResult(false);
70 }
71
72 public Task<IEnumerable<CachedExtension>> ListAsync(bool global, string extension, CancellationToken cancellationToken)
73 {
74 var found = new List<CachedExtension>();
75
76 (var extensionId, var extensionVersion) = ParseExtensionReference(extension);
77
78 var cacheFolder = this.CacheFolder(global);
79
80 var searchFolder = Path.Combine(cacheFolder, extensionId, extensionVersion);
81
82 if (!Directory.Exists(searchFolder))
83 {
84 }
85 else if (!String.IsNullOrEmpty(extensionVersion)) // looking for an explicit version of an extension.
86 {
87 var present = ExtensionFileExists(cacheFolder, extensionId, extensionVersion);
88 found.Add(new CachedExtension(extensionId, extensionVersion, !present));
89 }
90 else // looking for all versions of an extension or all versions of all extensions.
91 {
92 IEnumerable<string> foundExtensionIds;
93
94 if (String.IsNullOrEmpty(extensionId))
95 {
96 // Looking for all versions of all extensions.
97 foundExtensionIds = Directory.GetDirectories(cacheFolder).Select(folder => Path.GetFileName(folder)).ToList();
98 }
99 else
100 {
101 // Looking for all versions of a single extension.
102 var extensionFolder = Path.Combine(cacheFolder, extensionId);
103 foundExtensionIds = Directory.Exists(extensionFolder) ? new[] { extensionId } : Array.Empty<string>();
104 }
105
106 foreach (var foundExtensionId in foundExtensionIds)
107 {
108 var extensionFolder = Path.Combine(cacheFolder, foundExtensionId);
109
110 foreach (var folder in Directory.GetDirectories(extensionFolder))
111 {
112 cancellationToken.ThrowIfCancellationRequested();
113
114 var foundExtensionVersion = Path.GetFileName(folder);
115
116 if (!NuGetVersion.TryParse(foundExtensionVersion, out _))
117 {
118 continue;
119 }
120
121 var present = ExtensionFileExists(cacheFolder, foundExtensionId, foundExtensionVersion);
122 found.Add(new CachedExtension(foundExtensionId, foundExtensionVersion, !present));
123 }
124 }
125 }
126
127 return Task.FromResult((IEnumerable<CachedExtension>)found);
128 }
129
130 private async Task<bool> DownloadAndExtractAsync(bool global, string id, string version, CancellationToken cancellationToken)
131 {
132 var logger = NullLogger.Instance;
133
134 DefaultCredentialServiceUtility.SetupDefaultCredentialService(logger, nonInteractive: false);
135
136 var settings = Settings.LoadDefaultSettings(root: Environment.CurrentDirectory);
137 var sources = PackageSourceProvider.LoadPackageSources(settings).Where(s => s.IsEnabled);
138
139 using (var cache = new SourceCacheContext())
140 {
141 PackageSource versionSource = null;
142
143 var nugetVersion = String.IsNullOrEmpty(version) ? null : new NuGetVersion(version);
144
145 if (nugetVersion is null)
146 {
147 foreach (var source in sources)
148 {
149 var repository = Repository.Factory.GetCoreV3(source.Source);
150 var resource = await repository.GetResourceAsync<FindPackageByIdResource>();
151
152 var availableVersions = await resource.GetAllVersionsAsync(id, cache, logger, cancellationToken);
153 foreach (var availableVersion in availableVersions)
154 {
155 if (nugetVersion is null || nugetVersion < availableVersion)
156 {
157 nugetVersion = availableVersion;
158 versionSource = source;
159 }
160 }
161 }
162
163 if (nugetVersion is null)
164 {
165 return false;
166 }
167 }
168
169 var searchSources = versionSource is null ? sources : new[] { versionSource };
170
171 var extensionFolder = Path.Combine(this.CacheFolder(global), id, nugetVersion.ToString());
172
173 foreach (var source in searchSources)
174 {
175 var repository = Repository.Factory.GetCoreV3(source.Source);
176 var resource = await repository.GetResourceAsync<FindPackageByIdResource>();
177
178 using (var stream = new MemoryStream())
179 {
180 var downloaded = await resource.CopyNupkgToStreamAsync(id, nugetVersion, stream, cache, logger, cancellationToken);
181
182 if (downloaded)
183 {
184 stream.Position = 0;
185
186 using (var archive = new PackageArchiveReader(stream))
187 {
188 var files = PackagingConstants.Folders.Known.SelectMany(folder => archive.GetFiles(folder)).Distinct(StringComparer.OrdinalIgnoreCase);
189 await archive.CopyFilesAsync(extensionFolder, files, this.ExtractProgress, logger, cancellationToken);
190 }
191
192 return true;
193 }
194 }
195 }
196 }
197
198 return false;
199 }
200
201 private string ExtractProgress(string sourceFile, string targetPath, Stream fileStream) => fileStream.CopyToFile(targetPath);
202
203 private static (string extensionId, string extensionVersion) ParseExtensionReference(string extensionReference)
204 {
205 var extensionId = extensionReference ?? String.Empty;
206 var extensionVersion = String.Empty;
207
208 var index = extensionId.LastIndexOf('/');
209 if (index > 0)
210 {
211 extensionVersion = extensionReference.Substring(index + 1);
212 extensionId = extensionReference.Substring(0, index);
213
214 if (!NuGetVersion.TryParse(extensionVersion, out _))
215 {
216 throw new ArgumentException($"Invalid extension version in {extensionReference}");
217 }
218
219 if (String.IsNullOrEmpty(extensionId))
220 {
221 throw new ArgumentException($"Invalid extension id in {extensionReference}");
222 }
223 }
224
225 return (extensionId, extensionVersion);
226 }
227
228 private static bool ExtensionFileExists(string baseFolder, string extensionId, string extensionVersion)
229 {
230 var toolsFolder = Path.Combine(baseFolder, extensionId, extensionVersion, "tools");
231 if (!Directory.Exists(toolsFolder))
232 {
233 return false;
234 }
235
236 var extensionAssembly = Path.Combine(toolsFolder, extensionId + ".dll");
237
238 var present = File.Exists(extensionAssembly);
239 if (!present)
240 {
241 extensionAssembly = Path.Combine(toolsFolder, extensionId + ".exe");
242 present = File.Exists(extensionAssembly);
243 }
244
245 return present;
246 }
247 }
248}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.ExtensionCache
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using System.Threading;
9 using System.Threading.Tasks;
10 using WixToolset.Extensibility.Data;
11 using WixToolset.Extensibility.Services;
12
13 /// <summary>
14 /// Extension cache manager command.
15 /// </summary>
16 internal class ExtensionCacheManagerCommand : ICommandLineCommand
17 {
18 private enum CacheSubcommand
19 {
20 Add,
21 Remove,
22 List
23 }
24
25 public ExtensionCacheManagerCommand(IServiceProvider serviceProvider)
26 {
27 this.Messaging = serviceProvider.GetService<IMessaging>();
28 this.ExtensionReferences = new List<string>();
29 }
30
31 private IMessaging Messaging { get; }
32
33 public bool ShowLogo { get; private set; }
34
35 public bool StopParsing { get; private set; }
36
37 private bool ShowHelp { get; set; }
38
39 private bool Global { get; set; }
40
41 private CacheSubcommand? Subcommand { get; set; }
42
43 private List<string> ExtensionReferences { get; }
44
45 public async Task<int> ExecuteAsync(CancellationToken cancellationToken)
46 {
47 if (this.ShowHelp || !this.Subcommand.HasValue)
48 {
49 DisplayHelp();
50 return 1;
51 }
52
53 var success = false;
54 var cacheManager = new ExtensionCacheManager();
55
56 switch (this.Subcommand)
57 {
58 case CacheSubcommand.Add:
59 success = await this.AddExtensions(cacheManager, cancellationToken);
60 break;
61
62 case CacheSubcommand.Remove:
63 success = await this.RemoveExtensions(cacheManager, cancellationToken);
64 break;
65
66 case CacheSubcommand.List:
67 success = await this.ListExtensions(cacheManager, cancellationToken);
68 break;
69 }
70
71 return success ? 0 : 2;
72 }
73
74 public bool TryParseArgument(ICommandLineParser parser, string argument)
75 {
76 if (!parser.IsSwitch(argument))
77 {
78 if (!this.Subcommand.HasValue)
79 {
80 if (!Enum.TryParse(argument, true, out CacheSubcommand subcommand))
81 {
82 return false;
83 }
84
85 this.Subcommand = subcommand;
86 }
87 else
88 {
89 this.ExtensionReferences.Add(argument);
90 }
91
92 return true;
93 }
94
95 var parameter = argument.Substring(1);
96 switch (parameter.ToLowerInvariant())
97 {
98 case "?":
99 case "h":
100 case "-help":
101 this.ShowHelp = true;
102 this.ShowLogo = true;
103 this.StopParsing = true;
104 return true;
105
106 case "nologo":
107 case "-nologo":
108 this.ShowLogo = false;
109 return true;
110
111 case "g":
112 case "-global":
113 this.Global = true;
114 return true;
115 }
116
117 return false;
118 }
119
120 private async Task<bool> AddExtensions(ExtensionCacheManager cacheManager, CancellationToken cancellationToken)
121 {
122 var success = false;
123
124 foreach (var extensionRef in this.ExtensionReferences)
125 {
126 var added = await cacheManager.AddAsync(this.Global, extensionRef, cancellationToken);
127 success |= added;
128 }
129
130 return success;
131 }
132
133 private async Task<bool> RemoveExtensions(ExtensionCacheManager cacheManager, CancellationToken cancellationToken)
134 {
135 var success = false;
136
137 foreach (var extensionRef in this.ExtensionReferences)
138 {
139 var removed = await cacheManager.RemoveAsync(this.Global, extensionRef, cancellationToken);
140 success |= removed;
141 }
142
143 return success;
144 }
145
146 private async Task<bool> ListExtensions(ExtensionCacheManager cacheManager, CancellationToken cancellationToken)
147 {
148 var found = false;
149 var extensionRef = this.ExtensionReferences.FirstOrDefault();
150
151 var extensions = await cacheManager.ListAsync(this.Global, extensionRef, cancellationToken);
152
153 foreach (var extension in extensions)
154 {
155 this.Messaging.Write($"{extension.Id} {extension.Version}{(extension.Damaged ? " (damaged)" : String.Empty)}");
156 found = true;
157 }
158
159 return found;
160 }
161
162 private static void DisplayHelp()
163 {
164 Console.WriteLine();
165 Console.WriteLine("Usage: wix extension add|remove|list [extensionRef]");
166 Console.WriteLine();
167 Console.WriteLine("Options:");
168 Console.WriteLine(" -h|--help Show command line help.");
169 Console.WriteLine(" -g|--global Add/remove the extension for the current user.");
170 Console.WriteLine(" --nologo Suppress displaying the logo information.");
171 Console.WriteLine();
172 Console.WriteLine("Commands:");
173 Console.WriteLine();
174 Console.WriteLine(" add Add extension to the cache.");
175 Console.WriteLine(" list List extensions in the cache.");
176 Console.WriteLine(" remove Remove extension from the cache.");
177 Console.WriteLine();
178 Console.WriteLine(" extensionRef format: extensionId/version (the version is optional)");
179 }
180 }
181}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.ExtensionCache
4{
5 using System;
6 using System.Collections.Generic;
7 using WixToolset.Extensibility;
8 using WixToolset.Extensibility.Data;
9 using WixToolset.Extensibility.Services;
10
11 /// <summary>
12 /// Parses the "extension" command-line command. See <c>ExtensionCacheManagerCommand</c>
13 /// for the bulk of the command-line processing.
14 /// </summary>
15 internal class ExtensionCacheManagerExtensionCommandLine : BaseExtensionCommandLine
16 {
17 public ExtensionCacheManagerExtensionCommandLine(IServiceProvider serviceProvider)
18 {
19 this.ServiceProvider = serviceProvider;
20 }
21
22 private IServiceProvider ServiceProvider { get; }
23
24 public override IReadOnlyCollection<ExtensionCommandLineSwitch> CommandLineSwitches => new ExtensionCommandLineSwitch[]
25 {
26 new ExtensionCommandLineSwitch { Switch = "extension", Description = "Manage extension cache." },
27 };
28
29 public override bool TryParseCommand(ICommandLineParser parser, string argument, out ICommandLineCommand command)
30 {
31 command = null;
32
33 if ("extension".Equals(argument, StringComparison.OrdinalIgnoreCase))
34 {
35 command = new ExtensionCacheManagerCommand(this.ServiceProvider);
36 }
37
38 return command != null;
39 }
40 }
41}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.ExtensionCache
4{
5 using System;
6 using WixToolset.Extensibility;
7 using WixToolset.Extensibility.Services;
8
9 internal class ExtensionCacheManagerExtensionFactory : IExtensionFactory
10 {
11 public ExtensionCacheManagerExtensionFactory(IServiceProvider serviceProvider)
12 {
13 this.ServiceProvider = serviceProvider;
14 }
15
16 private IServiceProvider ServiceProvider { get; }
17
18 public bool TryCreateExtension(Type extensionType, out object extension)
19 {
20 extension = null;
21
22 if (extensionType == typeof(IExtensionCommandLine))
23 {
24 extension = new ExtensionCacheManagerExtensionCommandLine(this.ServiceProvider);
25 }
26
27 return extension != null;
28 }
29 }
30}
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project Sdk="Microsoft.NET.Sdk">
5 <PropertyGroup>
6 <TargetFrameworks>netstandard2.0</TargetFrameworks>
7 <TargetFrameworks Condition=" '$(Configuration)'=='Release' ">$(TargetFrameworks);net461;net472</TargetFrameworks>
8 <Description>Extension Cache</Description>
9 <Title>WiX Toolset Extension Cache</Title>
10 <DebugType>embedded</DebugType>
11 <PublishRepositoryUrl>true</PublishRepositoryUrl>
12 <CreateDocumentationFile>true</CreateDocumentationFile>
13 </PropertyGroup>
14
15 <ItemGroup>
16 <PackageReference Include="WixToolset.Data" Version="4.0.*" />
17 <PackageReference Include="WixToolset.Extensibility" Version="4.0.*" />
18 </ItemGroup>
19
20 <ItemGroup>
21 <PackageReference Include="NuGet.Credentials" Version="5.6.0" />
22 <PackageReference Include="NuGet.Protocol" Version="5.6.0" />
23 </ItemGroup>
24
25 <ItemGroup>
26 <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
27 <PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" PrivateAssets="All" />
28 </ItemGroup>
29</Project>
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.ExtensionCache
4{
5 using System;
6 using System.Collections.Generic;
7 using WixToolset.Extensibility.Services;
8
9 /// <summary>
10 /// Extensions methods for adding ExtensionCache services.
11 /// </summary>
12 public static class WixToolsetCoreServiceProviderExtensions
13 {
14 /// <summary>
15 /// Adds ExtensionCache services.
16 /// </summary>
17 /// <param name="coreProvider"></param>
18 /// <returns></returns>
19 public static IWixToolsetCoreServiceProvider AddExtensionCacheManager(this IWixToolsetCoreServiceProvider coreProvider)
20 {
21 var extensionManager = coreProvider.GetService<IExtensionManager>();
22 extensionManager.Add(typeof(ExtensionCacheManagerExtensionFactory).Assembly);
23
24 coreProvider.AddService(CreateExtensionCacheManager);
25 return coreProvider;
26 }
27
28 private static ExtensionCacheManager CreateExtensionCacheManager(IWixToolsetCoreServiceProvider coreProvider, Dictionary<Type, object> singletons)
29 {
30 var extensionCacheManager = new ExtensionCacheManager();
31 singletons.Add(typeof(ExtensionCacheManager), extensionCacheManager);
32
33 return extensionCacheManager;
34 }
35 }
36}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.TestPackage
4{
5 using System.IO;
6 using System.Xml;
7 using WixToolset.Core.Burn.Bundles;
8 using WixToolset.Extensibility.Services;
9
10 /// <summary>
11 /// Class to extract bundle contents for testing.
12 /// </summary>
13 public class BundleExtractor
14 {
15 /// <summary>
16 /// Extracts the BA container.
17 /// </summary>
18 /// <param name="messaging"></param>
19 /// <param name="bundleFilePath">Path to the bundle.</param>
20 /// <param name="destinationFolderPath">Path to extract to.</param>
21 /// <param name="tempFolderPath">Temp path for extraction.</param>
22 /// <returns></returns>
23 public static ExtractBAContainerResult ExtractBAContainer(IMessaging messaging, string bundleFilePath, string destinationFolderPath, string tempFolderPath)
24 {
25 var result = new ExtractBAContainerResult();
26 Directory.CreateDirectory(tempFolderPath);
27 using (var burnReader = BurnReader.Open(messaging, bundleFilePath))
28 {
29 result.Success = burnReader.ExtractUXContainer(destinationFolderPath, tempFolderPath);
30 }
31
32 if (result.Success)
33 {
34 result.ManifestDocument = LoadBurnManifest(destinationFolderPath);
35 result.ManifestNamespaceManager = GetBurnNamespaceManager(result.ManifestDocument, "burn");
36
37 result.BADataDocument = LoadBAData(destinationFolderPath);
38 result.BADataNamespaceManager = GetBADataNamespaceManager(result.BADataDocument, "ba");
39
40 result.BundleExtensionDataDocument = LoadBundleExtensionData(destinationFolderPath);
41 result.BundleExtensionDataNamespaceManager = GetBundleExtensionDataNamespaceManager(result.BundleExtensionDataDocument, "be");
42 }
43
44 return result;
45 }
46
47 /// <summary>
48 /// Extracts the attached container.
49 /// </summary>
50 /// <param name="messaging"></param>
51 /// <param name="bundleFilePath">Path to the bundle.</param>
52 /// <param name="destinationFolderPath">Path to extract to.</param>
53 /// <param name="tempFolderPath">Temp path for extraction.</param>
54 /// <returns>True if there was an attached container.</returns>
55 public static bool ExtractAttachedContainer(IMessaging messaging, string bundleFilePath, string destinationFolderPath, string tempFolderPath)
56 {
57 Directory.CreateDirectory(tempFolderPath);
58 using (var burnReader = BurnReader.Open(messaging, bundleFilePath))
59 {
60 return burnReader.ExtractAttachedContainer(destinationFolderPath, tempFolderPath);
61 }
62 }
63
64 /// <summary>
65 /// Gets an <see cref="XmlNamespaceManager"/> for BootstrapperApplicationData.xml with the given prefix assigned to the root namespace.
66 /// </summary>
67 /// <param name="document"></param>
68 /// <param name="prefix"></param>
69 /// <returns></returns>
70 public static XmlNamespaceManager GetBADataNamespaceManager(XmlDocument document, string prefix)
71 {
72 var namespaceManager = new XmlNamespaceManager(document.NameTable);
73 namespaceManager.AddNamespace(prefix, BurnCommon.BADataNamespace);
74 return namespaceManager;
75 }
76
77 /// <summary>
78 /// Gets an <see cref="XmlNamespaceManager"/> for BundleExtensionData.xml with the given prefix assigned to the root namespace.
79 /// </summary>
80 /// <param name="document"></param>
81 /// <param name="prefix"></param>
82 /// <returns></returns>
83 public static XmlNamespaceManager GetBundleExtensionDataNamespaceManager(XmlDocument document, string prefix)
84 {
85 var namespaceManager = new XmlNamespaceManager(document.NameTable);
86 namespaceManager.AddNamespace(prefix, BurnCommon.BundleExtensionDataNamespace);
87 return namespaceManager;
88 }
89
90 /// <summary>
91 /// Gets an <see cref="XmlNamespaceManager"/> for the Burn manifest.xml with the given prefix assigned to the root namespace.
92 /// </summary>
93 /// <param name="document"></param>
94 /// <param name="prefix"></param>
95 /// <returns></returns>
96 public static XmlNamespaceManager GetBurnNamespaceManager(XmlDocument document, string prefix)
97 {
98 var namespaceManager = new XmlNamespaceManager(document.NameTable);
99 namespaceManager.AddNamespace(prefix, BurnCommon.BurnNamespace);
100 return namespaceManager;
101 }
102
103 /// <summary>
104 /// Loads an XmlDocument with the BootstrapperApplicationData.xml from the given folder that contains the contents of the BA container.
105 /// </summary>
106 /// <param name="baFolderPath"></param>
107 /// <returns></returns>
108 public static XmlDocument LoadBAData(string baFolderPath)
109 {
110 var document = new XmlDocument();
111 document.Load(Path.Combine(baFolderPath, BurnCommon.BADataFileName));
112 return document;
113 }
114
115 /// <summary>
116 /// Loads an XmlDocument with the BootstrapperApplicationData.xml from the given folder that contains the contents of the BA container.
117 /// </summary>
118 /// <param name="baFolderPath"></param>
119 /// <returns></returns>
120 public static XmlDocument LoadBundleExtensionData(string baFolderPath)
121 {
122 var document = new XmlDocument();
123 document.Load(Path.Combine(baFolderPath, BurnCommon.BundleExtensionDataFileName));
124 return document;
125 }
126
127 /// <summary>
128 /// Loads an XmlDocument with the BootstrapperApplicationData.xml from the given folder that contains the contents of the BA container.
129 /// </summary>
130 /// <param name="baFolderPath"></param>
131 /// <returns></returns>
132 public static XmlDocument LoadBurnManifest(string baFolderPath)
133 {
134 var document = new XmlDocument();
135 document.Load(Path.Combine(baFolderPath, "manifest.xml"));
136 return document;
137 }
138 }
139}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.TestPackage
4{
5 using System.IO;
6 using System.Xml;
7 using Xunit;
8
9 /// <summary>
10 /// The result of extracting the BA container.
11 /// </summary>
12 public class ExtractBAContainerResult
13 {
14 /// <summary>
15 /// <see cref="XmlDocument"/> for BundleExtensionData.xml.
16 /// </summary>
17 public XmlDocument BundleExtensionDataDocument { get; set; }
18
19 /// <summary>
20 /// <see cref="XmlNamespaceManager"/> for BundleExtensionData.xml.
21 /// </summary>
22 public XmlNamespaceManager BundleExtensionDataNamespaceManager { get; set; }
23
24 /// <summary>
25 /// <see cref="XmlDocument"/> for BootstrapperApplicationData.xml.
26 /// </summary>
27 public XmlDocument BADataDocument { get; set; }
28
29 /// <summary>
30 /// <see cref="XmlNamespaceManager"/> for BootstrapperApplicationData.xml.
31 /// </summary>
32 public XmlNamespaceManager BADataNamespaceManager { get; set; }
33
34 /// <summary>
35 /// <see cref="XmlDocument"/> for the Burn manifest.xml.
36 /// </summary>
37 public XmlDocument ManifestDocument { get; set; }
38
39 /// <summary>
40 /// <see cref="XmlNamespaceManager"/> for the Burn manifest.xml.
41 /// </summary>
42 public XmlNamespaceManager ManifestNamespaceManager { get; set; }
43
44 /// <summary>
45 /// Whether extraction succeeded.
46 /// </summary>
47 public bool Success { get; set; }
48
49 /// <summary>
50 ///
51 /// </summary>
52 /// <returns></returns>
53 public ExtractBAContainerResult AssertSuccess()
54 {
55 Assert.True(this.Success);
56 return this;
57 }
58
59 /// <summary>
60 /// Returns the relative path of the BA entry point dll in the given folder.
61 /// </summary>
62 /// <param name="extractedBAContainerFolderPath"></param>
63 /// <returns></returns>
64 public string GetBAFilePath(string extractedBAContainerFolderPath)
65 {
66 var uxPayloads = this.SelectManifestNodes("/burn:BurnManifest/burn:UX/burn:Payload");
67 var baPayload = uxPayloads[0];
68 var relativeBAPath = baPayload.Attributes["FilePath"].Value;
69 return Path.Combine(extractedBAContainerFolderPath, relativeBAPath);
70 }
71
72 /// <summary>
73 /// Returns the relative path of the BundleExtension entry point dll in the given folder.
74 /// </summary>
75 /// <param name="extractedBAContainerFolderPath"></param>
76 /// <param name="extensionId"></param>
77 /// <returns></returns>
78 public string GetBundleExtensionFilePath(string extractedBAContainerFolderPath, string extensionId)
79 {
80 var uxPayloads = this.SelectManifestNodes($"/burn:BurnManifest/burn:UX/burn:Payload[@Id='{extensionId}']");
81 var bextPayload = uxPayloads[0];
82 var relativeBextPath = bextPayload.Attributes["FilePath"].Value;
83 return Path.Combine(extractedBAContainerFolderPath, relativeBextPath);
84 }
85
86 /// <summary>
87 ///
88 /// </summary>
89 /// <param name="xpath">elements must have the 'ba' prefix</param>
90 /// <returns></returns>
91 public XmlNodeList SelectBADataNodes(string xpath)
92 {
93 return this.BADataDocument.SelectNodes(xpath, this.BADataNamespaceManager);
94 }
95
96 /// <summary>
97 ///
98 /// </summary>
99 /// <param name="xpath">elements must have the 'be' prefix</param>
100 /// <returns></returns>
101 public XmlNodeList SelectBundleExtensionDataNodes(string xpath)
102 {
103 return this.BundleExtensionDataDocument.SelectNodes(xpath, this.BundleExtensionDataNamespaceManager);
104 }
105
106 /// <summary>
107 ///
108 /// </summary>
109 /// <param name="xpath">elements must have the 'burn' prefix</param>
110 /// <returns></returns>
111 public XmlNodeList SelectManifestNodes(string xpath)
112 {
113 return this.ManifestDocument.SelectNodes(xpath, this.ManifestNamespaceManager);
114 }
115 }
116}
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 @@
1using System.Collections.Generic;
2using WixToolset.Data;
3using WixToolset.Extensibility;
4using WixToolset.Extensibility.Services;
5
6namespace WixToolset.Core.TestPackage
7{
8 /// <summary>
9 /// An <see cref="IMessageListener"/> that simply stores all the messages.
10 /// </summary>
11 public sealed class TestMessageListener : IMessageListener
12 {
13 /// <summary>
14 /// All messages that have been received.
15 /// </summary>
16 public List<Message> Messages { get; } = new List<Message>();
17
18 /// <summary>
19 ///
20 /// </summary>
21 public string ShortAppName => "TEST";
22
23 /// <summary>
24 ///
25 /// </summary>
26 public string LongAppName => "Test";
27
28 /// <summary>
29 /// Stores the message in <see cref="Messages"/>.
30 /// </summary>
31 /// <param name="message"></param>
32 public void Write(Message message)
33 {
34 this.Messages.Add(message);
35 }
36
37 /// <summary>
38 /// Stores the message in <see cref="Messages"/>.
39 /// </summary>
40 /// <param name="message"></param>
41 public void Write(string message)
42 {
43 this.Messages.Add(new Message(null, MessageLevel.Information, 0, message));
44 }
45
46 /// <summary>
47 /// Always returns defaultMessageLevel.
48 /// </summary>
49 /// <param name="messaging"></param>
50 /// <param name="message"></param>
51 /// <param name="defaultMessageLevel"></param>
52 /// <returns></returns>
53 public MessageLevel CalculateMessageLevel(IMessaging messaging, Message message, MessageLevel defaultMessageLevel) => defaultMessageLevel;
54 }
55}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.TestPackage
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Threading;
8 using System.Threading.Tasks;
9 using WixToolset.Core.Burn;
10 using WixToolset.Core.WindowsInstaller;
11 using WixToolset.Data;
12 using WixToolset.Extensibility.Services;
13
14 /// <summary>
15 /// Utility class to emulate wix.exe with standard backends.
16 /// </summary>
17 public static class WixRunner
18 {
19 /// <summary>
20 /// Emulates calling wix.exe with standard backends.
21 /// </summary>
22 /// <param name="args"></param>
23 /// <param name="messages"></param>
24 /// <param name="warningsAsErrors"></param>
25 /// <returns></returns>
26 public static int Execute(string[] args, out List<Message> messages, bool warningsAsErrors = true)
27 {
28 var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider();
29 var task = Execute(args, serviceProvider, out messages, warningsAsErrors: warningsAsErrors);
30 return task.Result;
31 }
32
33 /// <summary>
34 /// Emulates calling wix.exe with standard backends.
35 /// This overload always treats warnings as errors.
36 /// </summary>
37 /// <param name="args"></param>
38 /// <returns></returns>
39 public static WixRunnerResult Execute(params string[] args)
40 {
41 return Execute(true, args);
42 }
43
44 /// <summary>
45 /// Emulates calling wix.exe with standard backends.
46 /// </summary>
47 /// <param name="warningsAsErrors"></param>
48 /// <param name="args"></param>
49 /// <returns></returns>
50 public static WixRunnerResult Execute(bool warningsAsErrors, params string[] args)
51 {
52 var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider();
53 var exitCode = Execute(args, serviceProvider, out var messages, warningsAsErrors: warningsAsErrors);
54 return new WixRunnerResult { ExitCode = exitCode.Result, Messages = messages.ToArray() };
55 }
56
57 /// <summary>
58 /// Emulates calling wix.exe with standard backends.
59 /// </summary>
60 /// <param name="args"></param>
61 /// <param name="coreProvider"></param>
62 /// <param name="messages"></param>
63 /// <param name="warningsAsErrors"></param>
64 /// <returns></returns>
65 public static Task<int> Execute(string[] args, IWixToolsetCoreServiceProvider coreProvider, out List<Message> messages, bool warningsAsErrors = true)
66 {
67 coreProvider.AddWindowsInstallerBackend()
68 .AddBundleBackend();
69
70 var listener = new TestMessageListener();
71
72 messages = listener.Messages;
73
74 var messaging = coreProvider.GetService<IMessaging>();
75 messaging.SetListener(listener);
76
77 var arguments = new List<string>(args);
78 if (warningsAsErrors)
79 {
80 arguments.Add("-wx");
81 }
82
83 var commandLine = coreProvider.GetService<ICommandLine>();
84 var command = commandLine.CreateCommand(arguments.ToArray());
85 return command?.ExecuteAsync(CancellationToken.None) ?? Task.FromResult(1);
86 }
87 }
88}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.TestPackage
4{
5 using System;
6 using System.Collections.Generic;
7 using WixToolset.Data;
8 using Xunit;
9
10 /// <summary>
11 /// The result of an Execute method of <see cref="WixRunner"/>.
12 /// </summary>
13 public class WixRunnerResult
14 {
15 /// <summary>
16 /// ExitCode for the operation.
17 /// </summary>
18 public int ExitCode { get; set; }
19
20 /// <summary>
21 /// Messages from the operation.
22 /// </summary>
23 public Message[] Messages { get; set; }
24
25 /// <summary>
26 ///
27 /// </summary>
28 /// <returns></returns>
29 public WixRunnerResult AssertSuccess()
30 {
31 AssertSuccess(this.ExitCode, this.Messages);
32 return this;
33 }
34
35 /// <summary>
36 ///
37 /// </summary>
38 /// <param name="exitCode"></param>
39 /// <param name="messages"></param>
40 public static void AssertSuccess(int exitCode, IEnumerable<Message> messages)
41 {
42 Assert.True(0 == exitCode, $"\r\n\r\nWixRunner failed with exit code: {exitCode}\r\n Output: {String.Join("\r\n ", FormatMessages(messages))}\r\n");
43 }
44
45 private static IEnumerable<string> FormatMessages(IEnumerable<Message> messages)
46 {
47 foreach (var message in messages)
48 {
49 var filename = message.SourceLineNumbers?.FileName ?? "TEST";
50 var line = message.SourceLineNumbers?.LineNumber ?? -1;
51 var type = message.Level.ToString().ToLowerInvariant();
52
53 if (line > 0)
54 {
55 filename = String.Concat(filename, "(", line, ")");
56 }
57
58 yield return String.Format("{0} : {1} {2}{3:0000}: {4}", filename, type, "TEST", message.Id, message.ToString());
59 }
60 }
61 }
62}
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project Sdk="Microsoft.NET.Sdk">
5 <PropertyGroup>
6 <TargetFrameworks>netstandard2.0</TargetFrameworks>
7 <TargetFrameworks Condition=" '$(Configuration)'=='Release' ">$(TargetFrameworks);net461;net472</TargetFrameworks>
8 <Description>Internal WiX Toolset Test Package</Description>
9 <DebugType>embedded</DebugType>
10 <PublishRepositoryUrl>true</PublishRepositoryUrl>
11 <CreateDocumentationFile>true</CreateDocumentationFile>
12 </PropertyGroup>
13
14 <ItemGroup>
15 <ProjectReference Include="..\WixToolset.Core\WixToolset.Core.csproj" IncludeAssets="true" />
16 <ProjectReference Include="..\WixToolset.Core.Burn\WixToolset.Core.Burn.csproj" IncludeAssets="true" />
17 <ProjectReference Include="..\WixToolset.Core.WindowsInstaller\WixToolset.Core.WindowsInstaller.csproj" IncludeAssets="true" />
18 </ItemGroup>
19
20 <ItemGroup>
21 <PackageReference Include="WixToolset.Data" Version="4.0.*" />
22 <PackageReference Include="WixToolset.Extensibility" Version="4.0.*" />
23 <PackageReference Include="WixToolset.Core.Native" Version="4.0.*" />
24 </ItemGroup>
25
26 <ItemGroup>
27 <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
28 <PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" PrivateAssets="All" />
29 </ItemGroup>
30
31 <ItemGroup>
32 <PackageReference Include="xunit.assert" Version="2.4.0" />
33 </ItemGroup>
34</Project>
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.TestPackage
4{
5 using System.Collections.Generic;
6 using System.IO;
7 using System.Text.RegularExpressions;
8 using System.Xml;
9
10 /// <summary>
11 /// Utility class to help compare XML in tests using string comparisons by using single quotes and stripping all namespaces.
12 /// </summary>
13 public static class XmlNodeExtensions
14 {
15 /// <summary>
16 /// Returns the node's outer XML using single quotes and stripping all namespaces.
17 /// </summary>
18 /// <param name="node"></param>
19 /// <param name="ignoredAttributesByElementName">Attributes for which the value should be set to '*'.</param>
20 /// <returns></returns>
21 public static string GetTestXml(this XmlNode node, Dictionary<string, List<string>> ignoredAttributesByElementName = null)
22 {
23 return node.OuterXml.GetTestXml(ignoredAttributesByElementName);
24 }
25
26 /// <summary>
27 /// Returns the XML using single quotes and stripping all namespaces.
28 /// </summary>
29 /// <param name="xml"></param>
30 /// <param name="ignoredAttributesByElementName">Attributes for which the value should be set to '*'.</param>
31 /// <returns></returns>
32 public static string GetTestXml(this string xml, Dictionary<string, List<string>> ignoredAttributesByElementName = null)
33 {
34 string formattedXml;
35 using (var sw = new StringWriter())
36 using (var writer = new TestXmlWriter(sw))
37 {
38 var doc = new XmlDocument();
39 doc.LoadXml(xml);
40
41 if (ignoredAttributesByElementName != null)
42 {
43 HandleIgnoredAttributes(doc, ignoredAttributesByElementName);
44 }
45
46 doc.Save(writer);
47 formattedXml = sw.ToString();
48 }
49
50 return Regex.Replace(formattedXml, " xmlns(:[^=]+)?='[^']*'", "");
51 }
52
53 private static void HandleIgnoredAttributes(XmlNode node, Dictionary<string, List<string>> ignoredAttributesByElementName)
54 {
55 if (node.Attributes != null && ignoredAttributesByElementName.TryGetValue(node.LocalName, out var ignoredAttributes))
56 {
57 foreach (var ignoredAttribute in ignoredAttributes)
58 {
59 var attribute = node.Attributes[ignoredAttribute];
60 if (attribute != null)
61 {
62 attribute.Value = "*";
63 }
64 }
65 }
66
67 if (node.ChildNodes != null)
68 {
69 foreach (XmlNode childNode in node.ChildNodes)
70 {
71 HandleIgnoredAttributes(childNode, ignoredAttributesByElementName);
72 }
73 }
74 }
75
76 private class TestXmlWriter : XmlTextWriter
77 {
78 public TestXmlWriter(TextWriter w)
79 : base(w)
80 {
81 this.QuoteChar = '\'';
82 }
83
84 public override void WriteStartDocument()
85 {
86 //OmitXmlDeclaration
87 }
88 }
89 }
90}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using WixToolset.Data.Symbols;
8 using WixToolset.Data.WindowsInstaller;
9
10 /// <summary>
11 /// Add back possibly suppressed sequence tables since all sequence tables must be present
12 /// for the merge process to work. We'll drop the suppressed sequence tables again as
13 /// necessary.
14 /// </summary>
15 internal class AddBackSuppressedSequenceTablesCommand
16 {
17 public AddBackSuppressedSequenceTablesCommand(WindowsInstallerData output, TableDefinitionCollection tableDefinitions)
18 {
19 this.Output = output;
20 this.TableDefinitions = tableDefinitions;
21 }
22
23 private WindowsInstallerData Output { get; }
24
25 private TableDefinitionCollection TableDefinitions { get; }
26
27 public IEnumerable<string> SuppressedTableNames { get; private set; }
28
29 public IEnumerable<string> Execute()
30 {
31 var suppressedTableNames = new HashSet<string>();
32
33 foreach (SequenceTable sequence in Enum.GetValues(typeof(SequenceTable)))
34 {
35 var sequenceTableName = sequence.WindowsInstallerTableName();
36 var sequenceTable = this.Output.Tables[sequenceTableName];
37
38 if (null == sequenceTable)
39 {
40 sequenceTable = this.Output.EnsureTable(this.TableDefinitions[sequenceTableName]);
41 }
42
43 if (0 == sequenceTable.Rows.Count)
44 {
45 suppressedTableNames.Add(sequenceTableName);
46 }
47 }
48
49 return this.SuppressedTableNames = suppressedTableNames;
50 }
51 }
52}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System.Collections.Generic;
6 using System.Linq;
7 using WixToolset.Data;
8 using WixToolset.Data.Symbols;
9
10 /// <summary>
11 /// Add CreateFolder symbols, if not already present, for null-keypath components.
12 /// </summary>
13 internal class AddCreateFoldersCommand
14 {
15 internal AddCreateFoldersCommand(IntermediateSection section)
16 {
17 this.Section = section;
18 }
19
20 private IntermediateSection Section { get; }
21
22 public void Execute()
23 {
24 var createFolderSymbolsByComponentRef = new HashSet<string>(this.Section.Symbols.OfType<CreateFolderSymbol>().Select(t => t.ComponentRef));
25 foreach (var componentSymbol in this.Section.Symbols.OfType<ComponentSymbol>().Where(t => t.KeyPathType == ComponentKeyPathType.Directory).ToList())
26 {
27 if (!createFolderSymbolsByComponentRef.Contains(componentSymbol.Id.Id))
28 {
29 this.Section.AddSymbol(new CreateFolderSymbol(componentSymbol.SourceLineNumbers)
30 {
31 DirectoryRef = componentSymbol.DirectoryRef,
32 ComponentRef = componentSymbol.Id.Id,
33 });
34 }
35 }
36 }
37 }
38} \ 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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using WixToolset.Data;
9 using WixToolset.Data.Symbols;
10 using WixToolset.Data.WindowsInstaller;
11
12 /// <summary>
13 /// Add referenced standard directory symbols, if not already present.
14 /// </summary>
15 internal class AddRequiredStandardDirectories
16 {
17 internal AddRequiredStandardDirectories(IntermediateSection section, Platform platform)
18 {
19 this.Section = section;
20 this.Platform = platform;
21 }
22
23 private IntermediateSection Section { get; }
24
25 private Platform Platform { get; }
26
27 public void Execute()
28 {
29 var directories = this.Section.Symbols.OfType<DirectorySymbol>().ToList();
30 var directoryIds = new SortedSet<string>(directories.Select(d => d.Id.Id));
31
32 foreach (var directory in directories)
33 {
34 var parentDirectoryId = directory.ParentDirectoryRef;
35
36 if (String.IsNullOrEmpty(parentDirectoryId))
37 {
38 if (directory.Id.Id != "TARGETDIR")
39 {
40 directory.ParentDirectoryRef = "TARGETDIR";
41 }
42 }
43 else
44 {
45 this.EnsureStandardDirectoryAdded(directoryIds, parentDirectoryId, directory.SourceLineNumbers);
46 }
47 }
48
49 if (!directoryIds.Contains("TARGETDIR") && WindowsInstallerStandard.TryGetStandardDirectory("TARGETDIR", out var targetDir))
50 {
51 directoryIds.Add(targetDir.Id.Id);
52 this.Section.AddSymbol(targetDir);
53 }
54 }
55
56 private void EnsureStandardDirectoryAdded(ISet<string> directoryIds, string directoryId, SourceLineNumber sourceLineNumbers)
57 {
58 if (!directoryIds.Contains(directoryId) && WindowsInstallerStandard.TryGetStandardDirectory(directoryId, out var standardDirectory))
59 {
60 var parentDirectoryId = this.GetStandardDirectoryParent(directoryId);
61
62 var directory = new DirectorySymbol(sourceLineNumbers, standardDirectory.Id)
63 {
64 Name = standardDirectory.Name,
65 ParentDirectoryRef = parentDirectoryId,
66 };
67
68 directoryIds.Add(directory.Id.Id);
69 this.Section.AddSymbol(directory);
70
71 if (!String.IsNullOrEmpty(parentDirectoryId))
72 {
73 this.EnsureStandardDirectoryAdded(directoryIds, parentDirectoryId, sourceLineNumbers);
74 }
75 }
76 }
77
78 private string GetStandardDirectoryParent(string directoryId)
79 {
80 switch (directoryId)
81 {
82 case "TARGETDIR":
83 return null;
84
85 case "CommonFiles6432Folder":
86 case "ProgramFiles6432Folder":
87 case "System6432Folder":
88 return WindowsInstallerStandard.GetPlatformSpecificDirectoryId(directoryId, this.Platform);
89
90 default:
91 return "TARGETDIR";
92 }
93 }
94 }
95}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Text;
7
8 internal class AssemblyName
9 {
10 public AssemblyName(string name, string culture, string version, string fileVersion, string architecture, string publicKeyToken, string type)
11 {
12 this.Name = name;
13 this.Culture = culture ?? "neutral";
14 this.Version = version;
15 this.FileVersion = fileVersion;
16 this.Architecture = architecture;
17
18 this.StrongNamedSigned = !String.IsNullOrEmpty(publicKeyToken);
19 this.PublicKeyToken = publicKeyToken;
20 this.Type = type;
21 }
22
23 public string Name { get; }
24
25 public string Culture { get; }
26
27 public string Version { get; }
28
29 public string FileVersion { get; }
30
31 public string Architecture { get; }
32
33 public string PublicKeyToken { get; }
34
35 public bool StrongNamedSigned { get; }
36
37 public string Type { get; }
38
39 public string GetFullName()
40 {
41 var assemblyName = new StringBuilder();
42
43 assemblyName.Append(this.Name);
44 assemblyName.Append(", Version=");
45 assemblyName.Append(this.Version);
46 assemblyName.Append(", Culture=");
47 assemblyName.Append(this.Culture);
48 assemblyName.Append(", PublicKeyToken=");
49 assemblyName.Append(this.PublicKeyToken ?? "null");
50
51 if (!String.IsNullOrEmpty(this.Architecture))
52 {
53 assemblyName.Append(", ProcessorArchitecture=");
54 assemblyName.Append(this.Architecture);
55 }
56
57 return assemblyName.ToString();
58 }
59 }
60}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.IO;
7 using System.Reflection.Metadata;
8 using System.Reflection.PortableExecutable;
9 using System.Security.Cryptography;
10 using System.Text;
11 using System.Xml;
12 using System.Xml.XPath;
13 using WixToolset.Data;
14
15 internal static class AssemblyNameReader
16 {
17 public static AssemblyName ReadAssembly(SourceLineNumber sourceLineNumbers, string assemblyPath, string fileVersion)
18 {
19 try
20 {
21 using (var stream = File.OpenRead(assemblyPath))
22 using (var peReader = new PEReader(stream))
23 {
24 var reader = peReader.GetMetadataReader();
25 var headers = peReader.PEHeaders;
26
27 var assembly = reader.GetAssemblyDefinition();
28 var attributes = assembly.GetCustomAttributes();
29
30 var name = ReadString(reader, assembly.Name);
31 var culture = ReadString(reader, assembly.Culture);
32 var architecture = ArchitectureFromHeaders(headers);
33 var version = assembly.Version.ToString();
34 var publicKeyToken = ReadPublicKeyToken(reader, assembly.PublicKey);
35
36 // There is a bug in v1 fusion that requires the assembly's "version" attribute
37 // to be equal to or longer than the "fileVersion" in length when its present;
38 // the workaround is to prepend zeroes to the last version number in the assembly
39 // version.
40 var targetNetfx1 = (headers.CorHeader.MajorRuntimeVersion == 2) && (headers.CorHeader.MinorRuntimeVersion == 0);
41 if (targetNetfx1 && !String.IsNullOrEmpty(fileVersion) && fileVersion.Length > version.Length)
42 {
43 var versionParts = version.Split('.');
44
45 if (versionParts.Length > 0)
46 {
47 var padding = new string('0', fileVersion.Length - version.Length);
48
49 versionParts[versionParts.Length - 1] = String.Concat(padding, versionParts[versionParts.Length - 1]);
50 version = String.Join(".", versionParts);
51 }
52 }
53
54 return new AssemblyName(name, culture, version, fileVersion, architecture, publicKeyToken, null);
55 }
56 }
57 catch (Exception e) when (e is FileNotFoundException || e is BadImageFormatException || e is InvalidOperationException)
58 {
59 throw new WixException(ErrorMessages.InvalidAssemblyFile(sourceLineNumbers, assemblyPath, $"{e.GetType().Name}: {e.Message}"));
60 }
61 }
62
63 public static AssemblyName ReadAssemblyManifest(SourceLineNumber sourceLineNumbers, string manifestPath)
64 {
65 string win32Type = null;
66 string win32Name = null;
67 string win32Version = null;
68 string win32ProcessorArchitecture = null;
69 string win32PublicKeyToken = null;
70
71 // Loading the dom is expensive we want more performant APIs than the DOM
72 // Navigator is cheaper than dom. Perhaps there is a cheaper API still.
73 try
74 {
75 var doc = new XPathDocument(manifestPath);
76 var nav = doc.CreateNavigator();
77 nav.MoveToRoot();
78
79 // This assumes a particular schema for a win32 manifest and does not
80 // provide error checking if the file does not conform to schema.
81 // The fallback case here is that nothing is added to the MsiAssemblyName
82 // table for an out of tolerance Win32 manifest. Perhaps warnings needed.
83 if (nav.MoveToFirstChild())
84 {
85 while (nav.NodeType != XPathNodeType.Element || nav.Name != "assembly")
86 {
87 nav.MoveToNext();
88 }
89
90 if (nav.MoveToFirstChild())
91 {
92 var hasNextSibling = true;
93 while (nav.NodeType != XPathNodeType.Element || nav.Name != "assemblyIdentity" && hasNextSibling)
94 {
95 hasNextSibling = nav.MoveToNext();
96 }
97
98 if (!hasNextSibling)
99 {
100 throw new WixException(ErrorMessages.InvalidManifestContent(sourceLineNumbers, manifestPath));
101 }
102
103 if (nav.MoveToAttribute("type", String.Empty))
104 {
105 win32Type = nav.Value;
106 nav.MoveToParent();
107 }
108
109 if (nav.MoveToAttribute("name", String.Empty))
110 {
111 win32Name = nav.Value;
112 nav.MoveToParent();
113 }
114
115 if (nav.MoveToAttribute("version", String.Empty))
116 {
117 win32Version = nav.Value;
118 nav.MoveToParent();
119 }
120
121 if (nav.MoveToAttribute("processorArchitecture", String.Empty))
122 {
123 win32ProcessorArchitecture = nav.Value;
124 nav.MoveToParent();
125 }
126
127 if (nav.MoveToAttribute("publicKeyToken", String.Empty))
128 {
129 win32PublicKeyToken = nav.Value;
130 nav.MoveToParent();
131 }
132 }
133 }
134 }
135 catch (FileNotFoundException fe)
136 {
137 throw new WixException(ErrorMessages.FileNotFound(sourceLineNumbers, fe.FileName, "AssemblyManifest"));
138 }
139 catch (XmlException xe)
140 {
141 throw new WixException(ErrorMessages.InvalidXml(sourceLineNumbers, "manifest", xe.Message));
142 }
143
144 return new AssemblyName(win32Name, null, win32Version, null, win32ProcessorArchitecture, win32PublicKeyToken, win32Type);
145 }
146
147 private static string ArchitectureFromHeaders(PEHeaders headers)
148 {
149 if (headers.PEHeader.Magic == PEMagic.PE32Plus)
150 {
151 return "AMD64";
152 }
153 else if ((headers.CorHeader.Flags & CorFlags.Requires32Bit) == CorFlags.Requires32Bit)
154 {
155 return "x86";
156 }
157 else if ((headers.CorHeader.Flags & CorFlags.ILOnly) == CorFlags.ILOnly)
158 {
159 return "MSIL";
160 }
161 else
162 {
163 // We return "x86" here because that seems to best match the Fusion-based
164 // GetAssemblyIdentityFromFile() method of acquiring the assembly identity.
165 return "x86";
166 }
167 }
168
169 private static string ReadString(MetadataReader reader, StringHandle handle)
170 {
171 return handle.IsNil ? null : reader.GetString(handle);
172 }
173
174 private static string ReadPublicKeyToken(MetadataReader reader, BlobHandle handle)
175 {
176 if (handle.IsNil)
177 {
178 return null;
179 }
180
181 var bytes = reader.GetBlobBytes(handle);
182 if (bytes.Length == 0)
183 {
184 return null;
185 }
186
187 var result = new StringBuilder();
188
189 // If we have the full public key, calculate the public key token from the
190 // last 8 bytes (in reverse order) of the public key's SHA1 hash.
191 if (bytes.Length > 8)
192 {
193 using (var sha1 = SHA1.Create())
194 {
195 var hash = sha1.ComputeHash(bytes);
196
197 for (var i = 1; i <= 8; ++i)
198 {
199 result.Append(hash[hash.Length - i].ToString("X2"));
200 }
201 }
202 }
203 else
204 {
205 foreach (var b in bytes)
206 {
207 result.Append(b.ToString("X2"));
208 }
209 }
210
211 return result.ToString();
212 }
213 }
214}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.Linq;
9 using WixToolset.Data;
10 using WixToolset.Data.Symbols;
11 using WixToolset.Extensibility.Data;
12 using WixToolset.Extensibility.Services;
13
14 /// <summary>
15 /// AssignMediaCommand assigns files to cabs based on Media or MediaTemplate rows.
16 /// </summary>
17 internal class AssignMediaCommand
18 {
19 private const int DefaultMaximumUncompressedMediaSize = 200; // Default value is 200 MB
20
21 public AssignMediaCommand(IntermediateSection section, IMessaging messaging, IEnumerable<IFileFacade> fileFacades, bool compressed)
22 {
23 this.CabinetNameTemplate = "Cab{0}.cab";
24 this.Section = section;
25 this.Messaging = messaging;
26 this.FileFacades = fileFacades;
27 this.FilesCompressed = compressed;
28 }
29
30 private IntermediateSection Section { get; }
31
32 private IMessaging Messaging { get; }
33
34 private IEnumerable<IFileFacade> FileFacades { get; }
35
36 private bool FilesCompressed { get; }
37
38 private string CabinetNameTemplate { get; set; }
39
40 /// <summary>
41 /// Gets cabinets with their file rows.
42 /// </summary>
43 public Dictionary<MediaSymbol, IEnumerable<IFileFacade>> FileFacadesByCabinetMedia { get; private set; }
44
45 /// <summary>
46 /// Get uncompressed file rows. This will contain file rows of File elements that are marked with compression=no.
47 /// This contains all the files when Package element is marked with compression=no
48 /// </summary>
49 public IEnumerable<IFileFacade> UncompressedFileFacades { get; private set; }
50
51 public void Execute()
52 {
53 var mediaSymbols = this.Section.Symbols.OfType<MediaSymbol>().ToList();
54 var mediaTemplateSymbols = this.Section.Symbols.OfType<WixMediaTemplateSymbol>().ToList();
55
56 // If both symbols are authored, it is an error.
57 if (mediaTemplateSymbols.Count > 0 && mediaSymbols.Count > 1)
58 {
59 throw new WixException(ErrorMessages.MediaTableCollision(null));
60 }
61
62 // If neither symbol is authored, default to a media template.
63 if (SectionType.Product == this.Section.Type && mediaTemplateSymbols.Count == 0 && mediaSymbols.Count == 0)
64 {
65 var mediaTemplate = new WixMediaTemplateSymbol()
66 {
67 CabinetTemplate = "cab{0}.cab",
68 };
69
70 this.Section.AddSymbol(mediaTemplate);
71 mediaTemplateSymbols.Add(mediaTemplate);
72 }
73
74 // When building merge module, all the files go to "#MergeModule.CABinet".
75 if (SectionType.Module == this.Section.Type)
76 {
77 var mergeModuleMediaSymbol = this.Section.AddSymbol(new MediaSymbol
78 {
79 Cabinet = "#MergeModule.CABinet",
80 });
81
82 this.FileFacadesByCabinetMedia = new Dictionary<MediaSymbol, IEnumerable<IFileFacade>>
83 {
84 { mergeModuleMediaSymbol, this.FileFacades }
85 };
86
87 this.UncompressedFileFacades = Array.Empty<IFileFacade>();
88 }
89 else
90 {
91 var filesByCabinetMedia = new Dictionary<MediaSymbol, List<IFileFacade>>();
92 var uncompressedFiles = new List<IFileFacade>();
93
94 if (mediaTemplateSymbols.Count > 0)
95 {
96 this.AutoAssignFiles(mediaTemplateSymbols, mediaSymbols, filesByCabinetMedia, uncompressedFiles);
97 }
98 else
99 {
100 this.ManuallyAssignFiles(mediaSymbols, filesByCabinetMedia, uncompressedFiles);
101 }
102
103 this.FileFacadesByCabinetMedia = filesByCabinetMedia.ToDictionary(kvp => kvp.Key, kvp => (IEnumerable<IFileFacade>)kvp.Value);
104
105 this.UncompressedFileFacades = uncompressedFiles;
106 }
107 }
108
109 /// <summary>
110 /// Assign files to cabinets based on MediaTemplate authoring.
111 /// </summary>
112 private void AutoAssignFiles(List<WixMediaTemplateSymbol> mediaTemplateTable, List<MediaSymbol> mediaSymbols, Dictionary<MediaSymbol, List<IFileFacade>> filesByCabinetMedia, List<IFileFacade> uncompressedFiles)
113 {
114 const int MaxCabIndex = 999;
115
116 ulong currentPreCabSize = 0;
117 ulong maxPreCabSizeInBytes;
118 var maxPreCabSizeInMB = 0;
119 var currentCabIndex = 0;
120
121 MediaSymbol currentMediaRow = null;
122
123 // Remove all previous media symbols since they will be replaced with
124 // media template.
125 foreach (var mediaSymbol in mediaSymbols)
126 {
127 this.Section.RemoveSymbol(mediaSymbol);
128 }
129
130 // Auto assign files to cabinets based on maximum uncompressed media size
131 var mediaTemplateRow = mediaTemplateTable.Single();
132
133 if (!String.IsNullOrEmpty(mediaTemplateRow.CabinetTemplate))
134 {
135 this.CabinetNameTemplate = mediaTemplateRow.CabinetTemplate;
136 }
137
138 var mumsString = Environment.GetEnvironmentVariable("WIX_MUMS");
139
140 try
141 {
142 // Override authored mums value if environment variable is authored.
143 if (!String.IsNullOrEmpty(mumsString))
144 {
145 maxPreCabSizeInMB = Int32.Parse(mumsString);
146 }
147 else
148 {
149 maxPreCabSizeInMB = mediaTemplateRow.MaximumUncompressedMediaSize ?? DefaultMaximumUncompressedMediaSize;
150 }
151
152 maxPreCabSizeInBytes = (ulong)maxPreCabSizeInMB * 1024 * 1024;
153 }
154 catch (FormatException)
155 {
156 throw new WixException(ErrorMessages.IllegalEnvironmentVariable("WIX_MUMS", mumsString));
157 }
158 catch (OverflowException)
159 {
160 throw new WixException(ErrorMessages.MaximumUncompressedMediaSizeTooLarge(null, maxPreCabSizeInMB));
161 }
162
163 var mediaSymbolsByDiskId = new Dictionary<int, MediaSymbol>();
164
165 foreach (var facade in this.FileFacades)
166 {
167 // When building a product, if the current file is not to be compressed or if
168 // the package set not to be compressed, don't cab it.
169 if (SectionType.Product == this.Section.Type && (facade.Uncompressed || !this.FilesCompressed))
170 {
171 uncompressedFiles.Add(facade);
172 continue;
173 }
174
175 if (currentCabIndex == MaxCabIndex)
176 {
177 // Associate current file with last cab (irrespective of the size) and cab index is not incremented anymore.
178 }
179 else
180 {
181 // Update current cab size.
182 currentPreCabSize += (ulong)facade.FileSize;
183
184 // Overflow due to current file
185 if (currentPreCabSize > maxPreCabSizeInBytes)
186 {
187 currentMediaRow = this.AddMediaSymbol(mediaTemplateRow, ++currentCabIndex);
188 mediaSymbolsByDiskId.Add(currentMediaRow.DiskId, currentMediaRow);
189 filesByCabinetMedia.Add(currentMediaRow, new List<IFileFacade>());
190
191 // Now files larger than MaxUncompressedMediaSize will be the only file in its cabinet so as to respect MaxUncompressedMediaSize
192 currentPreCabSize = (ulong)facade.FileSize;
193 }
194 else // file fits in the current cab.
195 {
196 if (currentMediaRow == null)
197 {
198 // Create new cab and MediaRow
199 currentMediaRow = this.AddMediaSymbol(mediaTemplateRow, ++currentCabIndex);
200 mediaSymbolsByDiskId.Add(currentMediaRow.DiskId, currentMediaRow);
201 filesByCabinetMedia.Add(currentMediaRow, new List<IFileFacade>());
202 }
203 }
204 }
205
206 // Associate current file with current cab.
207 var cabinetFiles = filesByCabinetMedia[currentMediaRow];
208 facade.DiskId = currentCabIndex;
209 cabinetFiles.Add(facade);
210 }
211
212 // If there are uncompressed files and no MediaRow, create a default one.
213 if (uncompressedFiles.Count > 0 && mediaSymbolsByDiskId.Count == 0)
214 {
215 var defaultMediaRow = this.Section.AddSymbol(new MediaSymbol(null, new Identifier(AccessModifier.Section, 1))
216 {
217 DiskId = 1,
218 });
219
220 mediaSymbolsByDiskId.Add(1, defaultMediaRow);
221 }
222 }
223
224 /// <summary>
225 /// Assign files to cabinets based on Media authoring.
226 /// </summary>
227 private void ManuallyAssignFiles(List<MediaSymbol> mediaSymbols, Dictionary<MediaSymbol, List<IFileFacade>> filesByCabinetMedia, List<IFileFacade> uncompressedFiles)
228 {
229 var mediaSymbolsByDiskId = new Dictionary<int, MediaSymbol>();
230
231 if (mediaSymbols.Any())
232 {
233 var cabinetMediaSymbols = new Dictionary<string, MediaSymbol>(StringComparer.OrdinalIgnoreCase);
234 foreach (var mediaSymbol in mediaSymbols)
235 {
236 // If the Media row has a cabinet, make sure it is unique across all Media rows.
237 if (!String.IsNullOrEmpty(mediaSymbol.Cabinet))
238 {
239 if (cabinetMediaSymbols.TryGetValue(mediaSymbol.Cabinet, out var existingRow))
240 {
241 this.Messaging.Write(ErrorMessages.DuplicateCabinetName(mediaSymbol.SourceLineNumbers, mediaSymbol.Cabinet));
242 this.Messaging.Write(ErrorMessages.DuplicateCabinetName2(existingRow.SourceLineNumbers, existingRow.Cabinet));
243 }
244 else
245 {
246 cabinetMediaSymbols.Add(mediaSymbol.Cabinet, mediaSymbol);
247 }
248
249 filesByCabinetMedia.Add(mediaSymbol, new List<IFileFacade>());
250 }
251
252 mediaSymbolsByDiskId.Add(mediaSymbol.DiskId, mediaSymbol);
253 }
254 }
255
256 foreach (var facade in this.FileFacades)
257 {
258 if (!mediaSymbolsByDiskId.TryGetValue(facade.DiskId, out var mediaSymbol))
259 {
260 this.Messaging.Write(ErrorMessages.MissingMedia(facade.SourceLineNumber, facade.DiskId));
261 continue;
262 }
263
264 // When building a product, if the current file is to be uncompressed or if
265 // the package set not to be compressed, don't cab it.
266 var compressed = facade.Compressed;
267 var uncompressed = facade.Uncompressed;
268 if (SectionType.Product == this.Section.Type && (uncompressed || (!compressed && !this.FilesCompressed)))
269 {
270 uncompressedFiles.Add(facade);
271 }
272 else // file is marked compressed.
273 {
274 if (filesByCabinetMedia.TryGetValue(mediaSymbol, out var cabinetFiles))
275 {
276 cabinetFiles.Add(facade);
277 }
278 else
279 {
280 this.Messaging.Write(ErrorMessages.ExpectedMediaCabinet(facade.SourceLineNumber, facade.Id, facade.DiskId));
281 }
282 }
283 }
284 }
285
286 /// <summary>
287 /// Adds a symbol to the section with cab name template filled in.
288 /// </summary>
289 /// <param name="mediaTemplateSymbol"></param>
290 /// <param name="cabIndex"></param>
291 /// <returns></returns>
292 private MediaSymbol AddMediaSymbol(WixMediaTemplateSymbol mediaTemplateSymbol, int cabIndex)
293 {
294 return this.Section.AddSymbol(new MediaSymbol(mediaTemplateSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, cabIndex))
295 {
296 DiskId = cabIndex,
297 Cabinet = String.Format(CultureInfo.InvariantCulture, this.CabinetNameTemplate, cabIndex),
298 CompressionLevel = mediaTemplateSymbol.CompressionLevel,
299 });
300 }
301 }
302}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.Linq;
9 using System.Text.RegularExpressions;
10 using WixToolset.Core.Native.Msi;
11 using WixToolset.Data;
12 using WixToolset.Data.Symbols;
13 using WixToolset.Data.WindowsInstaller;
14 using WixToolset.Data.WindowsInstaller.Rows;
15 using WixToolset.Extensibility.Services;
16
17 /// <summary>
18 /// Include transforms in a patch.
19 /// </summary>
20 internal class AttachPatchTransformsCommand
21 {
22 private static readonly string[] PatchUninstallBreakingTables = new[]
23 {
24 "AppId",
25 "BindImage",
26 "Class",
27 "Complus",
28 "CreateFolder",
29 "DuplicateFile",
30 "Environment",
31 "Extension",
32 "Font",
33 "IniFile",
34 "IsolatedComponent",
35 "LockPermissions",
36 "MIME",
37 "MoveFile",
38 "MsiLockPermissionsEx",
39 "MsiServiceConfig",
40 "MsiServiceConfigFailureActions",
41 "ODBCAttribute",
42 "ODBCDataSource",
43 "ODBCDriver",
44 "ODBCSourceAttribute",
45 "ODBCTranslator",
46 "ProgId",
47 "PublishComponent",
48 "RemoveIniFile",
49 "SelfReg",
50 "ServiceControl",
51 "ServiceInstall",
52 "TypeLib",
53 "Verb",
54 };
55
56 private readonly TableDefinitionCollection tableDefinitions;
57
58 public AttachPatchTransformsCommand(IMessaging messaging, IBackendHelper backendHelper, Intermediate intermediate, IEnumerable<PatchTransform> transforms)
59 {
60 this.tableDefinitions = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All);
61 this.Messaging = messaging;
62 this.BackendHelper = backendHelper;
63 this.Intermediate = intermediate;
64 this.Transforms = transforms;
65 }
66
67 private IMessaging Messaging { get; }
68
69 private IBackendHelper BackendHelper { get; }
70
71 private Intermediate Intermediate { get; }
72
73 private IEnumerable<PatchTransform> Transforms { get; }
74
75 public IEnumerable<SubStorage> SubStorages { get; private set; }
76
77 public IEnumerable<SubStorage> Execute()
78 {
79 var subStorages = new List<SubStorage>();
80
81 if (this.Transforms == null || !this.Transforms.Any())
82 {
83 this.Messaging.Write(ErrorMessages.PatchWithoutTransforms());
84 return subStorages;
85 }
86
87 var summaryInfo = this.ExtractPatchSummaryInfo();
88
89 var section = this.Intermediate.Sections.First();
90
91 var symbols = this.Intermediate.Sections.SelectMany(s => s.Symbols).ToList();
92
93 // Get the patch id from the WixPatchId symbol.
94 var patchSymbol = symbols.OfType<WixPatchSymbol>().FirstOrDefault();
95
96 if (String.IsNullOrEmpty(patchSymbol.Id?.Id))
97 {
98 this.Messaging.Write(ErrorMessages.ExpectedPatchIdInWixMsp());
99 return subStorages;
100 }
101
102 if (String.IsNullOrEmpty(patchSymbol.ClientPatchId))
103 {
104 this.Messaging.Write(ErrorMessages.ExpectedClientPatchIdInWixMsp());
105 return subStorages;
106 }
107
108 // enumerate patch.Media to map diskId to Media row
109 var patchMediaByDiskId = symbols.OfType<MediaSymbol>().ToDictionary(t => t.DiskId);
110
111 if (patchMediaByDiskId.Count == 0)
112 {
113 this.Messaging.Write(ErrorMessages.ExpectedMediaRowsInWixMsp());
114 return subStorages;
115 }
116
117 // populate MSP summary information
118 var patchMetadata = this.PopulateSummaryInformation(summaryInfo, symbols, patchSymbol);
119
120 // enumerate transforms
121 var productCodes = new SortedSet<string>();
122 var transformNames = new List<string>();
123 var validTransform = new List<Tuple<string, WindowsInstallerData>>();
124
125 var baselineSymbolsById = symbols.OfType<WixPatchBaselineSymbol>().ToDictionary(t => t.Id.Id);
126
127 foreach (var mainTransform in this.Transforms)
128 {
129 var baselineSymbol = baselineSymbolsById[mainTransform.Baseline];
130
131 var patchRefSymbols = symbols.OfType<WixPatchRefSymbol>().ToList();
132 if (patchRefSymbols.Count > 0)
133 {
134 if (!this.ReduceTransform(mainTransform.Transform, patchRefSymbols))
135 {
136 // transform has none of the content authored into this patch
137 continue;
138 }
139 }
140
141 // Validate the transform doesn't break any patch specific rules.
142 this.Validate(mainTransform);
143
144 // ensure consistent File.Sequence within each Media
145 var mediaSymbol = patchMediaByDiskId[baselineSymbol.DiskId];
146
147 // Ensure that files are sequenced after the last file in any transform.
148 var transformMediaTable = mainTransform.Transform.Tables["Media"];
149 if (null != transformMediaTable && 0 < transformMediaTable.Rows.Count)
150 {
151 foreach (MediaRow transformMediaRow in transformMediaTable.Rows)
152 {
153 if (!mediaSymbol.LastSequence.HasValue || mediaSymbol.LastSequence < transformMediaRow.LastSequence)
154 {
155 // The Binder will pre-increment the sequence.
156 mediaSymbol.LastSequence = transformMediaRow.LastSequence;
157 }
158 }
159 }
160
161 // Use the Media/@DiskId if greater than the last sequence for backward compatibility.
162 if (!mediaSymbol.LastSequence.HasValue || mediaSymbol.LastSequence < mediaSymbol.DiskId)
163 {
164 mediaSymbol.LastSequence = mediaSymbol.DiskId;
165 }
166
167 // Ignore media table in the transform.
168 mainTransform.Transform.Tables.Remove("Media");
169 mainTransform.Transform.Tables.Remove("MsiDigitalSignature");
170
171 var pairedTransform = this.BuildPairedTransform(summaryInfo, patchMetadata, patchSymbol, mainTransform.Transform, mediaSymbol, baselineSymbol, out var productCode);
172
173 productCode = productCode.ToUpperInvariant();
174 productCodes.Add(productCode);
175 validTransform.Add(Tuple.Create(productCode, mainTransform.Transform));
176
177 // attach these transforms to the patch object
178 // TODO: is this an acceptable way to auto-generate transform stream names?
179 var transformName = mainTransform.Baseline + "." + validTransform.Count.ToString(CultureInfo.InvariantCulture);
180 subStorages.Add(new SubStorage(transformName, mainTransform.Transform));
181 subStorages.Add(new SubStorage("#" + transformName, pairedTransform));
182
183 transformNames.Add(":" + transformName);
184 transformNames.Add(":#" + transformName);
185 }
186
187 if (validTransform.Count == 0)
188 {
189 this.Messaging.Write(ErrorMessages.PatchWithoutValidTransforms());
190 return subStorages;
191 }
192
193 // Validate that a patch authored as removable is actually removable
194 if (patchMetadata.TryGetValue("AllowRemoval", out var allowRemoval) && allowRemoval.Value == "1")
195 {
196 var uninstallable = true;
197
198 foreach (var entry in validTransform)
199 {
200 uninstallable &= this.CheckUninstallableTransform(entry.Item1, entry.Item2);
201 }
202
203 if (!uninstallable)
204 {
205 this.Messaging.Write(ErrorMessages.PatchNotRemovable());
206 return subStorages;
207 }
208 }
209
210 // Finish filling tables with transform-dependent data.
211 productCodes = FinalizePatchProductCodes(symbols, productCodes);
212
213 // Semicolon delimited list of the product codes that can accept the patch.
214 summaryInfo.Add(SummaryInformationType.PatchProductCodes, new SummaryInformationSymbol(patchSymbol.SourceLineNumbers)
215 {
216 PropertyId = SummaryInformationType.PatchProductCodes,
217 Value = String.Join(";", productCodes)
218 });
219
220 // Semicolon delimited list of transform substorage names in the order they are applied.
221 summaryInfo.Add(SummaryInformationType.TransformNames, new SummaryInformationSymbol(patchSymbol.SourceLineNumbers)
222 {
223 PropertyId = SummaryInformationType.TransformNames,
224 Value = String.Join(";", transformNames)
225 });
226
227 // Put the summary information that was extracted back in now that it is updated.
228 foreach (var readSummaryInfo in summaryInfo.Values.OrderBy(s => s.PropertyId))
229 {
230 section.AddSymbol(readSummaryInfo);
231 }
232
233 this.SubStorages = subStorages;
234
235 return subStorages;
236 }
237
238 private Dictionary<SummaryInformationType, SummaryInformationSymbol> ExtractPatchSummaryInfo()
239 {
240 var result = new Dictionary<SummaryInformationType, SummaryInformationSymbol>();
241
242 foreach (var section in this.Intermediate.Sections)
243 {
244 // Remove all summary information from the symbols and remember those that
245 // are not calculated or reserved.
246 foreach (var patchSummaryInfo in section.Symbols.OfType<SummaryInformationSymbol>().ToList())
247 {
248 section.RemoveSymbol(patchSummaryInfo);
249
250 if (patchSummaryInfo.PropertyId != SummaryInformationType.PatchProductCodes &&
251 patchSummaryInfo.PropertyId != SummaryInformationType.PatchCode &&
252 patchSummaryInfo.PropertyId != SummaryInformationType.PatchInstallerRequirement &&
253 patchSummaryInfo.PropertyId != SummaryInformationType.Reserved11 &&
254 patchSummaryInfo.PropertyId != SummaryInformationType.Reserved14 &&
255 patchSummaryInfo.PropertyId != SummaryInformationType.Reserved16)
256 {
257 result.Add(patchSummaryInfo.PropertyId, patchSummaryInfo);
258 }
259 }
260 }
261
262 return result;
263 }
264
265 private Dictionary<string, MsiPatchMetadataSymbol> PopulateSummaryInformation(Dictionary<SummaryInformationType, SummaryInformationSymbol> summaryInfo, List<IntermediateSymbol> symbols, WixPatchSymbol patchSymbol)
266 {
267 // PID_CODEPAGE
268 if (!summaryInfo.ContainsKey(SummaryInformationType.Codepage))
269 {
270 // Set the code page by default to the same code page for the
271 // string pool in the database.
272 AddSummaryInformation(SummaryInformationType.Codepage, patchSymbol.Codepage?.ToString(CultureInfo.InvariantCulture) ?? "0", patchSymbol.SourceLineNumbers);
273 }
274
275 // GUID patch code for the patch.
276 AddSummaryInformation(SummaryInformationType.PatchCode, patchSymbol.Id.Id, patchSymbol.SourceLineNumbers);
277
278 // Indicates the minimum Windows Installer version that is required to install the patch.
279 AddSummaryInformation(SummaryInformationType.PatchInstallerRequirement, ((int)SummaryInformation.InstallerRequirement.Version31).ToString(CultureInfo.InvariantCulture), patchSymbol.SourceLineNumbers);
280
281 if (!summaryInfo.ContainsKey(SummaryInformationType.Security))
282 {
283 AddSummaryInformation(SummaryInformationType.Security, "4", patchSymbol.SourceLineNumbers); // Read-only enforced;
284 }
285
286 // Use authored comments or default to display name.
287 MsiPatchMetadataSymbol commentsSymbol = null;
288
289 var metadataSymbols = symbols.OfType<MsiPatchMetadataSymbol>().Where(t => String.IsNullOrEmpty(t.Company)).ToDictionary(t => t.Property);
290
291 if (!summaryInfo.ContainsKey(SummaryInformationType.Title) &&
292 metadataSymbols.TryGetValue("DisplayName", out var displayName))
293 {
294 AddSummaryInformation(SummaryInformationType.Title, displayName.Value, displayName.SourceLineNumbers);
295
296 // Default comments to use display name as-is.
297 commentsSymbol = displayName;
298 }
299
300 // TODO: This code below seems unnecessary given the codepage is set at the top of this method.
301 //if (!summaryInfo.ContainsKey(SummaryInformationType.Codepage) &&
302 // metadataValues.TryGetValue("CodePage", out var codepage))
303 //{
304 // AddSummaryInformation(SummaryInformationType.Codepage, codepage);
305 //}
306
307 if (!summaryInfo.ContainsKey(SummaryInformationType.PatchPackageName) &&
308 metadataSymbols.TryGetValue("Description", out var description))
309 {
310 AddSummaryInformation(SummaryInformationType.PatchPackageName, description.Value, description.SourceLineNumbers);
311 }
312
313 if (!summaryInfo.ContainsKey(SummaryInformationType.Author) &&
314 metadataSymbols.TryGetValue("ManufacturerName", out var manufacturer))
315 {
316 AddSummaryInformation(SummaryInformationType.Author, manufacturer.Value, manufacturer.SourceLineNumbers);
317 }
318
319 // Special metadata marshalled through the build.
320 //var wixMetadataValues = symbols.OfType<WixPatchMetadataSymbol>().ToDictionary(t => t.Id.Id, t => t.Value);
321
322 //if (wixMetadataValues.TryGetValue("Comments", out var wixComments))
323 if (metadataSymbols.TryGetValue("Comments", out var wixComments))
324 {
325 commentsSymbol = wixComments;
326 }
327
328 // Write the package comments to summary info.
329 if (!summaryInfo.ContainsKey(SummaryInformationType.Comments) &&
330 commentsSymbol != null)
331 {
332 AddSummaryInformation(SummaryInformationType.Comments, commentsSymbol.Value, commentsSymbol.SourceLineNumbers);
333 }
334
335 return metadataSymbols;
336
337 void AddSummaryInformation(SummaryInformationType type, string value, SourceLineNumber sourceLineNumber)
338 {
339 summaryInfo.Add(type, new SummaryInformationSymbol(sourceLineNumber)
340 {
341 PropertyId = type,
342 Value = value
343 });
344 }
345 }
346
347 /// <summary>
348 /// Ensure transform is uninstallable.
349 /// </summary>
350 /// <param name="productCode">Product code in transform.</param>
351 /// <param name="transform">Transform generated by torch.</param>
352 /// <returns>True if the transform is uninstallable</returns>
353 private bool CheckUninstallableTransform(string productCode, WindowsInstallerData transform)
354 {
355 var success = true;
356
357 foreach (var tableName in PatchUninstallBreakingTables)
358 {
359 if (transform.TryGetTable(tableName, out var table))
360 {
361 foreach (var row in table.Rows)
362 {
363 if (row.Operation == RowOperation.Add)
364 {
365 success = false;
366
367 var primaryKey = row.GetPrimaryKey('/') ?? String.Empty;
368
369 this.Messaging.Write(ErrorMessages.NewRowAddedInTable(row.SourceLineNumbers, productCode, table.Name, primaryKey));
370 }
371 }
372 }
373 }
374
375 return success;
376 }
377
378 /// <summary>
379 /// Reduce the transform according to the patch references.
380 /// </summary>
381 /// <param name="transform">transform generated by torch.</param>
382 /// <param name="patchRefSymbols">Table contains patch family filter.</param>
383 /// <returns>true if the transform is not empty</returns>
384 private bool ReduceTransform(WindowsInstallerData transform, IEnumerable<WixPatchRefSymbol> patchRefSymbols)
385 {
386 // identify sections to keep
387 var oldSections = new Dictionary<string, Row>();
388 var newSections = new Dictionary<string, Row>();
389 var tableKeyRows = new Dictionary<string, Dictionary<string, Row>>();
390 var sequenceList = new List<Table>();
391 var componentFeatureAddsIndex = new Dictionary<string, List<string>>();
392 var customActionTable = new Dictionary<string, Row>();
393 var directoryTableAdds = new Dictionary<string, Row>();
394 var featureTableAdds = new Dictionary<string, Row>();
395 var keptComponents = new Dictionary<string, Row>();
396 var keptDirectories = new Dictionary<string, Row>();
397 var keptFeatures = new Dictionary<string, Row>();
398 var keptLockPermissions = new HashSet<string>();
399 var keptMsiLockPermissionExs = new HashSet<string>();
400
401 var componentCreateFolderIndex = new Dictionary<string, List<string>>();
402 var directoryLockPermissionsIndex = new Dictionary<string, List<Row>>();
403 var directoryMsiLockPermissionsExIndex = new Dictionary<string, List<Row>>();
404
405 foreach (var patchRefSymbol in patchRefSymbols)
406 {
407 var tableName = patchRefSymbol.Table;
408 var key = patchRefSymbol.PrimaryKeys;
409
410 // Short circuit filtering if all changes should be included.
411 if ("*" == tableName && "*" == key)
412 {
413 RemoveProductCodeFromTransform(transform);
414 return true;
415 }
416
417 if (!transform.Tables.TryGetTable(tableName, out var table))
418 {
419 // Table not found.
420 continue;
421 }
422
423 // Index the table.
424 if (!tableKeyRows.TryGetValue(tableName, out var keyRows))
425 {
426 keyRows = new Dictionary<string, Row>();
427 tableKeyRows.Add(tableName, keyRows);
428
429 foreach (var newRow in table.Rows)
430 {
431 var primaryKey = newRow.GetPrimaryKey();
432 keyRows.Add(primaryKey, newRow);
433 }
434 }
435
436 if (!keyRows.TryGetValue(key, out var row))
437 {
438 // Row not found.
439 continue;
440 }
441
442 // Differ.sectionDelimiter
443 var sections = row.SectionId.Split('/');
444 oldSections[sections[0]] = row;
445 newSections[sections[1]] = row;
446 }
447
448 // throw away sections not referenced
449 var keptRows = 0;
450 Table directoryTable = null;
451 Table featureTable = null;
452 Table lockPermissionsTable = null;
453 Table msiLockPermissionsTable = null;
454
455 foreach (var table in transform.Tables)
456 {
457 if ("_SummaryInformation" == table.Name)
458 {
459 continue;
460 }
461
462 if (table.Name == "AdminExecuteSequence"
463 || table.Name == "AdminUISequence"
464 || table.Name == "AdvtExecuteSequence"
465 || table.Name == "InstallUISequence"
466 || table.Name == "InstallExecuteSequence")
467 {
468 sequenceList.Add(table);
469 continue;
470 }
471
472 for (var i = 0; i < table.Rows.Count; i++)
473 {
474 var row = table.Rows[i];
475
476 if (table.Name == "CreateFolder")
477 {
478 var createFolderComponentId = row.FieldAsString(1);
479
480 if (!componentCreateFolderIndex.TryGetValue(createFolderComponentId, out var directoryList))
481 {
482 directoryList = new List<string>();
483 componentCreateFolderIndex.Add(createFolderComponentId, directoryList);
484 }
485
486 directoryList.Add(row.FieldAsString(0));
487 }
488
489 if (table.Name == "CustomAction")
490 {
491 customActionTable.Add(row.FieldAsString(0), row);
492 }
493
494 if (table.Name == "Directory")
495 {
496 directoryTable = table;
497 if (RowOperation.Add == row.Operation)
498 {
499 directoryTableAdds.Add(row.FieldAsString(0), row);
500 }
501 }
502
503 if (table.Name == "Feature")
504 {
505 featureTable = table;
506 if (RowOperation.Add == row.Operation)
507 {
508 featureTableAdds.Add(row.FieldAsString(0), row);
509 }
510 }
511
512 if (table.Name == "FeatureComponents")
513 {
514 if (RowOperation.Add == row.Operation)
515 {
516 var featureId = row.FieldAsString(0);
517 var componentId = row.FieldAsString(1);
518
519 if (!componentFeatureAddsIndex.TryGetValue(componentId, out var featureList))
520 {
521 featureList = new List<string>();
522 componentFeatureAddsIndex.Add(componentId, featureList);
523 }
524
525 featureList.Add(featureId);
526 }
527 }
528
529 if (table.Name == "LockPermissions")
530 {
531 lockPermissionsTable = table;
532 if ("CreateFolder" == row.FieldAsString(1))
533 {
534 var directoryId = row.FieldAsString(0);
535
536 if (!directoryLockPermissionsIndex.TryGetValue(directoryId, out var rowList))
537 {
538 rowList = new List<Row>();
539 directoryLockPermissionsIndex.Add(directoryId, rowList);
540 }
541
542 rowList.Add(row);
543 }
544 }
545
546 if (table.Name == "MsiLockPermissionsEx")
547 {
548 msiLockPermissionsTable = table;
549 if ("CreateFolder" == row.FieldAsString(1))
550 {
551 var directoryId = row.FieldAsString(0);
552
553 if (!directoryMsiLockPermissionsExIndex.TryGetValue(directoryId, out var rowList))
554 {
555 rowList = new List<Row>();
556 directoryMsiLockPermissionsExIndex.Add(directoryId, rowList);
557 }
558
559 rowList.Add(row);
560 }
561 }
562
563 if (null == row.SectionId)
564 {
565 table.Rows.RemoveAt(i);
566 i--;
567 }
568 else
569 {
570 var sections = row.SectionId.Split('/');
571 // ignore the row without section id.
572 if (0 == sections[0].Length && 0 == sections[1].Length)
573 {
574 table.Rows.RemoveAt(i);
575 i--;
576 }
577 else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections))
578 {
579 if ("Component" == table.Name)
580 {
581 keptComponents.Add(row.FieldAsString(0), row);
582 }
583
584 if ("Directory" == table.Name)
585 {
586 keptDirectories.Add(row.FieldAsString(0), row);
587 }
588
589 if ("Feature" == table.Name)
590 {
591 keptFeatures.Add(row.FieldAsString(0), row);
592 }
593
594 keptRows++;
595 }
596 else
597 {
598 table.Rows.RemoveAt(i);
599 i--;
600 }
601 }
602 }
603 }
604
605 keptRows += ReduceTransformSequenceTable(sequenceList, oldSections, newSections, customActionTable);
606
607 if (null != directoryTable)
608 {
609 foreach (var componentRow in keptComponents.Values)
610 {
611 var componentId = componentRow.FieldAsString(0);
612
613 if (RowOperation.Add == componentRow.Operation)
614 {
615 // Make sure each added component has its required directory and feature heirarchy.
616 var directoryId = componentRow.FieldAsString(2);
617 while (null != directoryId && directoryTableAdds.TryGetValue(directoryId, out var directoryRow))
618 {
619 if (!keptDirectories.ContainsKey(directoryId))
620 {
621 directoryTable.Rows.Add(directoryRow);
622 keptDirectories.Add(directoryId, directoryRow);
623 keptRows++;
624 }
625
626 directoryId = directoryRow.FieldAsString(1);
627 }
628
629 if (componentFeatureAddsIndex.TryGetValue(componentId, out var componentFeatureIds))
630 {
631 foreach (var featureId in componentFeatureIds)
632 {
633 var currentFeatureId = featureId;
634 while (null != currentFeatureId && featureTableAdds.TryGetValue(currentFeatureId, out var featureRow))
635 {
636 if (!keptFeatures.ContainsKey(currentFeatureId))
637 {
638 featureTable.Rows.Add(featureRow);
639 keptFeatures.Add(currentFeatureId, featureRow);
640 keptRows++;
641 }
642
643 currentFeatureId = featureRow.FieldAsString(1);
644 }
645 }
646 }
647 }
648
649 // Hook in changes LockPermissions and MsiLockPermissions for folders for each component that has been kept.
650 foreach (var keptComponentId in keptComponents.Keys)
651 {
652 if (componentCreateFolderIndex.TryGetValue(keptComponentId, out var directoryList))
653 {
654 foreach (var directoryId in directoryList)
655 {
656 if (directoryLockPermissionsIndex.TryGetValue(directoryId, out var lockPermissionsRowList))
657 {
658 foreach (var lockPermissionsRow in lockPermissionsRowList)
659 {
660 var key = lockPermissionsRow.GetPrimaryKey('/');
661 if (keptLockPermissions.Add(key))
662 {
663 lockPermissionsTable.Rows.Add(lockPermissionsRow);
664 keptRows++;
665 }
666 }
667 }
668
669 if (directoryMsiLockPermissionsExIndex.TryGetValue(directoryId, out var msiLockPermissionsExRowList))
670 {
671 foreach (var msiLockPermissionsExRow in msiLockPermissionsExRowList)
672 {
673 var key = msiLockPermissionsExRow.GetPrimaryKey('/');
674 if (keptMsiLockPermissionExs.Add(key))
675 {
676 msiLockPermissionsTable.Rows.Add(msiLockPermissionsExRow);
677 keptRows++;
678 }
679 }
680 }
681 }
682 }
683 }
684 }
685 }
686
687 keptRows += ReduceTransformSequenceTable(sequenceList, oldSections, newSections, customActionTable);
688
689 // Delete tables that are empty.
690 var tablesToDelete = transform.Tables.Where(t => t.Rows.Count == 0).Select(t => t.Name).ToList();
691
692 foreach (var tableName in tablesToDelete)
693 {
694 transform.Tables.Remove(tableName);
695 }
696
697 return keptRows > 0;
698 }
699
700 private void Validate(PatchTransform patchTransform)
701 {
702 var transformPath = patchTransform.Baseline; // TODO: this is used in error messages, how best to set it?
703 var transform = patchTransform.Transform;
704
705 // Changing the ProdocutCode in a patch transform is not recommended.
706 if (transform.TryGetTable("Property", out var propertyTable))
707 {
708 foreach (var row in propertyTable.Rows)
709 {
710 // Only interested in modified rows; fast check.
711 if (RowOperation.Modify == row.Operation &&
712 "ProductCode".Equals(row.FieldAsString(0), StringComparison.Ordinal))
713 {
714 this.Messaging.Write(WarningMessages.MajorUpgradePatchNotRecommended());
715 }
716 }
717 }
718
719 // If there is nothing in the component table we can return early because the remaining checks are component based.
720 if (!transform.TryGetTable("Component", out var componentTable))
721 {
722 return;
723 }
724
725 // Index Feature table row operations
726 var featureOps = new Dictionary<string, RowOperation>();
727 if (transform.TryGetTable("Feature", out var featureTable))
728 {
729 foreach (var row in featureTable.Rows)
730 {
731 featureOps[row.FieldAsString(0)] = row.Operation;
732 }
733 }
734
735 // Index Component table and check for keypath modifications
736 var componentKeyPath = new Dictionary<string, string>();
737 var deletedComponent = new Dictionary<string, Row>();
738 foreach (var row in componentTable.Rows)
739 {
740 var id = row.FieldAsString(0);
741 var keypath = row.FieldAsString(5) ?? String.Empty;
742
743 componentKeyPath.Add(id, keypath);
744
745 if (RowOperation.Delete == row.Operation)
746 {
747 deletedComponent.Add(id, row);
748 }
749 else if (RowOperation.Modify == row.Operation)
750 {
751 if (row.Fields[1].Modified)
752 {
753 // Changing the guid of a component is equal to deleting the old one and adding a new one.
754 deletedComponent.Add(id, row);
755 }
756
757 // If the keypath is modified its an error
758 if (row.Fields[5].Modified)
759 {
760 this.Messaging.Write(ErrorMessages.InvalidKeypathChange(row.SourceLineNumbers, id, transformPath));
761 }
762 }
763 }
764
765 // Verify changes in the file table
766 if (transform.TryGetTable("File", out var fileTable))
767 {
768 var componentWithChangedKeyPath = new Dictionary<string, string>();
769 foreach (FileRow row in fileTable.Rows)
770 {
771 if (RowOperation.None == row.Operation)
772 {
773 continue;
774 }
775
776 var fileId = row.File;
777 var componentId = row.Component;
778
779 // If this file is the keypath of a component
780 if (componentKeyPath.TryGetValue(componentId, out var keyPath) && keyPath.Equals(fileId, StringComparison.Ordinal))
781 {
782 if (row.Fields[2].Modified)
783 {
784 // You can't change the filename of a file that is the keypath of a component.
785 this.Messaging.Write(ErrorMessages.InvalidKeypathChange(row.SourceLineNumbers, componentId, transformPath));
786 }
787
788 if (!componentWithChangedKeyPath.ContainsKey(componentId))
789 {
790 componentWithChangedKeyPath.Add(componentId, fileId);
791 }
792 }
793
794 if (RowOperation.Delete == row.Operation)
795 {
796 // If the file is removed from a component that is not deleted.
797 if (!deletedComponent.ContainsKey(componentId))
798 {
799 var foundRemoveFileEntry = false;
800 var filename = this.BackendHelper.GetMsiFileName(row.FieldAsString(2), false, true);
801
802 if (transform.TryGetTable("RemoveFile", out var removeFileTable))
803 {
804 foreach (var removeFileRow in removeFileTable.Rows)
805 {
806 if (RowOperation.Delete == removeFileRow.Operation)
807 {
808 continue;
809 }
810
811 if (componentId == removeFileRow.FieldAsString(1))
812 {
813 // Check if there is a RemoveFile entry for this file
814 if (null != removeFileRow[2])
815 {
816 var removeFileName = this.BackendHelper.GetMsiFileName(removeFileRow.FieldAsString(2), false, true);
817
818 // Convert the MSI format for a wildcard string to Regex format.
819 removeFileName = removeFileName.Replace('.', '|').Replace('?', '.').Replace("*", ".*").Replace("|", "\\.");
820
821 var regex = new Regex(removeFileName, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
822 if (regex.IsMatch(filename))
823 {
824 foundRemoveFileEntry = true;
825 break;
826 }
827 }
828 }
829 }
830 }
831
832 if (!foundRemoveFileEntry)
833 {
834 this.Messaging.Write(WarningMessages.InvalidRemoveFile(row.SourceLineNumbers, fileId, componentId));
835 }
836 }
837 }
838 }
839 }
840
841 var featureComponentsTable = transform.Tables["FeatureComponents"];
842
843 if (0 < deletedComponent.Count)
844 {
845 // Index FeatureComponents table.
846 var featureComponents = new Dictionary<string, List<string>>();
847
848 if (null != featureComponentsTable)
849 {
850 foreach (var row in featureComponentsTable.Rows)
851 {
852 var componentId = row.FieldAsString(1);
853
854 if (!featureComponents.TryGetValue(componentId, out var features))
855 {
856 features = new List<string>();
857 featureComponents.Add(componentId, features);
858 }
859
860 features.Add(row.FieldAsString(0));
861 }
862 }
863
864 // Check to make sure if a component was deleted, the feature was too.
865 foreach (var entry in deletedComponent)
866 {
867 if (featureComponents.TryGetValue(entry.Key, out var features))
868 {
869 foreach (var featureId in features)
870 {
871 if (!featureOps.TryGetValue(featureId, out var op) || op != RowOperation.Delete)
872 {
873 // The feature was not deleted.
874 this.Messaging.Write(ErrorMessages.InvalidRemoveComponent(((Row)entry.Value).SourceLineNumbers, entry.Key.ToString(), featureId, transformPath));
875 }
876 }
877 }
878 }
879 }
880
881 // Warn if new components are added to existing features
882 if (null != featureComponentsTable)
883 {
884 foreach (var row in featureComponentsTable.Rows)
885 {
886 if (RowOperation.Add == row.Operation)
887 {
888 // Check if the feature is in the Feature table
889 var feature_ = row.FieldAsString(0);
890 var component_ = row.FieldAsString(1);
891
892 // Features may not be present if not referenced
893 if (!featureOps.ContainsKey(feature_) || RowOperation.Add != (RowOperation)featureOps[feature_])
894 {
895 this.Messaging.Write(WarningMessages.NewComponentAddedToExistingFeature(row.SourceLineNumbers, component_, feature_, transformPath));
896 }
897 }
898 }
899 }
900 }
901
902 /// <summary>
903 /// Remove the ProductCode property from the transform.
904 /// </summary>
905 /// <param name="transform">The transform.</param>
906 /// <remarks>
907 /// Changing the ProductCode is not supported in a patch.
908 /// </remarks>
909 private static void RemoveProductCodeFromTransform(WindowsInstallerData transform)
910 {
911 if (transform.Tables.TryGetTable("Property", out var propertyTable))
912 {
913 for (var i = 0; i < propertyTable.Rows.Count; ++i)
914 {
915 var propertyRow = propertyTable.Rows[i];
916 var property = (string)propertyRow[0];
917
918 if ("ProductCode" == property)
919 {
920 propertyTable.Rows.RemoveAt(i);
921 break;
922 }
923 }
924 }
925 }
926
927 /// <summary>
928 /// Check if the section is in a PatchFamily.
929 /// </summary>
930 /// <param name="oldSection">Section id in target wixout</param>
931 /// <param name="newSection">Section id in upgrade wixout</param>
932 /// <param name="oldSections">Dictionary contains section id should be kept in the baseline wixout.</param>
933 /// <param name="newSections">Dictionary contains section id should be kept in the upgrade wixout.</param>
934 /// <returns>true if section in patch family</returns>
935 private static bool IsInPatchFamily(string oldSection, string newSection, Dictionary<string, Row> oldSections, Dictionary<string, Row> newSections)
936 {
937 var result = false;
938
939 if ((String.IsNullOrEmpty(oldSection) && newSections.ContainsKey(newSection)) || (String.IsNullOrEmpty(newSection) && oldSections.ContainsKey(oldSection)))
940 {
941 result = true;
942 }
943 else if (!String.IsNullOrEmpty(oldSection) && !String.IsNullOrEmpty(newSection) && (oldSections.ContainsKey(oldSection) || newSections.ContainsKey(newSection)))
944 {
945 result = true;
946 }
947
948 return result;
949 }
950
951 /// <summary>
952 /// Reduce the transform sequence tables.
953 /// </summary>
954 /// <param name="sequenceList">ArrayList of tables to be reduced</param>
955 /// <param name="oldSections">Hashtable contains section id should be kept in the baseline wixout.</param>
956 /// <param name="newSections">Hashtable contains section id should be kept in the target wixout.</param>
957 /// <param name="customAction">Hashtable contains all the rows in the CustomAction table.</param>
958 /// <returns>Number of rows left</returns>
959 private static int ReduceTransformSequenceTable(List<Table> sequenceList, Dictionary<string, Row> oldSections, Dictionary<string, Row> newSections, Dictionary<string, Row> customAction)
960 {
961 var keptRows = 0;
962
963 foreach (var currentTable in sequenceList)
964 {
965 for (var i = 0; i < currentTable.Rows.Count; i++)
966 {
967 var row = currentTable.Rows[i];
968 var actionName = row.Fields[0].Data.ToString();
969 var sections = row.SectionId.Split('/');
970 var isSectionIdEmpty = (sections[0].Length == 0 && sections[1].Length == 0);
971
972 if (row.Operation == RowOperation.None)
973 {
974 // Ignore the rows without section id.
975 if (isSectionIdEmpty)
976 {
977 currentTable.Rows.RemoveAt(i);
978 i--;
979 }
980 else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections))
981 {
982 keptRows++;
983 }
984 else
985 {
986 currentTable.Rows.RemoveAt(i);
987 i--;
988 }
989 }
990 else if (row.Operation == RowOperation.Modify)
991 {
992 var sequenceChanged = row.Fields[2].Modified;
993 var conditionChanged = row.Fields[1].Modified;
994
995 if (sequenceChanged && !conditionChanged)
996 {
997 keptRows++;
998 }
999 else if (!sequenceChanged && conditionChanged)
1000 {
1001 if (isSectionIdEmpty)
1002 {
1003 currentTable.Rows.RemoveAt(i);
1004 i--;
1005 }
1006 else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections))
1007 {
1008 keptRows++;
1009 }
1010 else
1011 {
1012 currentTable.Rows.RemoveAt(i);
1013 i--;
1014 }
1015 }
1016 else if (sequenceChanged && conditionChanged)
1017 {
1018 if (isSectionIdEmpty)
1019 {
1020 row.Fields[1].Modified = false;
1021 keptRows++;
1022 }
1023 else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections))
1024 {
1025 keptRows++;
1026 }
1027 else
1028 {
1029 row.Fields[1].Modified = false;
1030 keptRows++;
1031 }
1032 }
1033 }
1034 else if (row.Operation == RowOperation.Delete)
1035 {
1036 if (isSectionIdEmpty)
1037 {
1038 // it is a stardard action which is added by wix, we should keep this action.
1039 row.Operation = RowOperation.None;
1040 keptRows++;
1041 }
1042 else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections))
1043 {
1044 keptRows++;
1045 }
1046 else
1047 {
1048 if (customAction.ContainsKey(actionName))
1049 {
1050 currentTable.Rows.RemoveAt(i);
1051 i--;
1052 }
1053 else
1054 {
1055 // it is a stardard action, we should keep this action.
1056 row.Operation = RowOperation.None;
1057 keptRows++;
1058 }
1059 }
1060 }
1061 else if (row.Operation == RowOperation.Add)
1062 {
1063 if (isSectionIdEmpty)
1064 {
1065 keptRows++;
1066 }
1067 else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections))
1068 {
1069 keptRows++;
1070 }
1071 else
1072 {
1073 if (customAction.ContainsKey(actionName))
1074 {
1075 currentTable.Rows.RemoveAt(i);
1076 i--;
1077 }
1078 else
1079 {
1080 keptRows++;
1081 }
1082 }
1083 }
1084 }
1085 }
1086
1087 return keptRows;
1088 }
1089
1090 /// <summary>
1091 /// Create the #transform for the given main transform.
1092 /// </summary>
1093 private WindowsInstallerData BuildPairedTransform(Dictionary<SummaryInformationType, SummaryInformationSymbol> summaryInfo, Dictionary<string, MsiPatchMetadataSymbol> patchMetadata, WixPatchSymbol patchIdSymbol, WindowsInstallerData mainTransform, MediaSymbol mediaSymbol, WixPatchBaselineSymbol baselineSymbol, out string productCode)
1094 {
1095 productCode = null;
1096
1097 var pairedTransform = new WindowsInstallerData(null)
1098 {
1099 Type = OutputType.Transform,
1100 Codepage = mainTransform.Codepage
1101 };
1102
1103 // lookup productVersion property to correct summaryInformation
1104 var newProductVersion = mainTransform.Tables["Property"]?.Rows.FirstOrDefault(r => r.FieldAsString(0) == "ProductVersion")?.FieldAsString(1);
1105
1106 var mainSummaryTable = mainTransform.Tables["_SummaryInformation"];
1107 var mainSummaryRows = mainSummaryTable.Rows.ToDictionary(r => r.FieldAsInteger(0));
1108
1109 var baselineValidationFlags = ((int)baselineSymbol.ValidationFlags).ToString(CultureInfo.InvariantCulture);
1110
1111 if (!mainSummaryRows.ContainsKey((int)SummaryInformationType.TransformValidationFlags))
1112 {
1113 var mainSummaryRow = mainSummaryTable.CreateRow(baselineSymbol.SourceLineNumbers);
1114 mainSummaryRow[0] = (int)SummaryInformationType.TransformValidationFlags;
1115 mainSummaryRow[1] = baselineValidationFlags;
1116 }
1117
1118 // copy summary information from core transform
1119 var pairedSummaryTable = pairedTransform.EnsureTable(this.tableDefinitions["_SummaryInformation"]);
1120
1121 foreach (var mainSummaryRow in mainSummaryTable.Rows)
1122 {
1123 var type = (SummaryInformationType)mainSummaryRow.FieldAsInteger(0);
1124 var value = mainSummaryRow.FieldAsString(1);
1125 switch (type)
1126 {
1127 case SummaryInformationType.TransformProductCodes:
1128 var propertyData = value.Split(';');
1129 var oldProductVersion = propertyData[0].Substring(38);
1130 var upgradeCode = propertyData[2];
1131 productCode = propertyData[0].Substring(0, 38);
1132
1133 if (newProductVersion == null)
1134 {
1135 newProductVersion = oldProductVersion;
1136 }
1137
1138 // Force mainTranform to 'old;new;upgrade' and pairedTransform to 'new;new;upgrade'
1139 mainSummaryRow[1] = String.Concat(productCode, oldProductVersion, ';', productCode, newProductVersion, ';', upgradeCode);
1140 value = String.Concat(productCode, newProductVersion, ';', productCode, newProductVersion, ';', upgradeCode);
1141 break;
1142 case SummaryInformationType.TransformValidationFlags: // use validation flags authored into the patch XML.
1143 value = baselineValidationFlags;
1144 mainSummaryRow[1] = value;
1145 break;
1146 }
1147
1148 var pairedSummaryRow = pairedSummaryTable.CreateRow(mainSummaryRow.SourceLineNumbers);
1149 pairedSummaryRow[0] = mainSummaryRow[0];
1150 pairedSummaryRow[1] = value;
1151 }
1152
1153 if (productCode == null)
1154 {
1155 this.Messaging.Write(ErrorMessages.CouldNotDetermineProductCodeFromTransformSummaryInfo());
1156 return null;
1157 }
1158
1159 // Copy File table
1160 if (mainTransform.Tables.TryGetTable("File", out var mainFileTable) && 0 < mainFileTable.Rows.Count)
1161 {
1162 var pairedFileTable = pairedTransform.EnsureTable(mainFileTable.Definition);
1163
1164 foreach (FileRow mainFileRow in mainFileTable.Rows)
1165 {
1166 // Set File.Sequence to non null to satisfy transform bind.
1167 mainFileRow.Sequence = 1;
1168
1169 // Delete's don't need rows in the paired transform.
1170 if (mainFileRow.Operation == RowOperation.Delete)
1171 {
1172 continue;
1173 }
1174
1175 var pairedFileRow = (FileRow)pairedFileTable.CreateRow(mainFileRow.SourceLineNumbers);
1176 pairedFileRow.Operation = RowOperation.Modify;
1177 mainFileRow.CopyTo(pairedFileRow);
1178
1179 // Override authored media for patch bind.
1180 mainFileRow.DiskId = mediaSymbol.DiskId;
1181
1182 // Suppress any change to File.Sequence to avoid bloat.
1183 mainFileRow.Fields[7].Modified = false;
1184
1185 // Force File row to appear in the transform.
1186 switch (mainFileRow.Operation)
1187 {
1188 case RowOperation.Modify:
1189 case RowOperation.Add:
1190 pairedFileRow.Attributes |= WindowsInstallerConstants.MsidbFileAttributesPatchAdded;
1191 pairedFileRow.Fields[6].Modified = true;
1192 pairedFileRow.Operation = mainFileRow.Operation;
1193 break;
1194 default:
1195 pairedFileRow.Fields[6].Modified = false;
1196 break;
1197 }
1198 }
1199 }
1200
1201 // Add Media row to pairedTransform
1202 var pairedMediaTable = pairedTransform.EnsureTable(this.tableDefinitions["Media"]);
1203 var pairedMediaRow = (MediaRow)pairedMediaTable.CreateRow(mediaSymbol.SourceLineNumbers);
1204 pairedMediaRow.Operation = RowOperation.Add;
1205 pairedMediaRow.DiskId = mediaSymbol.DiskId;
1206 pairedMediaRow.LastSequence = mediaSymbol.LastSequence ?? 0;
1207 pairedMediaRow.DiskPrompt = mediaSymbol.DiskPrompt;
1208 pairedMediaRow.Cabinet = mediaSymbol.Cabinet;
1209 pairedMediaRow.VolumeLabel = mediaSymbol.VolumeLabel;
1210 pairedMediaRow.Source = mediaSymbol.Source;
1211
1212 // Add PatchPackage for this Media
1213 var pairedPackageTable = pairedTransform.EnsureTable(this.tableDefinitions["PatchPackage"]);
1214 pairedPackageTable.Operation = TableOperation.Add;
1215 var pairedPackageRow = pairedPackageTable.CreateRow(mediaSymbol.SourceLineNumbers);
1216 pairedPackageRow.Operation = RowOperation.Add;
1217 pairedPackageRow[0] = patchIdSymbol.Id.Id;
1218 pairedPackageRow[1] = mediaSymbol.DiskId;
1219
1220 // Add the property to the patch transform's Property table.
1221 var pairedPropertyTable = pairedTransform.EnsureTable(this.tableDefinitions["Property"]);
1222 pairedPropertyTable.Operation = TableOperation.Add;
1223
1224 // Add property to both identify client patches and whether those patches are removable or not
1225 patchMetadata.TryGetValue("AllowRemoval", out var allowRemovalSymbol);
1226
1227 var pairedPropertyRow = pairedPropertyTable.CreateRow(allowRemovalSymbol?.SourceLineNumbers);
1228 pairedPropertyRow.Operation = RowOperation.Add;
1229 pairedPropertyRow[0] = String.Concat(patchIdSymbol.ClientPatchId, ".AllowRemoval");
1230 pairedPropertyRow[1] = allowRemovalSymbol?.Value ?? "0";
1231
1232 // Add this patch code GUID to the patch transform to identify
1233 // which patches are installed, including in multi-patch
1234 // installations.
1235 pairedPropertyRow = pairedPropertyTable.CreateRow(patchIdSymbol.SourceLineNumbers);
1236 pairedPropertyRow.Operation = RowOperation.Add;
1237 pairedPropertyRow[0] = String.Concat(patchIdSymbol.ClientPatchId, ".PatchCode");
1238 pairedPropertyRow[1] = patchIdSymbol.Id.Id;
1239
1240 // Add PATCHNEWPACKAGECODE to apply to admin layouts.
1241 pairedPropertyRow = pairedPropertyTable.CreateRow(patchIdSymbol.SourceLineNumbers);
1242 pairedPropertyRow.Operation = RowOperation.Add;
1243 pairedPropertyRow[0] = "PATCHNEWPACKAGECODE";
1244 pairedPropertyRow[1] = patchIdSymbol.Id.Id;
1245
1246 // Add PATCHNEWSUMMARYCOMMENTS and PATCHNEWSUMMARYSUBJECT to apply to admin layouts.
1247 if (summaryInfo.TryGetValue(SummaryInformationType.Subject, out var subjectSymbol))
1248 {
1249 pairedPropertyRow = pairedPropertyTable.CreateRow(subjectSymbol.SourceLineNumbers);
1250 pairedPropertyRow.Operation = RowOperation.Add;
1251 pairedPropertyRow[0] = "PATCHNEWSUMMARYSUBJECT";
1252 pairedPropertyRow[1] = subjectSymbol.Value;
1253 }
1254
1255 if (summaryInfo.TryGetValue(SummaryInformationType.Comments, out var commentsSymbol))
1256 {
1257 pairedPropertyRow = pairedPropertyTable.CreateRow(commentsSymbol.SourceLineNumbers);
1258 pairedPropertyRow.Operation = RowOperation.Add;
1259 pairedPropertyRow[0] = "PATCHNEWSUMMARYCOMMENTS";
1260 pairedPropertyRow[1] = commentsSymbol.Value;
1261 }
1262
1263 return pairedTransform;
1264 }
1265
1266 private static SortedSet<string> FinalizePatchProductCodes(List<IntermediateSymbol> symbols, SortedSet<string> productCodes)
1267 {
1268 var patchTargetSymbols = symbols.OfType<WixPatchTargetSymbol>().ToList();
1269
1270 if (patchTargetSymbols.Any())
1271 {
1272 var targets = new SortedSet<string>();
1273 var replace = true;
1274 foreach (var wixPatchTargetRow in patchTargetSymbols)
1275 {
1276 var target = wixPatchTargetRow.ProductCode.ToUpperInvariant();
1277 if (target == "*")
1278 {
1279 replace = false;
1280 }
1281 else
1282 {
1283 targets.Add(target);
1284 }
1285 }
1286
1287 // Replace the target ProductCodes with the authored list.
1288 if (replace)
1289 {
1290 productCodes = targets;
1291 }
1292 else
1293 {
1294 // Copy the authored target ProductCodes into the list.
1295 foreach (var target in targets)
1296 {
1297 productCodes.Add(target);
1298 }
1299 }
1300 }
1301
1302 return productCodes;
1303 }
1304 }
1305}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 using WixToolset.Data;
10 using WixToolset.Data.Symbols;
11 using WixToolset.Data.WindowsInstaller;
12 using WixToolset.Extensibility;
13 using WixToolset.Extensibility.Data;
14 using WixToolset.Extensibility.Services;
15
16 /// <summary>
17 /// Binds a databse.
18 /// </summary>
19 internal class BindDatabaseCommand
20 {
21 // As outlined in RFC 4122, this is our namespace for generating name-based (version 3) UUIDs.
22 internal static readonly Guid WixComponentGuidNamespace = new Guid("{3064E5C6-FB63-4FE9-AC49-E446A792EFA5}");
23
24 public BindDatabaseCommand(IBindContext context, IEnumerable<IWindowsInstallerBackendBinderExtension> backendExtension, string cubeFile) : this(context, backendExtension, null, cubeFile)
25 {
26 }
27
28 public BindDatabaseCommand(IBindContext context, IEnumerable<IWindowsInstallerBackendBinderExtension> backendExtension, IEnumerable<SubStorage> subStorages, string cubeFile)
29 {
30 this.ServiceProvider = context.ServiceProvider;
31
32 this.Messaging = context.ServiceProvider.GetService<IMessaging>();
33
34 this.WindowsInstallerBackendHelper = context.ServiceProvider.GetService<IWindowsInstallerBackendHelper>();
35
36 this.PathResolver = this.ServiceProvider.GetService<IPathResolver>();
37
38 this.CabbingThreadCount = context.CabbingThreadCount;
39 this.CabCachePath = context.CabCachePath;
40 this.DefaultCompressionLevel = context.DefaultCompressionLevel;
41 this.DelayedFields = context.DelayedFields;
42 this.ExpectedEmbeddedFiles = context.ExpectedEmbeddedFiles;
43 this.FileSystemManager = new FileSystemManager(context.FileSystemExtensions);
44 this.Intermediate = context.IntermediateRepresentation;
45 this.IntermediateFolder = context.IntermediateFolder;
46 this.OutputPath = context.OutputPath;
47 this.OutputPdbPath = context.PdbPath;
48 this.PdbType = context.PdbType;
49 this.ResolvedCodepage = context.ResolvedCodepage;
50 this.ResolvedSummaryInformationCodepage = context.ResolvedSummaryInformationCodepage;
51 this.ResolvedLcid = context.ResolvedLcid;
52 this.SuppressLayout = context.SuppressLayout;
53
54 this.SubStorages = subStorages;
55
56 this.SuppressValidation = context.SuppressValidation;
57 this.Ices = context.Ices;
58 this.SuppressedIces = context.SuppressIces;
59 this.CubeFiles = String.IsNullOrEmpty(cubeFile) ? null : new[] { cubeFile };
60
61 this.BackendExtensions = backendExtension;
62 }
63
64 public IServiceProvider ServiceProvider { get; }
65
66 private IMessaging Messaging { get; }
67
68 private IWindowsInstallerBackendHelper WindowsInstallerBackendHelper { get; }
69
70 private IPathResolver PathResolver { get; }
71
72 private int CabbingThreadCount { get; }
73
74 private string CabCachePath { get; }
75
76 private CompressionLevel? DefaultCompressionLevel { get; }
77
78 public IEnumerable<IDelayedField> DelayedFields { get; }
79
80 public IEnumerable<IExpectedExtractFile> ExpectedEmbeddedFiles { get; }
81
82 public FileSystemManager FileSystemManager { get; }
83
84 public bool DeltaBinaryPatch { get; set; }
85
86 private IEnumerable<IWindowsInstallerBackendBinderExtension> BackendExtensions { get; }
87
88 private IEnumerable<SubStorage> SubStorages { get; }
89
90 private Intermediate Intermediate { get; }
91
92 private string OutputPath { get; }
93
94 public PdbType PdbType { get; set; }
95
96 private string OutputPdbPath { get; }
97
98 private int? ResolvedCodepage { get; }
99
100 private int? ResolvedSummaryInformationCodepage { get; }
101
102 private int? ResolvedLcid { get; }
103
104 private bool SuppressAddingValidationRows { get; }
105
106 private bool SuppressLayout { get; }
107
108 private string IntermediateFolder { get; }
109
110 private bool SuppressValidation { get; }
111
112 private IEnumerable<string> Ices { get; }
113
114 private IEnumerable<string> SuppressedIces { get; }
115
116 private IEnumerable<string> CubeFiles { get; }
117
118 public IBindResult Execute()
119 {
120 if (!this.Intermediate.HasLevel(Data.IntermediateLevels.Linked) || !this.Intermediate.HasLevel(Data.IntermediateLevels.Resolved))
121 {
122 this.Messaging.Write(ErrorMessages.IntermediatesMustBeResolved(this.Intermediate.Id));
123 }
124
125 var section = this.Intermediate.Sections.Single();
126
127 var packageSymbol = (section.Type == SectionType.Product) ? this.GetSingleSymbol<WixPackageSymbol>(section) : null;
128 var moduleSymbol = (section.Type == SectionType.Module) ? this.GetSingleSymbol<WixModuleSymbol>(section) : null;
129 var patchSymbol = (section.Type == SectionType.Patch) ? this.GetSingleSymbol<WixPatchSymbol>(section) : null;
130
131 var fileTransfers = new List<IFileTransfer>();
132 var trackedFiles = new List<ITrackedFile>();
133
134 var containsMergeModules = false;
135
136 // Load standard tables, authored custom tables, and extension custom tables.
137 TableDefinitionCollection tableDefinitions;
138 {
139 var command = new LoadTableDefinitionsCommand(this.Messaging, section, this.BackendExtensions);
140 command.Execute();
141
142 tableDefinitions = command.TableDefinitions;
143 }
144
145 // Calculate codepage
146 var codepage = this.CalculateCodepage(packageSymbol, moduleSymbol, patchSymbol);
147
148 // Process properties and create the delayed variable cache if needed.
149 Dictionary<string, string> variableCache = null;
150 string productLanguage = null;
151 {
152 var command = new ProcessPropertiesCommand(section, packageSymbol, this.ResolvedLcid ?? 0, this.DelayedFields.Any(), this.WindowsInstallerBackendHelper);
153 command.Execute();
154
155 variableCache = command.DelayedVariablesCache;
156 productLanguage = command.ProductLanguage;
157 }
158
159 // Process the summary information table after properties are processed.
160 bool compressed;
161 bool longNames;
162 int installerVersion;
163 Platform platform;
164 string modularizationSuffix;
165 {
166 var branding = this.ServiceProvider.GetService<IWixBranding>();
167
168 var command = new BindSummaryInfoCommand(section, this.ResolvedSummaryInformationCodepage, productLanguage, this.WindowsInstallerBackendHelper, branding);
169 command.Execute();
170
171 compressed = command.Compressed;
172 longNames = command.LongNames;
173 installerVersion = command.InstallerVersion;
174 platform = command.Platform;
175 modularizationSuffix = command.ModularizationSuffix;
176 }
177
178 // Sequence all the actions.
179 {
180 var command = new SequenceActionsCommand(this.Messaging, section);
181 command.Execute();
182 }
183
184 if (section.Type == SectionType.Product || section.Type == SectionType.Module)
185 {
186 var command = new AddRequiredStandardDirectories(section, platform);
187 command.Execute();
188 }
189
190 {
191 var command = new CreateSpecialPropertiesCommand(section);
192 command.Execute();
193 }
194
195#if TODO_PATCHING
196 ////if (OutputType.Patch == this.Output.Type)
197 ////{
198 //// foreach (SubStorage substorage in this.Output.SubStorages)
199 //// {
200 //// Output transform = substorage.Data;
201
202 //// ResolveFieldsCommand command = new ResolveFieldsCommand();
203 //// command.Tables = transform.Tables;
204 //// command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles;
205 //// command.FileManagerCore = this.FileManagerCore;
206 //// command.FileManagers = this.FileManagers;
207 //// command.SupportDelayedResolution = false;
208 //// command.TempFilesLocation = this.TempFilesLocation;
209 //// command.WixVariableResolver = this.WixVariableResolver;
210 //// command.Execute();
211
212 //// this.MergeUnrealTables(transform.Tables);
213 //// }
214 ////}
215#endif
216
217 if (this.Messaging.EncounteredError)
218 {
219 return null;
220 }
221
222 this.Intermediate.UpdateLevel(Data.WindowsInstaller.IntermediateLevels.FullyBound);
223 this.Messaging.Write(VerboseMessages.UpdatingFileInformation());
224
225 // Extract files that come from binary .wixlibs and WixExtensions (this does not extract files from merge modules).
226 {
227 var extractedFiles = this.WindowsInstallerBackendHelper.ExtractEmbeddedFiles(this.ExpectedEmbeddedFiles);
228
229 trackedFiles.AddRange(extractedFiles);
230 }
231
232 // This must occur after all variables and source paths have been resolved.
233 List<IFileFacade> fileFacades;
234 if (SectionType.Patch == section.Type)
235 {
236 var command = new GetFileFacadesFromTransforms(this.Messaging, this.WindowsInstallerBackendHelper, this.FileSystemManager, this.SubStorages);
237 command.Execute();
238
239 fileFacades = command.FileFacades;
240 }
241 else
242 {
243 var command = new GetFileFacadesCommand(section, this.WindowsInstallerBackendHelper);
244 command.Execute();
245
246 fileFacades = command.FileFacades;
247 }
248
249 // Retrieve file information from merge modules.
250 if (SectionType.Product == section.Type)
251 {
252 var wixMergeSymbols = section.Symbols.OfType<WixMergeSymbol>().ToList();
253
254 if (wixMergeSymbols.Any())
255 {
256 containsMergeModules = true;
257
258 var command = new ExtractMergeModuleFilesCommand(this.Messaging, this.WindowsInstallerBackendHelper, wixMergeSymbols, fileFacades, installerVersion, this.IntermediateFolder, this.SuppressLayout);
259 command.Execute();
260
261 fileFacades.AddRange(command.MergeModulesFileFacades);
262 }
263 }
264
265 // stop processing if an error previously occurred
266 if (this.Messaging.EncounteredError)
267 {
268 return null;
269 }
270
271 // Process SoftwareTags in MSI packages.
272 if (SectionType.Product == section.Type)
273 {
274 var softwareTags = section.Symbols.OfType<WixProductTagSymbol>().ToList();
275
276 if (softwareTags.Any())
277 {
278 var command = new ProcessPackageSoftwareTagsCommand(section, softwareTags, this.IntermediateFolder);
279 command.Execute();
280 }
281 }
282
283 // Gather information about files that do not come from merge modules.
284 {
285 var command = new UpdateFileFacadesCommand(this.Messaging, section, fileFacades, fileFacades.Where(f => !f.FromModule), variableCache, overwriteHash: true);
286 command.Execute();
287 }
288
289 // stop processing if an error previously occurred
290 if (this.Messaging.EncounteredError)
291 {
292 return null;
293 }
294
295 // Now that the variable cache is populated, resolve any delayed fields.
296 if (this.DelayedFields.Any())
297 {
298 this.WindowsInstallerBackendHelper.ResolveDelayedFields(this.DelayedFields, variableCache);
299 }
300
301 // Update symbols that reference text files on disk.
302 {
303 var command = new UpdateFromTextFilesCommand(this.Messaging, section);
304 command.Execute();
305 }
306
307 // Add missing CreateFolder symbols to null-keypath components.
308 {
309 var command = new AddCreateFoldersCommand(section);
310 command.Execute();
311 }
312
313 // Process dependency references.
314 if (SectionType.Product == section.Type || SectionType.Module == section.Type)
315 {
316 var dependencyRefs = section.Symbols.OfType<WixDependencyRefSymbol>().ToList();
317
318 if (dependencyRefs.Any())
319 {
320 var command = new ProcessDependencyReferencesCommand(this.WindowsInstallerBackendHelper, section, dependencyRefs);
321 command.Execute();
322 }
323 }
324
325 // If there are any backend extensions, give them the opportunity to process
326 // the section now that the fields have all be resolved.
327 //
328 if (this.BackendExtensions.Any())
329 {
330 using (new IntermediateFieldContext("wix.bind.finalize"))
331 {
332 foreach (var extension in this.BackendExtensions)
333 {
334 extension.SymbolsFinalized(section);
335 }
336
337 var reresolvedFiles = section.Symbols
338 .OfType<FileSymbol>()
339 .Where(s => s.Fields.Any(f => f?.Context == "wix.bind.finalize"))
340 .ToList();
341
342 if (reresolvedFiles.Any())
343 {
344 var updatedFacades = reresolvedFiles.Select(f => fileFacades.First(ff => ff.Id == f.Id?.Id));
345
346 var command = new UpdateFileFacadesCommand(this.Messaging, section, fileFacades, updatedFacades, variableCache, overwriteHash: false);
347 command.Execute();
348 }
349 }
350
351 if (this.Messaging.EncounteredError)
352 {
353 return null;
354 }
355 }
356
357 // Set generated component guids and validate all guids.
358 {
359 var command = new FinalizeComponentGuids(this.Messaging, this.WindowsInstallerBackendHelper, this.PathResolver, section, platform);
360 command.Execute();
361 }
362
363 // Assign files to media and update file sequences.
364 Dictionary<MediaSymbol, IEnumerable<IFileFacade>> filesByCabinetMedia;
365 IEnumerable<IFileFacade> uncompressedFiles;
366 {
367 var order = new OptimizeFileFacadesOrderCommand(this.WindowsInstallerBackendHelper, this.PathResolver, section, platform, fileFacades);
368 order.Execute();
369
370 fileFacades = order.FileFacades;
371
372 var assign = new AssignMediaCommand(section, this.Messaging, fileFacades, compressed);
373 assign.Execute();
374
375 filesByCabinetMedia = assign.FileFacadesByCabinetMedia;
376 uncompressedFiles = assign.UncompressedFileFacades;
377
378 var update = new UpdateMediaSequencesCommand(section, fileFacades);
379 update.Execute();
380 }
381
382 // stop processing if an error previously occurred
383 if (this.Messaging.EncounteredError)
384 {
385 return null;
386 }
387
388 // Time to create the WindowsInstallerData object. Try to put as much above here as possible, updating the IR is better.
389 WindowsInstallerData data;
390 {
391 var command = new CreateWindowsInstallerDataFromIRCommand(this.Messaging, section, tableDefinitions, codepage, this.BackendExtensions, this.WindowsInstallerBackendHelper);
392 data = command.Execute();
393 }
394
395 IEnumerable<string> suppressedTableNames = null;
396 if (data.Type == OutputType.Module)
397 {
398 // Modularize identifiers.
399 var modularize = new ModularizeCommand(this.WindowsInstallerBackendHelper, data, modularizationSuffix, section.Symbols.OfType<WixSuppressModularizationSymbol>());
400 modularize.Execute();
401
402 // Ensure all sequence tables in place because, mergemod.dll requires them.
403 var unsuppress = new AddBackSuppressedSequenceTablesCommand(data, tableDefinitions);
404 suppressedTableNames = unsuppress.Execute();
405 }
406 else if (data.Type == OutputType.Patch)
407 {
408 foreach (var storage in this.SubStorages)
409 {
410 data.SubStorages.Add(storage);
411 }
412 }
413
414 // Stop processing if an error previously occurred.
415 if (this.Messaging.EncounteredError)
416 {
417 return null;
418 }
419
420 // Ensure the intermediate folder is created since delta patches will be
421 // created there.
422 Directory.CreateDirectory(this.IntermediateFolder);
423
424 if (SectionType.Patch == section.Type && this.DeltaBinaryPatch)
425 {
426 var command = new CreateDeltaPatchesCommand(fileFacades, this.IntermediateFolder, section.Symbols.OfType<WixPatchSymbol>().FirstOrDefault());
427 command.Execute();
428 }
429
430 // create cabinet files and process uncompressed files
431 var layoutDirectory = Path.GetDirectoryName(this.OutputPath);
432 if (!this.SuppressLayout || OutputType.Module == data.Type)
433 {
434 this.Messaging.Write(VerboseMessages.CreatingCabinetFiles());
435
436 var mediaTemplate = section.Symbols.OfType<WixMediaTemplateSymbol>().FirstOrDefault();
437
438 var command = new CreateCabinetsCommand(this.ServiceProvider, this.WindowsInstallerBackendHelper, mediaTemplate);
439 command.CabbingThreadCount = this.CabbingThreadCount;
440 command.CabCachePath = this.CabCachePath;
441 command.DefaultCompressionLevel = this.DefaultCompressionLevel;
442 command.Data = data;
443 command.Messaging = this.Messaging;
444 command.BackendExtensions = this.BackendExtensions;
445 command.LayoutDirectory = layoutDirectory;
446 command.Compressed = compressed;
447 command.ModularizationSuffix = modularizationSuffix;
448 command.FileFacadesByCabinet = filesByCabinetMedia;
449 command.ResolveMedia = this.ResolveMedia;
450 command.TableDefinitions = tableDefinitions;
451 command.IntermediateFolder = this.IntermediateFolder;
452 command.Execute();
453
454 fileTransfers.AddRange(command.FileTransfers);
455 trackedFiles.AddRange(command.TrackedFiles);
456 }
457
458 // stop processing if an error previously occurred
459 if (this.Messaging.EncounteredError)
460 {
461 return null;
462 }
463
464 // We can create instance transforms since Component Guids and Outputs are created.
465 if (data.Type == OutputType.Product)
466 {
467 var command = new CreateInstanceTransformsCommand(section, data, tableDefinitions, this.WindowsInstallerBackendHelper);
468 command.Execute();
469 }
470 else if (data.Type == OutputType.Patch)
471 {
472 // Copy output data back into the transforms.
473 var command = new UpdateTransformsWithFileFacades(this.Messaging, data, this.SubStorages, tableDefinitions, fileFacades);
474 command.Execute();
475 }
476
477 // Generate database file.
478 {
479 this.Messaging.Write(VerboseMessages.GeneratingDatabase());
480
481 var trackMsi = this.WindowsInstallerBackendHelper.TrackFile(this.OutputPath, TrackedFileType.Final);
482 trackedFiles.Add(trackMsi);
483
484 var command = new GenerateDatabaseCommand(this.Messaging, this.WindowsInstallerBackendHelper, this.FileSystemManager, data, trackMsi.Path, tableDefinitions, this.IntermediateFolder, keepAddedColumns: false, this.SuppressAddingValidationRows, useSubdirectory: false);
485 command.Execute();
486
487 trackedFiles.AddRange(command.GeneratedTemporaryFiles);
488 }
489
490 // Stop processing if an error previously occurred.
491 if (this.Messaging.EncounteredError)
492 {
493 return null;
494 }
495
496 // Merge modules.
497 if (containsMergeModules)
498 {
499 this.Messaging.Write(VerboseMessages.MergingModules());
500
501 var command = new MergeModulesCommand(this.Messaging, fileFacades, section, suppressedTableNames, this.OutputPath, this.IntermediateFolder);
502 command.Execute();
503 }
504
505 if (this.Messaging.EncounteredError)
506 {
507 return null;
508 }
509
510 // Validate the output if there are CUBe files and we're not explicitly suppressing validation.
511 if (this.CubeFiles != null && !this.SuppressValidation)
512 {
513 var command = new ValidateDatabaseCommand(this.Messaging, this.WindowsInstallerBackendHelper, this.IntermediateFolder, data, this.OutputPath, this.CubeFiles, this.Ices, this.SuppressedIces);
514 command.Execute();
515
516 trackedFiles.AddRange(command.TrackedFiles);
517 }
518
519 if (this.Messaging.EncounteredError)
520 {
521 return null;
522 }
523
524 // Process uncompressed files.
525 if (!this.SuppressLayout && uncompressedFiles.Any())
526 {
527 var command = new ProcessUncompressedFilesCommand(section, this.WindowsInstallerBackendHelper, this.PathResolver);
528 command.Compressed = compressed;
529 command.FileFacades = uncompressedFiles;
530 command.LayoutDirectory = layoutDirectory;
531 command.LongNamesInImage = longNames;
532 command.ResolveMedia = this.ResolveMedia;
533 command.DatabasePath = this.OutputPath;
534 command.Execute();
535
536 fileTransfers.AddRange(command.FileTransfers);
537 trackedFiles.AddRange(command.TrackedFiles);
538 }
539
540 // TODO: this is not sufficient to collect all Input files (for example, it misses Binary and Icon tables).
541 trackedFiles.AddRange(fileFacades.Select(f => this.WindowsInstallerBackendHelper.TrackFile(f.SourcePath, TrackedFileType.Input, f.SourceLineNumber)));
542
543 var result = this.ServiceProvider.GetService<IBindResult>();
544 result.FileTransfers = fileTransfers;
545 result.TrackedFiles = trackedFiles;
546 result.Wixout = this.CreateWixout(trackedFiles, this.Intermediate, data);
547
548 return result;
549 }
550
551 private int CalculateCodepage(WixPackageSymbol packageSymbol, WixModuleSymbol moduleSymbol, WixPatchSymbol patchSymbol)
552 {
553 var codepage = packageSymbol?.Codepage ?? moduleSymbol?.Codepage ?? patchSymbol?.Codepage;
554
555 if (String.IsNullOrEmpty(codepage))
556 {
557 codepage = this.ResolvedCodepage?.ToString() ?? "65001";
558
559 if (packageSymbol != null)
560 {
561 packageSymbol.Codepage = codepage;
562 }
563 else if (moduleSymbol != null)
564 {
565 moduleSymbol.Codepage = codepage;
566 }
567 else if (patchSymbol != null)
568 {
569 patchSymbol.Codepage = codepage;
570 }
571 }
572
573 return this.WindowsInstallerBackendHelper.GetValidCodePage(codepage);
574 }
575
576 private T GetSingleSymbol<T>(IntermediateSection section) where T : IntermediateSymbol
577 {
578 var symbols = section.Symbols.OfType<T>().ToList();
579
580 if (1 != symbols.Count)
581 {
582 throw new WixException(ErrorMessages.MissingBundleInformation(nameof(T)));
583 }
584
585 return symbols[0];
586 }
587
588 private WixOutput CreateWixout(List<ITrackedFile> trackedFiles, Intermediate intermediate, WindowsInstallerData data)
589 {
590 WixOutput wixout;
591
592 if (String.IsNullOrEmpty(this.OutputPdbPath))
593 {
594 wixout = WixOutput.Create();
595 }
596 else
597 {
598 var trackPdb = this.WindowsInstallerBackendHelper.TrackFile(this.OutputPdbPath, TrackedFileType.Final);
599 trackedFiles.Add(trackPdb);
600
601 wixout = WixOutput.Create(trackPdb.Path);
602 }
603
604 intermediate.Save(wixout);
605
606 data.Save(wixout);
607
608 wixout.Reopen();
609
610 return wixout;
611 }
612
613 private string ResolveMedia(MediaSymbol media, string mediaLayoutDirectory, string layoutDirectory)
614 {
615 string layout = null;
616
617 foreach (var extension in this.BackendExtensions)
618 {
619 layout = extension.ResolveMedia(media, mediaLayoutDirectory, layoutDirectory);
620 if (!String.IsNullOrEmpty(layout))
621 {
622 break;
623 }
624 }
625
626 // If no binder file manager resolved the layout, do the default behavior.
627 if (String.IsNullOrEmpty(layout))
628 {
629 if (String.IsNullOrEmpty(mediaLayoutDirectory))
630 {
631 layout = layoutDirectory;
632 }
633 else if (Path.IsPathRooted(mediaLayoutDirectory))
634 {
635 layout = mediaLayoutDirectory;
636 }
637 else
638 {
639 layout = Path.Combine(layoutDirectory, mediaLayoutDirectory);
640 }
641 }
642
643 return layout;
644 }
645 }
646}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Globalization;
7 using System.Linq;
8 using WixToolset.Data;
9 using WixToolset.Data.Symbols;
10 using WixToolset.Extensibility.Services;
11
12 /// <summary>
13 /// Binds the summary information table of a database.
14 /// </summary>
15 internal class BindSummaryInfoCommand
16 {
17 public BindSummaryInfoCommand(IntermediateSection section, int? summaryInformationCodepage, string productLanguage, IBackendHelper backendHelper, IWixBranding branding)
18 {
19 this.Section = section;
20 this.SummaryInformationCodepage = summaryInformationCodepage;
21 this.ProductLanguage = productLanguage;
22 this.BackendHelper = backendHelper;
23 this.Branding = branding;
24 }
25
26 private IntermediateSection Section { get; }
27
28 private int? SummaryInformationCodepage { get; }
29
30 private string ProductLanguage { get; }
31
32 private IBackendHelper BackendHelper { get; }
33
34 private IWixBranding Branding { get; }
35
36 /// <summary>
37 /// Returns a flag indicating if files are compressed by default.
38 /// </summary>
39 public bool Compressed { get; private set; }
40
41 /// <summary>
42 /// Returns a flag indicating if uncompressed files use long filenames.
43 /// </summary>
44 public bool LongNames { get; private set; }
45
46 public int InstallerVersion { get; private set; }
47
48 public Platform Platform { get; private set; }
49
50 /// <summary>
51 /// Modularization guid, or null if the output is not a module.
52 /// </summary>
53 public string ModularizationSuffix { get; private set; }
54
55 public void Execute()
56 {
57 this.Compressed = false;
58 this.LongNames = false;
59 this.InstallerVersion = 0;
60 this.ModularizationSuffix = null;
61
62 SummaryInformationSymbol summaryInformationCodepageSymbol = null;
63 SummaryInformationSymbol platformAndLanguageSymbol = null;
64 var foundCreateDateTime = false;
65 var foundLastSaveDataTime = false;
66 var foundCreatingApplication = false;
67 var foundPackageCode = false;
68 var now = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss", CultureInfo.InvariantCulture);
69
70 foreach (var summaryInformationSymbol in this.Section.Symbols.OfType<SummaryInformationSymbol>())
71 {
72 switch (summaryInformationSymbol.PropertyId)
73 {
74 case SummaryInformationType.Codepage: // PID_CODEPAGE
75 summaryInformationCodepageSymbol = summaryInformationSymbol;
76 break;
77
78 case SummaryInformationType.PlatformAndLanguage:
79 platformAndLanguageSymbol = summaryInformationSymbol;
80 break;
81
82 case SummaryInformationType.PackageCode: // PID_REVNUMBER
83 foundPackageCode = true;
84 var packageCode = summaryInformationSymbol.Value;
85
86 if (SectionType.Module == this.Section.Type)
87 {
88 this.ModularizationSuffix = "." + packageCode.Substring(1, 36).Replace('-', '_');
89 }
90 break;
91 case SummaryInformationType.Created:
92 foundCreateDateTime = true;
93 break;
94 case SummaryInformationType.LastSaved:
95 foundLastSaveDataTime = true;
96 break;
97 case SummaryInformationType.WindowsInstallerVersion:
98 this.InstallerVersion = summaryInformationSymbol[SummaryInformationSymbolFields.Value].AsNumber();
99 break;
100 case SummaryInformationType.WordCount:
101 if (SectionType.Patch == this.Section.Type)
102 {
103 this.LongNames = true;
104 this.Compressed = true;
105 }
106 else
107 {
108 var attributes = summaryInformationSymbol[SummaryInformationSymbolFields.Value].AsNumber();
109 this.LongNames = (0 == (attributes & 1));
110 this.Compressed = (2 == (attributes & 2));
111 }
112 break;
113 case SummaryInformationType.CreatingApplication: // PID_APPNAME
114 foundCreatingApplication = true;
115 break;
116 }
117 }
118
119 // Ensure the codepage is set properly.
120 if (summaryInformationCodepageSymbol == null)
121 {
122 summaryInformationCodepageSymbol = this.Section.AddSymbol(new SummaryInformationSymbol(null)
123 {
124 PropertyId = SummaryInformationType.Codepage
125 });
126 }
127
128 var codepage = summaryInformationCodepageSymbol.Value;
129
130 if (String.IsNullOrEmpty(codepage))
131 {
132 codepage = this.SummaryInformationCodepage?.ToString(CultureInfo.InvariantCulture) ?? "1252";
133 }
134
135 summaryInformationCodepageSymbol.Value = this.BackendHelper.GetValidCodePage(codepage, onlyAnsi: true).ToString(CultureInfo.InvariantCulture);
136
137 // Ensure the language is set properly and figure out what platform we are targeting.
138 if (platformAndLanguageSymbol != null)
139 {
140 this.Platform = EnsureLanguageAndGetPlatformFromSummaryInformation(platformAndLanguageSymbol, this.ProductLanguage);
141 }
142
143 // Set the revision number (package/patch code) if it should be automatically generated.
144 if (!foundPackageCode)
145 {
146 this.Section.AddSymbol(new SummaryInformationSymbol(null)
147 {
148 PropertyId = SummaryInformationType.PackageCode,
149 Value = this.BackendHelper.CreateGuid(),
150 });
151 }
152
153 // add a summary information row for the create time/date property if its not already set
154 if (!foundCreateDateTime)
155 {
156 this.Section.AddSymbol(new SummaryInformationSymbol(null)
157 {
158 PropertyId = SummaryInformationType.Created,
159 Value = now,
160 });
161 }
162
163 // add a summary information row for the last save time/date property if its not already set
164 if (!foundLastSaveDataTime)
165 {
166 this.Section.AddSymbol(new SummaryInformationSymbol(null)
167 {
168 PropertyId = SummaryInformationType.LastSaved,
169 Value = now,
170 });
171 }
172
173 // add a summary information row for the creating application property if its not already set
174 if (!foundCreatingApplication)
175 {
176 this.Section.AddSymbol(new SummaryInformationSymbol(null)
177 {
178 PropertyId = SummaryInformationType.CreatingApplication,
179 Value = this.Branding.GetCreatingApplication(),
180 });
181 }
182 }
183
184 private static Platform EnsureLanguageAndGetPlatformFromSummaryInformation(SummaryInformationSymbol symbol, string language)
185 {
186 var value = symbol.Value;
187 var separatorIndex = value.IndexOf(';');
188 var platformValue = separatorIndex > 0 ? value.Substring(0, separatorIndex) : value;
189
190 // If the language was provided and there was language value after the separator
191 // (or the separator was absent) then use the provided language.
192 if (!String.IsNullOrEmpty(language) && (separatorIndex < 0 || separatorIndex + 1 == value.Length))
193 {
194 symbol.Value = platformValue + ';' + language;
195 }
196
197 switch (platformValue)
198 {
199 case "x64":
200 return Platform.X64;
201
202 case "Arm64":
203 return Platform.ARM64;
204
205 case "Intel":
206 default:
207 return Platform.X86;
208 }
209 }
210 }
211}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Globalization;
7 using System.IO;
8 using WixToolset.Core.Native.Msi;
9 using WixToolset.Data;
10 using WixToolset.Data.Symbols;
11 using WixToolset.Data.WindowsInstaller;
12 using WixToolset.Extensibility.Services;
13
14 internal class BindTransformCommand
15 {
16 public BindTransformCommand(IMessaging messaging, IBackendHelper backendHelper, FileSystemManager fileSystemManager, string intermediateFolder, WindowsInstallerData transform, string outputPath, TableDefinitionCollection tableDefinitions)
17 {
18 this.Messaging = messaging;
19 this.BackendHelper = backendHelper;
20 this.FileSystemManager = fileSystemManager;
21 this.IntermediateFolder = intermediateFolder;
22 this.Transform = transform;
23 this.OutputPath = outputPath;
24 this.TableDefinitions = tableDefinitions;
25 }
26
27 private IMessaging Messaging { get; }
28
29 private IBackendHelper BackendHelper { get; }
30
31 private FileSystemManager FileSystemManager { get; }
32
33 private TableDefinitionCollection TableDefinitions { get; }
34
35 private string IntermediateFolder { get; }
36
37 private WindowsInstallerData Transform { get; }
38
39 private string OutputPath { get; }
40
41 public void Execute()
42 {
43 var transformFlags = 0;
44
45 var targetOutput = new WindowsInstallerData(null);
46 var updatedOutput = new WindowsInstallerData(null);
47
48 // TODO: handle added columns
49
50 // to generate a localized transform, both the target and updated
51 // databases need to have the same code page. the only reason to
52 // set different code pages is to support localized primary key
53 // columns, but that would only support deleting rows. if this
54 // becomes necessary, define a PreviousCodepage property on the
55 // Output class and persist this throughout transform generation.
56 targetOutput.Codepage = this.Transform.Codepage;
57 updatedOutput.Codepage = this.Transform.Codepage;
58
59 // remove certain Property rows which will be populated from summary information values
60 string targetUpgradeCode = null;
61 string updatedUpgradeCode = null;
62
63 if (this.Transform.TryGetTable("Property", out var propertyTable))
64 {
65 for (int i = propertyTable.Rows.Count - 1; i >= 0; i--)
66 {
67 Row row = propertyTable.Rows[i];
68
69 if ("ProductCode" == (string)row[0] || "ProductLanguage" == (string)row[0] || "ProductVersion" == (string)row[0] || "UpgradeCode" == (string)row[0])
70 {
71 propertyTable.Rows.RemoveAt(i);
72
73 if ("UpgradeCode" == (string)row[0])
74 {
75 updatedUpgradeCode = (string)row[1];
76 }
77 }
78 }
79 }
80
81 var targetSummaryInfo = targetOutput.EnsureTable(this.TableDefinitions["_SummaryInformation"]);
82 var updatedSummaryInfo = updatedOutput.EnsureTable(this.TableDefinitions["_SummaryInformation"]);
83 var targetPropertyTable = targetOutput.EnsureTable(this.TableDefinitions["Property"]);
84 var updatedPropertyTable = updatedOutput.EnsureTable(this.TableDefinitions["Property"]);
85
86 // process special summary information values
87 foreach (var row in this.Transform.Tables["_SummaryInformation"].Rows)
88 {
89 var summaryId = row.FieldAsInteger(0);
90 var summaryData = row.FieldAsString(1);
91
92 if ((int)SummaryInformation.Transform.CodePage == summaryId)
93 {
94 // convert from a web name if provided
95 var codePage = summaryData;
96 if (null == codePage)
97 {
98 codePage = "0";
99 }
100 else
101 {
102 codePage = this.BackendHelper.GetValidCodePage(codePage).ToString(CultureInfo.InvariantCulture);
103 }
104
105 var previousCodePage = row.Fields[1].PreviousData;
106 if (null == previousCodePage)
107 {
108 previousCodePage = "0";
109 }
110 else
111 {
112 previousCodePage = this.BackendHelper.GetValidCodePage(previousCodePage).ToString(CultureInfo.InvariantCulture);
113 }
114
115 var targetCodePageRow = targetSummaryInfo.CreateRow(null);
116 targetCodePageRow[0] = 1; // PID_CODEPAGE
117 targetCodePageRow[1] = previousCodePage;
118
119 var updatedCodePageRow = updatedSummaryInfo.CreateRow(null);
120 updatedCodePageRow[0] = 1; // PID_CODEPAGE
121 updatedCodePageRow[1] = codePage;
122 }
123 else if ((int)SummaryInformation.Transform.TargetPlatformAndLanguage == summaryId ||
124 (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == summaryId)
125 {
126 // the target language
127 var propertyData = summaryData.Split(';');
128 var lang = 2 == propertyData.Length ? propertyData[1] : "0";
129
130 var tempSummaryInfo = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == summaryId ? targetSummaryInfo : updatedSummaryInfo;
131 var tempPropertyTable = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == summaryId ? targetPropertyTable : updatedPropertyTable;
132
133 var productLanguageRow = tempPropertyTable.CreateRow(null);
134 productLanguageRow[0] = "ProductLanguage";
135 productLanguageRow[1] = lang;
136
137 // set the platform;language on the MSI to be generated
138 var templateRow = tempSummaryInfo.CreateRow(null);
139 templateRow[0] = 7; // PID_TEMPLATE
140 templateRow[1] = summaryData;
141 }
142 else if ((int)SummaryInformation.Transform.ProductCodes == summaryId)
143 {
144 var propertyData = summaryData.Split(';');
145
146 var targetProductCodeRow = targetPropertyTable.CreateRow(null);
147 targetProductCodeRow[0] = "ProductCode";
148 targetProductCodeRow[1] = propertyData[0].Substring(0, 38);
149
150 var targetProductVersionRow = targetPropertyTable.CreateRow(null);
151 targetProductVersionRow[0] = "ProductVersion";
152 targetProductVersionRow[1] = propertyData[0].Substring(38);
153
154 var updatedProductCodeRow = updatedPropertyTable.CreateRow(null);
155 updatedProductCodeRow[0] = "ProductCode";
156 updatedProductCodeRow[1] = propertyData[1].Substring(0, 38);
157
158 var updatedProductVersionRow = updatedPropertyTable.CreateRow(null);
159 updatedProductVersionRow[0] = "ProductVersion";
160 updatedProductVersionRow[1] = propertyData[1].Substring(38);
161
162 // UpgradeCode is optional and may not exists in the target
163 // or upgraded databases, so do not include a null-valued
164 // UpgradeCode property.
165
166 targetUpgradeCode = propertyData[2];
167 if (!String.IsNullOrEmpty(targetUpgradeCode))
168 {
169 var targetUpgradeCodeRow = targetPropertyTable.CreateRow(null);
170 targetUpgradeCodeRow[0] = "UpgradeCode";
171 targetUpgradeCodeRow[1] = targetUpgradeCode;
172
173 // If the target UpgradeCode is specified, an updated
174 // UpgradeCode is required.
175 if (String.IsNullOrEmpty(updatedUpgradeCode))
176 {
177 updatedUpgradeCode = targetUpgradeCode;
178 }
179 }
180
181 if (!String.IsNullOrEmpty(updatedUpgradeCode))
182 {
183 var updatedUpgradeCodeRow = updatedPropertyTable.CreateRow(null);
184 updatedUpgradeCodeRow[0] = "UpgradeCode";
185 updatedUpgradeCodeRow[1] = updatedUpgradeCode;
186 }
187 }
188 else if ((int)SummaryInformation.Transform.ValidationFlags == summaryId)
189 {
190 transformFlags = Convert.ToInt32(summaryData, CultureInfo.InvariantCulture);
191 }
192 else if ((int)SummaryInformation.Transform.Reserved11 == summaryId)
193 {
194 // PID_LASTPRINTED should be null for transforms
195 row.Operation = RowOperation.None;
196 }
197 else
198 {
199 // add everything else as is
200 var targetRow = targetSummaryInfo.CreateRow(null);
201 targetRow[0] = row[0];
202 targetRow[1] = row[1];
203
204 var updatedRow = updatedSummaryInfo.CreateRow(null);
205 updatedRow[0] = row[0];
206 updatedRow[1] = row[1];
207 }
208 }
209
210 // Validate that both databases have an UpgradeCode if the
211 // authoring transform will validate the UpgradeCode; otherwise,
212 // MsiCreateTransformSummaryinfo() will fail with 1620.
213 if (((int)TransformFlags.ValidateUpgradeCode & transformFlags) != 0 &&
214 (String.IsNullOrEmpty(targetUpgradeCode) || String.IsNullOrEmpty(updatedUpgradeCode)))
215 {
216 this.Messaging.Write(ErrorMessages.BothUpgradeCodesRequired());
217 }
218
219 string emptyFile = null;
220
221 foreach (var table in this.Transform.Tables)
222 {
223 // Ignore unreal tables when building transforms except the _Stream table.
224 // These tables are ignored when generating the database so there is no reason
225 // to process them here.
226 if (table.Definition.Unreal && "_Streams" != table.Name)
227 {
228 continue;
229 }
230
231 // process table operations
232 switch (table.Operation)
233 {
234 case TableOperation.Add:
235 updatedOutput.EnsureTable(table.Definition);
236 break;
237 case TableOperation.Drop:
238 targetOutput.EnsureTable(table.Definition);
239 continue;
240 default:
241 targetOutput.EnsureTable(table.Definition);
242 updatedOutput.EnsureTable(table.Definition);
243 break;
244 }
245
246 // process row operations
247 foreach (var row in table.Rows)
248 {
249 switch (row.Operation)
250 {
251 case RowOperation.Add:
252 var updatedTable = updatedOutput.EnsureTable(table.Definition);
253 updatedTable.Rows.Add(row);
254 continue;
255
256 case RowOperation.Delete:
257 var targetTable = targetOutput.EnsureTable(table.Definition);
258 targetTable.Rows.Add(row);
259
260 // fill-in non-primary key values
261 foreach (var field in row.Fields)
262 {
263 if (!field.Column.PrimaryKey)
264 {
265 if (ColumnType.Number == field.Column.Type && !field.Column.IsLocalizable)
266 {
267 field.Data = field.Column.MinValue;
268 }
269 else if (ColumnType.Object == field.Column.Type)
270 {
271 if (null == emptyFile)
272 {
273 emptyFile = Path.Combine(this.IntermediateFolder, "empty");
274 }
275
276 field.Data = emptyFile;
277 }
278 else
279 {
280 field.Data = "0";
281 }
282 }
283 }
284 continue;
285 }
286
287 // Assure that the file table's sequence is populated
288 if ("File" == table.Name)
289 {
290 foreach (var fileRow in table.Rows)
291 {
292 if (null == fileRow[7])
293 {
294 if (RowOperation.Add == fileRow.Operation)
295 {
296 this.Messaging.Write(ErrorMessages.InvalidAddedFileRowWithoutSequence(fileRow.SourceLineNumbers, (string)fileRow[0]));
297 break;
298 }
299
300 // Set to 1 to prevent invalid IDT file from being generated
301 fileRow[7] = 1;
302 }
303 }
304 }
305
306 // process modified and unmodified rows
307 var modifiedRow = false;
308 var targetRow = table.Definition.CreateRow(null);
309 var updatedRow = row;
310 for (var i = 0; i < row.Fields.Length; i++)
311 {
312 var updatedField = row.Fields[i];
313
314 if (updatedField.Modified)
315 {
316 // set a different value in the target row to ensure this value will be modified during transform generation
317 if (ColumnType.Number == updatedField.Column.Type && !updatedField.Column.IsLocalizable)
318 {
319 var data = updatedField.AsNullableInteger();
320 targetRow[i] = (data == 1) ? 2 : 1;
321 }
322 else if (ColumnType.Object == updatedField.Column.Type)
323 {
324 if (null == emptyFile)
325 {
326 emptyFile = Path.Combine(this.IntermediateFolder, "empty");
327 }
328
329 targetRow[i] = emptyFile;
330 }
331 else
332 {
333 var data = updatedField.AsString();
334 targetRow[i] = (data == "0") ? "1" : "0";
335 }
336
337 modifiedRow = true;
338 }
339 else if (ColumnType.Object == updatedField.Column.Type)
340 {
341 var objectField = (ObjectField)updatedField;
342
343 // create an empty file for comparing against
344 if (null == objectField.PreviousData)
345 {
346 if (null == emptyFile)
347 {
348 emptyFile = Path.Combine(this.IntermediateFolder, "empty");
349 }
350
351 targetRow[i] = emptyFile;
352 modifiedRow = true;
353 }
354 else if (!this.FileSystemManager.CompareFiles(objectField.PreviousData, (string)objectField.Data))
355 {
356 targetRow[i] = objectField.PreviousData;
357 modifiedRow = true;
358 }
359 }
360 else // unmodified
361 {
362 if (null != updatedField.Data)
363 {
364 targetRow[i] = updatedField.Data;
365 }
366 }
367 }
368
369 // modified rows and certain special rows go in the target and updated msi databases
370 if (modifiedRow ||
371 ("Property" == table.Name &&
372 ("ProductCode" == (string)row[0] ||
373 "ProductLanguage" == (string)row[0] ||
374 "ProductVersion" == (string)row[0] ||
375 "UpgradeCode" == (string)row[0])))
376 {
377 var targetTable = targetOutput.EnsureTable(table.Definition);
378 targetTable.Rows.Add(targetRow);
379
380 var updatedTable = updatedOutput.EnsureTable(table.Definition);
381 updatedTable.Rows.Add(updatedRow);
382 }
383 }
384 }
385
386 //foreach (BinderExtension extension in this.Extensions)
387 //{
388 // extension.PostBind(this.Context);
389 //}
390
391 // Any errors encountered up to this point can cause errors during generation.
392 if (this.Messaging.EncounteredError)
393 {
394 return;
395 }
396
397 var transformFileName = Path.GetFileNameWithoutExtension(this.OutputPath);
398 var targetDatabaseFile = Path.Combine(this.IntermediateFolder, String.Concat(transformFileName, "_target.msi"));
399 var updatedDatabaseFile = Path.Combine(this.IntermediateFolder, String.Concat(transformFileName, "_updated.msi"));
400
401 try
402 {
403 if (!String.IsNullOrEmpty(emptyFile))
404 {
405 using (var fileStream = File.Create(emptyFile))
406 {
407 }
408 }
409
410 this.GenerateDatabase(targetOutput, targetDatabaseFile, keepAddedColumns: false);
411 this.GenerateDatabase(updatedOutput, updatedDatabaseFile, keepAddedColumns: true);
412
413 // make sure the directory exists
414 Directory.CreateDirectory(Path.GetDirectoryName(this.OutputPath));
415
416 // create the transform file
417 using (var targetDatabase = new Database(targetDatabaseFile, OpenDatabase.ReadOnly))
418 using (var updatedDatabase = new Database(updatedDatabaseFile, OpenDatabase.ReadOnly))
419 {
420 if (updatedDatabase.GenerateTransform(targetDatabase, this.OutputPath))
421 {
422 updatedDatabase.CreateTransformSummaryInfo(targetDatabase, this.OutputPath, (TransformErrorConditions)(transformFlags & 0xFFFF), (TransformValidations)((transformFlags >> 16) & 0xFFFF));
423 }
424 else
425 {
426 this.Messaging.Write(ErrorMessages.NoDifferencesInTransform(this.Transform.SourceLineNumbers));
427 }
428 }
429 }
430 finally
431 {
432 if (!String.IsNullOrEmpty(emptyFile))
433 {
434 File.Delete(emptyFile);
435 }
436 }
437 }
438
439 private void GenerateDatabase(WindowsInstallerData output, string outputPath, bool keepAddedColumns)
440 {
441 var command = new GenerateDatabaseCommand(this.Messaging, this.BackendHelper, this.FileSystemManager, output, outputPath, this.TableDefinitions, this.IntermediateFolder, keepAddedColumns, suppressAddingValidationRows: true, useSubdirectory: true);
442 command.Execute();
443 }
444 }
445}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 using System.Threading;
10 using WixToolset.Core.Native;
11 using WixToolset.Data;
12 using WixToolset.Extensibility.Services;
13
14 /// <summary>
15 /// Builds cabinets using multiple threads. This implements a thread pool that generates cabinets with multiple
16 /// threads. Unlike System.Threading.ThreadPool, it waits until all threads are finished.
17 /// </summary>
18 internal sealed class CabinetBuilder
19 {
20 private readonly Queue<CabinetWorkItem> cabinetWorkItems;
21 private int threadCount;
22
23 // Address of Binder's callback function for Cabinet Splitting
24 private readonly IntPtr newCabNamesCallBackAddress;
25
26 /// <summary>
27 /// Instantiate a new CabinetBuilder.
28 /// </summary>
29 /// <param name="messaging"></param>
30 /// <param name="threadCount">number of threads to use</param>
31 /// <param name="newCabNamesCallBackAddress">Address of Binder's callback function for Cabinet Splitting</param>
32 public CabinetBuilder(IMessaging messaging, int threadCount, IntPtr newCabNamesCallBackAddress)
33 {
34 if (0 >= threadCount)
35 {
36 throw new ArgumentOutOfRangeException(nameof(threadCount));
37 }
38
39 this.cabinetWorkItems = new Queue<CabinetWorkItem>();
40 this.Messaging = messaging;
41 this.threadCount = threadCount;
42
43 // Set Address of Binder's callback function for Cabinet Splitting
44 this.newCabNamesCallBackAddress = newCabNamesCallBackAddress;
45 }
46
47 private IMessaging Messaging { get; }
48
49 public int MaximumCabinetSizeForLargeFileSplitting { get; set; }
50
51 public int MaximumUncompressedMediaSize { get; set; }
52
53 /// <summary>
54 /// Enqueues a CabinetWorkItem to the queue.
55 /// </summary>
56 /// <param name="cabinetWorkItem">cabinet work item</param>
57 public void Enqueue(CabinetWorkItem cabinetWorkItem) => this.cabinetWorkItems.Enqueue(cabinetWorkItem);
58
59 /// <summary>
60 /// Create the queued cabinets.
61 /// </summary>
62 /// <returns>error message number (zero if no error)</returns>
63 public void CreateQueuedCabinets()
64 {
65 // don't create more threads than the number of cabinets to build
66 var numberOfThreads = Math.Min(this.threadCount, this.cabinetWorkItems.Count);
67
68 if (0 < numberOfThreads)
69 {
70 var threads = new Thread[numberOfThreads];
71
72 for (var i = 0; i < threads.Length; i++)
73 {
74 threads[i] = new Thread(new ThreadStart(this.ProcessWorkItems));
75 threads[i].Start();
76 }
77
78 // wait for all threads to finish
79 foreach (var thread in threads)
80 {
81 thread.Join();
82 }
83 }
84 }
85
86 /// <summary>
87 /// This function gets called by multiple threads to do actual work.
88 /// It takes one work item at a time and calls this.CreateCabinet().
89 /// It does not return until cabinetWorkItems queue is empty
90 /// </summary>
91 private void ProcessWorkItems()
92 {
93 try
94 {
95 while (true)
96 {
97 CabinetWorkItem cabinetWorkItem;
98
99 lock (this.cabinetWorkItems)
100 {
101 // check if there are any more cabinets to create
102 if (0 == this.cabinetWorkItems.Count)
103 {
104 break;
105 }
106
107 cabinetWorkItem = this.cabinetWorkItems.Dequeue();
108 }
109
110 // create a cabinet
111 this.CreateCabinet(cabinetWorkItem);
112 }
113 }
114 catch (WixException we)
115 {
116 this.Messaging.Write(we.Error);
117 }
118 catch (Exception e)
119 {
120 this.Messaging.Write(ErrorMessages.UnexpectedException(e));
121 }
122 }
123
124 /// <summary>
125 /// Creates a cabinet using the wixcab.dll interop layer.
126 /// </summary>
127 /// <param name="cabinetWorkItem">CabinetWorkItem containing information about the cabinet to create.</param>
128 private void CreateCabinet(CabinetWorkItem cabinetWorkItem)
129 {
130 this.Messaging.Write(VerboseMessages.CreateCabinet(cabinetWorkItem.CabinetFile));
131
132 var maxCabinetSize = 0; // The value of 0 corresponds to default of 2GB which means no cabinet splitting
133 ulong maxPreCompressedSizeInBytes = 0;
134
135 if (this.MaximumCabinetSizeForLargeFileSplitting != 0)
136 {
137 // User Specified Max Cab Size for File Splitting, So Check if this cabinet has a single file larger than MaximumUncompressedFileSize
138 // If a file is larger than MaximumUncompressedFileSize, then the cabinet containing it will have only this file
139 if (1 == cabinetWorkItem.FileFacades.Count())
140 {
141 // Cabinet has Single File, Check if this is Large File than needs Splitting into Multiple cabs
142 // Get the Value for Max Uncompressed Media Size
143 maxPreCompressedSizeInBytes = (ulong)this.MaximumUncompressedMediaSize * 1024 * 1024;
144
145 var facade = cabinetWorkItem.FileFacades.First();
146
147 // If the file is larger than MaximumUncompressedFileSize set Maximum Cabinet Size for Cabinet Splitting
148 if ((ulong)facade.FileSize >= maxPreCompressedSizeInBytes)
149 {
150 maxCabinetSize = this.MaximumCabinetSizeForLargeFileSplitting;
151 }
152 }
153 }
154
155 // create the cabinet file
156 var cabinetPath = Path.GetFullPath(cabinetWorkItem.CabinetFile);
157
158 var files = cabinetWorkItem.FileFacades
159 .OrderBy(f => f.Sequence)
160 .Select(facade => facade.Hash == null ?
161 new CabinetCompressFile(facade.SourcePath, facade.Id + cabinetWorkItem.ModularizationSuffix) :
162 new CabinetCompressFile(facade.SourcePath, facade.Id + cabinetWorkItem.ModularizationSuffix, facade.Hash.HashPart1, facade.Hash.HashPart2, facade.Hash.HashPart3, facade.Hash.HashPart4))
163 .ToList();
164
165 var cab = new Cabinet(cabinetPath);
166 cab.Compress(files, cabinetWorkItem.CompressionLevel, maxCabinetSize, cabinetWorkItem.MaxThreshold);
167
168 // TODO: Handle newCabNamesCallBackAddress from compression.
169 }
170 }
171}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 using WixToolset.Core.Native;
10 using WixToolset.Data;
11 using WixToolset.Extensibility;
12 using WixToolset.Extensibility.Data;
13 using WixToolset.Extensibility.Services;
14
15 internal class CabinetResolver
16 {
17 public CabinetResolver(IServiceProvider serviceProvider, string cabCachePath, IEnumerable<IWindowsInstallerBackendBinderExtension> backendExtensions)
18 {
19 this.ServiceProvider = serviceProvider;
20
21 this.CabCachePath = cabCachePath;
22
23 this.BackendExtensions = backendExtensions;
24 }
25
26 private IServiceProvider ServiceProvider { get; }
27
28 private string CabCachePath { get; }
29
30 private IEnumerable<IWindowsInstallerBackendBinderExtension> BackendExtensions { get; }
31
32 public IResolvedCabinet ResolveCabinet(string cabinetPath, IEnumerable<IFileFacade> fileFacades)
33 {
34 var filesWithPath = fileFacades.Select(this.CreateBindFileWithPath).ToList();
35
36 IResolvedCabinet resolved = null;
37
38 foreach (var extension in this.BackendExtensions)
39 {
40 resolved = extension.ResolveCabinet(cabinetPath, filesWithPath);
41
42 if (null != resolved)
43 {
44 return resolved;
45 }
46 }
47
48 // By default cabinet should be built and moved to the suggested location.
49 resolved = this.ServiceProvider.GetService<IResolvedCabinet>();
50 resolved.BuildOption = CabinetBuildOption.BuildAndMove;
51 resolved.Path = cabinetPath;
52
53 // If a cabinet cache path was provided, change the location for the cabinet
54 // to be built to and check if there is a cabinet that can be reused.
55 if (!String.IsNullOrEmpty(this.CabCachePath))
56 {
57 var cabinetName = Path.GetFileName(cabinetPath);
58 resolved.Path = Path.Combine(this.CabCachePath, cabinetName);
59
60 if (CheckFileExists(resolved.Path))
61 {
62 // Assume that none of the following are true:
63 // 1. any files are added or removed
64 // 2. order of files changed or names changed
65 // 3. modified time changed
66 var cabinetValid = true;
67
68 var cabinet = new Cabinet(resolved.Path);
69 var fileList = cabinet.Enumerate();
70
71 if (filesWithPath.Count() != fileList.Count)
72 {
73 cabinetValid = false;
74 }
75 else
76 {
77 var i = 0;
78 foreach (var file in filesWithPath)
79 {
80 // First check that the file identifiers match because that is quick and easy.
81 var cabFileInfo = fileList[i];
82 cabinetValid = (cabFileInfo.FileId == file.Id);
83 if (cabinetValid)
84 {
85 // Still valid so ensure the file sizes are the same.
86 var fileInfo = new FileInfo(file.Path);
87 cabinetValid = (cabFileInfo.Size == fileInfo.Length);
88 if (cabinetValid)
89 {
90 // Still valid so ensure the source time stamp hasn't changed.
91 cabinetValid = cabFileInfo.SameAsDateTime(fileInfo.LastWriteTime);
92 }
93 }
94
95 if (!cabinetValid)
96 {
97 break;
98 }
99
100 i++;
101 }
102 }
103
104 resolved.BuildOption = cabinetValid ? CabinetBuildOption.Copy : CabinetBuildOption.BuildAndCopy;
105 }
106 }
107
108 return resolved;
109 }
110
111 private IBindFileWithPath CreateBindFileWithPath(IFileFacade facade)
112 {
113 var result = this.ServiceProvider.GetService<IBindFileWithPath>();
114 result.Id = facade.Id;
115 result.Path = facade.SourcePath;
116
117 return result;
118 }
119
120 private static bool CheckFileExists(string path)
121 {
122 try
123 {
124 return File.Exists(path);
125 }
126 catch (ArgumentException)
127 {
128 throw new WixException(ErrorMessages.IllegalCharactersInPath(path));
129 }
130 }
131 }
132}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System.Collections.Generic;
6 using WixToolset.Data;
7 using WixToolset.Extensibility.Data;
8
9 /// <summary>
10 /// A cabinet builder work item.
11 /// </summary>
12 internal sealed class CabinetWorkItem
13 {
14 /// <summary>
15 /// Instantiate a new CabinetWorkItem.
16 /// </summary>
17 /// <param name="fileFacades">The collection of files in this cabinet.</param>
18 /// <param name="cabinetFile">The cabinet file.</param>
19 /// <param name="maxThreshold">Maximum threshold for each cabinet.</param>
20 /// <param name="compressionLevel">The compression level of the cabinet.</param>
21 /// <param name="modularizationSuffix">Modularization suffix used when building a Merge Module.</param>
22 /// <!--<param name="binderFileManager">The binder file manager.</param>-->
23 public CabinetWorkItem(IEnumerable<IFileFacade> fileFacades, string cabinetFile, int maxThreshold, CompressionLevel compressionLevel, string modularizationSuffix /*, BinderFileManager binderFileManager*/)
24 {
25 this.CabinetFile = cabinetFile;
26 this.CompressionLevel = compressionLevel;
27 this.ModularizationSuffix = modularizationSuffix;
28 this.FileFacades = fileFacades;
29 //this.BinderFileManager = binderFileManager;
30 this.MaxThreshold = maxThreshold;
31 }
32
33 /// <summary>
34 /// Gets the cabinet file.
35 /// </summary>
36 /// <value>The cabinet file.</value>
37 public string CabinetFile { get; }
38
39 /// <summary>
40 /// Gets the compression level of the cabinet.
41 /// </summary>
42 /// <value>The compression level of the cabinet.</value>
43 public CompressionLevel CompressionLevel { get; }
44
45 /// <summary>
46 /// Gets the modularization suffix used when building a Merge Module.
47 /// </summary>
48 public string ModularizationSuffix { get; }
49
50 /// <summary>
51 /// Gets the collection of files in this cabinet.
52 /// </summary>
53 /// <value>The collection of files in this cabinet.</value>
54 public IEnumerable<IFileFacade> FileFacades { get; }
55
56 // <summary>
57 // Gets the binder file manager.
58 // </summary>
59 // <value>The binder file manager.</value>
60 //public BinderFileManager BinderFileManager { get; private set; }
61
62 /// <summary>
63 /// Gets the max threshold.
64 /// </summary>
65 /// <value>The maximum threshold for a folder in a cabinet.</value>
66 public int MaxThreshold { get; }
67 }
68}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.IO;
9 using System.Linq;
10 using System.Runtime.InteropServices;
11 using WixToolset.Data;
12 using WixToolset.Data.Symbols;
13 using WixToolset.Data.WindowsInstaller;
14 using WixToolset.Extensibility;
15 using WixToolset.Extensibility.Data;
16 using WixToolset.Extensibility.Services;
17
18 /// <summary>
19 /// Creates cabinet files.
20 /// </summary>
21 internal class CreateCabinetsCommand
22 {
23 public const int DefaultMaximumUncompressedMediaSize = 200; // Default value is 200 MB
24 public const int MaxValueOfMaxCabSizeForLargeFileSplitting = 2 * 1024; // 2048 MB (i.e. 2 GB)
25
26 private readonly List<IFileTransfer> fileTransfers;
27
28 private readonly List<ITrackedFile> trackedFiles;
29
30 private readonly FileSplitCabNamesCallback newCabNamesCallBack;
31
32 private Dictionary<string, string> lastCabinetAddedToMediaTable; // Key is First Cabinet Name, Value is Last Cabinet Added in the Split Sequence
33
34 public CreateCabinetsCommand(IServiceProvider serviceProvider, IBackendHelper backendHelper, WixMediaTemplateSymbol mediaTemplate)
35 {
36 this.fileTransfers = new List<IFileTransfer>();
37
38 this.trackedFiles = new List<ITrackedFile>();
39
40 this.newCabNamesCallBack = this.NewCabNamesCallBack;
41
42 this.ServiceProvider = serviceProvider;
43
44 this.BackendHelper = backendHelper;
45
46 this.MediaTemplate = mediaTemplate;
47 }
48
49 private IServiceProvider ServiceProvider { get; }
50
51 private IBackendHelper BackendHelper { get; }
52
53 private WixMediaTemplateSymbol MediaTemplate { get; }
54
55 /// <summary>
56 /// Sets the number of threads to use for cabinet creation.
57 /// </summary>
58 public int CabbingThreadCount { private get; set; }
59
60 public string CabCachePath { private get; set; }
61
62 public IMessaging Messaging { private get; set; }
63
64 public string IntermediateFolder { private get; set; }
65
66 /// <summary>
67 /// Sets the default compression level to use for cabinets
68 /// that don't have their compression level explicitly set.
69 /// </summary>
70 public CompressionLevel? DefaultCompressionLevel { private get; set; }
71
72 public IEnumerable<IWindowsInstallerBackendBinderExtension> BackendExtensions { private get; set; }
73
74 public WindowsInstallerData Data { private get; set; }
75
76 public string LayoutDirectory { private get; set; }
77
78 public bool Compressed { private get; set; }
79
80 public string ModularizationSuffix { private get; set; }
81
82 public Dictionary<MediaSymbol, IEnumerable<IFileFacade>> FileFacadesByCabinet { private get; set; }
83
84 public Func<MediaSymbol, string, string, string> ResolveMedia { private get; set; }
85
86 public TableDefinitionCollection TableDefinitions { private get; set; }
87
88 public IEnumerable<IFileTransfer> FileTransfers => this.fileTransfers;
89
90 public IEnumerable<ITrackedFile> TrackedFiles => this.trackedFiles;
91
92 public void Execute()
93 {
94 this.lastCabinetAddedToMediaTable = new Dictionary<string, string>();
95
96 // If the cabbing thread count wasn't provided, default the number of cabbing threads to the number of processors.
97 if (this.CabbingThreadCount <= 0)
98 {
99 this.CabbingThreadCount = this.CalculateCabbingThreadCount();
100
101 this.Messaging.Write(VerboseMessages.SetCabbingThreadCount(this.CabbingThreadCount.ToString()));
102 }
103
104 // Send Binder object to Facilitate NewCabNamesCallBack Callback
105 var cabinetBuilder = new CabinetBuilder(this.Messaging, this.CabbingThreadCount, Marshal.GetFunctionPointerForDelegate(this.newCabNamesCallBack));
106
107 // Supply Compile MediaTemplate Attributes to Cabinet Builder
108 this.GetMediaTemplateAttributes(out var maximumCabinetSizeForLargeFileSplitting, out var maximumUncompressedMediaSize);
109 cabinetBuilder.MaximumCabinetSizeForLargeFileSplitting = maximumCabinetSizeForLargeFileSplitting;
110 cabinetBuilder.MaximumUncompressedMediaSize = maximumUncompressedMediaSize;
111
112 foreach (var entry in this.FileFacadesByCabinet)
113 {
114 var mediaSymbol = entry.Key;
115 var files = entry.Value;
116 var compressionLevel = mediaSymbol.CompressionLevel ?? this.DefaultCompressionLevel ?? CompressionLevel.Medium;
117 var cabinetDir = this.ResolveMedia(mediaSymbol, mediaSymbol.Layout, this.LayoutDirectory);
118
119 var cabinetWorkItem = this.CreateCabinetWorkItem(this.Data, cabinetDir, mediaSymbol, compressionLevel, files);
120 if (null != cabinetWorkItem)
121 {
122 cabinetBuilder.Enqueue(cabinetWorkItem);
123 }
124 }
125
126 // stop processing if an error previously occurred
127 if (this.Messaging.EncounteredError)
128 {
129 return;
130 }
131
132 // create queued cabinets with multiple threads
133 cabinetBuilder.CreateQueuedCabinets();
134 if (this.Messaging.EncounteredError)
135 {
136 return;
137 }
138 }
139
140 private int CalculateCabbingThreadCount()
141 {
142 var cabbingThreadCount = Environment.ProcessorCount;
143
144 if (cabbingThreadCount <= 0)
145 {
146 cabbingThreadCount = 1; // reset to 1 when the environment variable is invalid.
147
148 this.Messaging.Write(WarningMessages.InvalidEnvironmentVariable("NUMBER_OF_PROCESSORS", Environment.ProcessorCount.ToString(), cabbingThreadCount.ToString()));
149 }
150
151 return cabbingThreadCount;
152 }
153
154 /// <summary>
155 /// Creates a work item to create a cabinet.
156 /// </summary>
157 /// <param name="data">Windows Installer data for the current database.</param>
158 /// <param name="cabinetDir">Directory to create cabinet in.</param>
159 /// <param name="mediaSymbol">Media symbol containing information about the cabinet.</param>
160 /// <param name="compressionLevel">Desired compression level.</param>
161 /// <param name="fileFacades">Collection of files in this cabinet.</param>
162 /// <returns>created CabinetWorkItem object</returns>
163 private CabinetWorkItem CreateCabinetWorkItem(WindowsInstallerData data, string cabinetDir, MediaSymbol mediaSymbol, CompressionLevel compressionLevel, IEnumerable<IFileFacade> fileFacades)
164 {
165 CabinetWorkItem cabinetWorkItem = null;
166 var tempCabinetFileX = Path.Combine(this.IntermediateFolder, mediaSymbol.Cabinet);
167
168 // check for an empty cabinet
169 if (!fileFacades.Any())
170 {
171 // Remove the leading '#' from the embedded cabinet name to make the warning easier to understand
172 var cabinetName = mediaSymbol.Cabinet.TrimStart('#');
173
174 // If building a patch, remind them to run -p for torch.
175 if (OutputType.Patch == data.Type)
176 {
177 this.Messaging.Write(WarningMessages.EmptyCabinet(mediaSymbol.SourceLineNumbers, cabinetName, true));
178 }
179 else
180 {
181 this.Messaging.Write(WarningMessages.EmptyCabinet(mediaSymbol.SourceLineNumbers, cabinetName));
182 }
183 }
184
185 var cabinetResolver = new CabinetResolver(this.ServiceProvider, this.CabCachePath, this.BackendExtensions);
186
187 var resolvedCabinet = cabinetResolver.ResolveCabinet(tempCabinetFileX, fileFacades);
188
189 // create a cabinet work item if it's not being skipped
190 if (CabinetBuildOption.BuildAndCopy == resolvedCabinet.BuildOption || CabinetBuildOption.BuildAndMove == resolvedCabinet.BuildOption)
191 {
192 // Default to the threshold for best smartcabbing (makes smallest cabinet).
193 cabinetWorkItem = new CabinetWorkItem(fileFacades, resolvedCabinet.Path, maxThreshold: 0, compressionLevel, this.ModularizationSuffix /*, this.FileManager*/);
194 }
195 else // reuse the cabinet from the cabinet cache.
196 {
197 this.Messaging.Write(VerboseMessages.ReusingCabCache(mediaSymbol.SourceLineNumbers, mediaSymbol.Cabinet, resolvedCabinet.Path));
198
199 try
200 {
201 // Ensure the cached cabinet timestamp is current to prevent perpetual incremental builds. The
202 // problematic scenario goes like this. Imagine two cabinets in the cache. Update a file that
203 // goes into one of the cabinets. One cabinet will get rebuilt, the other will be copied from
204 // the cache. Now the file (an input) has a newer timestamp than the reused cabient (an output)
205 // causing the project to look like it perpetually needs a rebuild until all of the reused
206 // cabinets get newer timestamps.
207 File.SetLastWriteTime(resolvedCabinet.Path, DateTime.Now);
208 }
209 catch (Exception e)
210 {
211 this.Messaging.Write(WarningMessages.CannotUpdateCabCache(mediaSymbol.SourceLineNumbers, resolvedCabinet.Path, e.Message));
212 }
213 }
214
215 var trackResolvedCabinet = this.BackendHelper.TrackFile(resolvedCabinet.Path, TrackedFileType.Intermediate, mediaSymbol.SourceLineNumbers);
216 this.trackedFiles.Add(trackResolvedCabinet);
217
218 if (mediaSymbol.Cabinet.StartsWith("#", StringComparison.Ordinal))
219 {
220 var streamsTable = data.EnsureTable(this.TableDefinitions["_Streams"]);
221
222 var streamRow = streamsTable.CreateRow(mediaSymbol.SourceLineNumbers);
223 streamRow[0] = mediaSymbol.Cabinet.Substring(1);
224 streamRow[1] = resolvedCabinet.Path;
225 }
226 else
227 {
228 var trackDestination = this.BackendHelper.TrackFile(Path.Combine(cabinetDir, mediaSymbol.Cabinet), TrackedFileType.Final, mediaSymbol.SourceLineNumbers);
229 this.trackedFiles.Add(trackDestination);
230
231 var transfer = this.BackendHelper.CreateFileTransfer(resolvedCabinet.Path, trackDestination.Path, resolvedCabinet.BuildOption == CabinetBuildOption.BuildAndMove, mediaSymbol.SourceLineNumbers);
232 this.fileTransfers.Add(transfer);
233 }
234
235 return cabinetWorkItem;
236 }
237
238 //private ResolvedCabinet ResolveCabinet(string cabinetPath, IEnumerable<FileFacade> fileFacades)
239 //{
240 // ResolvedCabinet resolved = null;
241
242 // List<BindFileWithPath> filesWithPath = fileFacades.Select(f => new BindFileWithPath() { Id = f.File.File, Path = f.WixFile.Source }).ToList();
243
244 // foreach (var extension in this.BackendExtensions)
245 // {
246 // resolved = extension.ResolveCabinet(cabinetPath, filesWithPath);
247 // if (null != resolved)
248 // {
249 // break;
250 // }
251 // }
252
253 // return resolved;
254 //}
255
256 /// <summary>
257 /// Delegate for Cabinet Split Callback
258 /// </summary>
259 [UnmanagedFunctionPointer(CallingConvention.StdCall)]
260 internal delegate void FileSplitCabNamesCallback([MarshalAs(UnmanagedType.LPWStr)]string firstCabName, [MarshalAs(UnmanagedType.LPWStr)]string newCabName, [MarshalAs(UnmanagedType.LPWStr)]string fileToken);
261
262 /// <summary>
263 /// Call back to Add File Transfer for new Cab and add new Cab to Media table
264 /// This callback can come from Multiple Cabinet Builder Threads and so should be thread safe
265 /// This callback will not be called in case there is no File splitting. i.e. MaximumCabinetSizeForLargeFileSplitting was not authored
266 /// </summary>
267 /// <param name="firstCabName">The name of splitting cabinet without extention e.g. "cab1".</param>
268 /// <param name="newCabinetName">The name of the new cabinet that would be formed by splitting e.g. "cab1b.cab"</param>
269 /// <param name="fileToken">The file token of the first file present in the splitting cabinet</param>
270 internal void NewCabNamesCallBack([MarshalAs(UnmanagedType.LPWStr)]string firstCabName, [MarshalAs(UnmanagedType.LPWStr)]string newCabinetName, [MarshalAs(UnmanagedType.LPWStr)]string fileToken)
271 {
272 throw new NotImplementedException();
273#if TODO_CAB_SPANNING
274 // Locking Mutex here as this callback can come from Multiple Cabinet Builder Threads
275 var mutex = new Mutex(false, "WixCabinetSplitBinderCallback");
276 try
277 {
278 if (!mutex.WaitOne(0, false)) // Check if you can get the lock
279 {
280 // Cound not get the Lock
281 this.Messaging.Write(VerboseMessages.CabinetsSplitInParallel());
282 mutex.WaitOne(); // Wait on other thread
283 }
284
285 var firstCabinetName = firstCabName + ".cab";
286 var transferAdded = false; // Used for Error Handling
287
288 // Create File Transfer for new Cabinet using transfer of Base Cabinet
289 foreach (var transfer in this.FileTransfers)
290 {
291 if (firstCabinetName.Equals(Path.GetFileName(transfer.Source), StringComparison.InvariantCultureIgnoreCase))
292 {
293 var newCabSourcePath = Path.Combine(Path.GetDirectoryName(transfer.Source), newCabinetName);
294 var newCabTargetPath = Path.Combine(Path.GetDirectoryName(transfer.Destination), newCabinetName);
295
296 var trackSource = this.BackendHelper.TrackFile(newCabSourcePath, TrackedFileType.Intermediate, transfer.SourceLineNumbers);
297 this.trackedFiles.Add(trackSource);
298
299 var trackTarget = this.BackendHelper.TrackFile(newCabTargetPath, TrackedFileType.Final, transfer.SourceLineNumbers);
300 this.trackedFiles.Add(trackTarget);
301
302 var newTransfer = this.BackendHelper.CreateFileTransfer(trackSource.Path, trackTarget.Path, transfer.Move, transfer.SourceLineNumbers);
303 this.fileTransfers.Add(newTransfer);
304
305 transferAdded = true;
306 break;
307 }
308 }
309
310 // Check if File Transfer was added
311 if (!transferAdded)
312 {
313 throw new WixException(ErrorMessages.SplitCabinetCopyRegistrationFailed(newCabinetName, firstCabinetName));
314 }
315
316 // Add the new Cabinets to media table using LastSequence of Base Cabinet
317 var mediaTable = this.Output.Tables["Media"];
318 var wixFileTable = this.Output.Tables["WixFile"];
319 var diskIDForLastSplitCabAdded = 0; // The DiskID value for the first cab in this cabinet split chain
320 var lastSequenceForLastSplitCabAdded = 0; // The LastSequence value for the first cab in this cabinet split chain
321 var lastSplitCabinetFound = false; // Used for Error Handling
322
323 var lastCabinetOfThisSequence = String.Empty;
324 // Get the Value of Last Cabinet Added in this split Sequence from Dictionary
325 if (!this.lastCabinetAddedToMediaTable.TryGetValue(firstCabinetName, out lastCabinetOfThisSequence))
326 {
327 // If there is no value for this sequence, then use first Cabinet is the last one of this split sequence
328 lastCabinetOfThisSequence = firstCabinetName;
329 }
330
331 foreach (MediaRow mediaRow in mediaTable.Rows)
332 {
333 // Get details for the Last Cabinet Added in this Split Sequence
334 if ((lastSequenceForLastSplitCabAdded == 0) && lastCabinetOfThisSequence.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase))
335 {
336 lastSequenceForLastSplitCabAdded = mediaRow.LastSequence;
337 diskIDForLastSplitCabAdded = mediaRow.DiskId;
338 lastSplitCabinetFound = true;
339 }
340
341 // Check for Name Collision for the new Cabinet added
342 if (newCabinetName.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase))
343 {
344 // Name Collision of generated Split Cabinet Name and user Specified Cab name for current row
345 throw new WixException(ErrorMessages.SplitCabinetNameCollision(newCabinetName, firstCabinetName));
346 }
347 }
348
349 // Check if the last Split Cabinet was found in the Media Table
350 if (!lastSplitCabinetFound)
351 {
352 throw new WixException(ErrorMessages.SplitCabinetInsertionFailed(newCabinetName, firstCabinetName, lastCabinetOfThisSequence));
353 }
354
355 // The new Row has to be inserted just after the last cab in this cabinet split chain according to DiskID Sort
356 // This is because the FDI Extract requires DiskID of Split Cabinets to be continuous. It Fails otherwise with
357 // Error 2350 (FDI Server Error) as next DiskID did not have the right split cabinet during extraction
358 MediaRow newMediaRow = (MediaRow)mediaTable.CreateRow(null);
359 newMediaRow.Cabinet = newCabinetName;
360 newMediaRow.DiskId = diskIDForLastSplitCabAdded + 1; // When Sorted with DiskID, this new Cabinet Row is an Insertion
361 newMediaRow.LastSequence = lastSequenceForLastSplitCabAdded;
362
363 // Now increment the DiskID for all rows that come after the newly inserted row to Ensure that DiskId is unique
364 foreach (MediaRow mediaRow in mediaTable.Rows)
365 {
366 // Check if this row comes after inserted row and it is not the new cabinet inserted row
367 if (mediaRow.DiskId >= newMediaRow.DiskId && !newCabinetName.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase))
368 {
369 mediaRow.DiskId++; // Increment DiskID
370 }
371 }
372
373 // Now Increment DiskID for All files Rows so that they refer to the right Media Row
374 foreach (WixFileRow wixFileRow in wixFileTable.Rows)
375 {
376 // Check if this row comes after inserted row and if this row is not the file that has to go into the current cabinet
377 // This check will work as we have only one large file in every splitting cabinet
378 // If we want to support splitting cabinet with more large files we need to update this code
379 if (wixFileRow.DiskId >= newMediaRow.DiskId && !wixFileRow.File.Equals(fileToken, StringComparison.InvariantCultureIgnoreCase))
380 {
381 wixFileRow.DiskId++; // Increment DiskID
382 }
383 }
384
385 // Update the Last Cabinet Added in the Split Sequence in Dictionary for future callback
386 this.lastCabinetAddedToMediaTable[firstCabinetName] = newCabinetName;
387
388 mediaTable.ValidateRows(); // Valdiates DiskDIs, throws Exception as Wix Error if validation fails
389 }
390 finally
391 {
392 // Releasing the Mutex here
393 mutex.ReleaseMutex();
394 }
395#endif
396 }
397
398
399 /// <summary>
400 /// Gets Compiler Values of MediaTemplate Attributes governing Maximum Cabinet Size after applying Environment Variable Overrides
401 /// </summary>
402 private void GetMediaTemplateAttributes(out int maxCabSizeForLargeFileSplitting, out int maxUncompressedMediaSize)
403 {
404 // Get Environment Variable Overrides for MediaTemplate Attributes governing Maximum Cabinet Size
405 var mcslfsString = Environment.GetEnvironmentVariable("WIX_MCSLFS");
406 var mumsString = Environment.GetEnvironmentVariable("WIX_MUMS");
407
408 // Supply Compile MediaTemplate Attributes to Cabinet Builder
409 if (this.MediaTemplate != null)
410 {
411 // Get the Value for Max Cab Size for File Splitting
412 var maxCabSizeForLargeFileInMB = 0;
413 try
414 {
415 // Override authored mcslfs value if environment variable is authored.
416 maxCabSizeForLargeFileInMB = !String.IsNullOrEmpty(mcslfsString) ? Int32.Parse(mcslfsString) : this.MediaTemplate.MaximumCabinetSizeForLargeFileSplitting ?? MaxValueOfMaxCabSizeForLargeFileSplitting;
417
418 var testOverFlow = (ulong)maxCabSizeForLargeFileInMB * 1024 * 1024;
419 maxCabSizeForLargeFileSplitting = maxCabSizeForLargeFileInMB;
420 }
421 catch (FormatException)
422 {
423 throw new WixException(ErrorMessages.IllegalEnvironmentVariable("WIX_MCSLFS", mcslfsString));
424 }
425 catch (OverflowException)
426 {
427 throw new WixException(ErrorMessages.MaximumCabinetSizeForLargeFileSplittingTooLarge(null, maxCabSizeForLargeFileInMB, MaxValueOfMaxCabSizeForLargeFileSplitting));
428 }
429
430 var maxPreCompressedSizeInMB = 0;
431 try
432 {
433 // Override authored mums value if environment variable is authored.
434 maxPreCompressedSizeInMB = !String.IsNullOrEmpty(mumsString) ? Int32.Parse(mumsString) : this.MediaTemplate.MaximumUncompressedMediaSize ?? DefaultMaximumUncompressedMediaSize;
435
436 var testOverFlow = (ulong)maxPreCompressedSizeInMB * 1024 * 1024;
437 maxUncompressedMediaSize = maxPreCompressedSizeInMB;
438 }
439 catch (FormatException)
440 {
441 throw new WixException(ErrorMessages.IllegalEnvironmentVariable("WIX_MUMS", mumsString));
442 }
443 catch (OverflowException)
444 {
445 throw new WixException(ErrorMessages.MaximumUncompressedMediaSizeTooLarge(null, maxPreCompressedSizeInMB));
446 }
447 }
448 else
449 {
450 maxCabSizeForLargeFileSplitting = 0;
451 maxUncompressedMediaSize = DefaultMaximumUncompressedMediaSize;
452 }
453 }
454 }
455}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.IO;
9 using WixToolset.Data;
10 using WixToolset.Data.Symbols;
11 using WixToolset.Extensibility.Data;
12
13 /// <summary>
14 /// Creates delta patches and updates the appropriate rows to point to the newly generated patches.
15 /// </summary>
16 internal class CreateDeltaPatchesCommand
17 {
18 public CreateDeltaPatchesCommand(List<IFileFacade> fileFacades, string intermediateFolder, WixPatchSymbol wixPatchId)
19 {
20 this.FileFacades = fileFacades;
21 this.IntermediateFolder = intermediateFolder;
22 this.WixPatchId = wixPatchId;
23 }
24
25 private IEnumerable<IFileFacade> FileFacades { get; }
26
27 private WixPatchSymbol WixPatchId { get; }
28
29 private string IntermediateFolder { get; }
30
31 public void Execute()
32 {
33 var optimizePatchSizeForLargeFiles = this.WixPatchId?.OptimizePatchSizeForLargeFiles ?? false;
34 var apiPatchingSymbolFlags = (PatchSymbolFlags)(this.WixPatchId?.ApiPatchingSymbolFlags ?? 0);
35
36#if TODO_PATCHING_DELTA
37 foreach (FileFacade facade in this.FileFacades)
38 {
39 if (RowOperation.Modify == facade.File.Operation &&
40 0 != (facade.WixFile.PatchAttributes & PatchAttributeType.IncludeWholeFile))
41 {
42 string deltaBase = String.Concat("delta_", facade.File.File);
43 string deltaFile = Path.Combine(this.IntermediateFolder, String.Concat(deltaBase, ".dpf"));
44 string headerFile = Path.Combine(this.IntermediateFolder, String.Concat(deltaBase, ".phd"));
45
46 bool retainRangeWarning = false;
47
48 if (PatchAPI.PatchInterop.CreateDelta(
49 deltaFile,
50 facade.WixFile.Source,
51 facade.DeltaPatchFile.Symbols,
52 facade.DeltaPatchFile.RetainOffsets,
53 new[] { facade.WixFile.PreviousSource },
54 facade.DeltaPatchFile.PreviousSymbols.Split(new[] { ';' }),
55 facade.DeltaPatchFile.PreviousIgnoreLengths.Split(new[] { ';' }),
56 facade.DeltaPatchFile.PreviousIgnoreOffsets.Split(new[] { ';' }),
57 facade.DeltaPatchFile.PreviousRetainLengths.Split(new[] { ';' }),
58 facade.DeltaPatchFile.PreviousRetainOffsets.Split(new[] { ';' }),
59 apiPatchingSymbolFlags,
60 optimizePatchSizeForLargeFiles,
61 out retainRangeWarning))
62 {
63 PatchAPI.PatchInterop.ExtractDeltaHeader(deltaFile, headerFile);
64
65 facade.WixFile.Source = deltaFile;
66 facade.WixFile.DeltaPatchHeaderSource = headerFile;
67 }
68
69 if (retainRangeWarning)
70 {
71 // TODO: get patch family to add to warning message for PatchWiz parity.
72 Messaging.Instance.OnMessage(WixWarnings.RetainRangeMismatch(facade.File.SourceLineNumbers, facade.File.File));
73 }
74 }
75 }
76#endif
77
78 throw new NotImplementedException();
79 }
80 }
81}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Globalization;
7 using System.IO;
8 using System.Text;
9 using WixToolset.Data;
10 using WixToolset.Data.WindowsInstaller;
11 using WixToolset.Extensibility.Services;
12
13 internal class CreateIdtFileCommand
14 {
15 public CreateIdtFileCommand(IMessaging messaging, Table table, int codepage, string intermediateFolder, bool keepAddedColumns)
16 {
17 this.Messaging = messaging;
18 this.Table = table;
19 this.Codepage = codepage;
20 this.IntermediateFolder = intermediateFolder;
21 this.KeepAddedColumns = keepAddedColumns;
22 }
23
24 private IMessaging Messaging { get; }
25
26 private Table Table { get; }
27
28 private int Codepage { get; set; }
29
30 private string IntermediateFolder { get; }
31
32 private bool KeepAddedColumns { get; }
33
34 public string IdtPath { get; private set; }
35
36 public void Execute()
37 {
38 // write out the table to an IDT file
39 var encoding = GetCodepageEncoding(this.Codepage);
40
41 this.IdtPath = Path.Combine(this.IntermediateFolder, String.Concat(this.Table.Name, ".idt"));
42
43 using (var idtWriter = new StreamWriter(this.IdtPath, false, encoding))
44 {
45 this.TableToIdtDefinition(this.Table, idtWriter, this.KeepAddedColumns);
46 }
47 }
48
49 private void TableToIdtDefinition(Table table, StreamWriter writer, bool keepAddedColumns)
50 {
51 if (table.Definition.Unreal)
52 {
53 return;
54 }
55
56 if (TableDefinition.MaxColumnsInRealTable < table.Definition.Columns.Length)
57 {
58 throw new WixException(ErrorMessages.TooManyColumnsInRealTable(table.Definition.Name, table.Definition.Columns.Length, TableDefinition.MaxColumnsInRealTable));
59 }
60
61 // Tack on the table header, and flush before we start writing bytes directly to the stream.
62 var header = this.TableDefinitionToIdtDefinition(table.Definition, keepAddedColumns);
63 writer.Write(header);
64 writer.Flush();
65
66 using (var binary = new BinaryWriter(writer.BaseStream, writer.Encoding, true))
67 {
68 // Create an encoding that replaces characters with question marks, and doesn't throw. We'll
69 // use this in case of errors
70 Encoding convertEncoding = Encoding.GetEncoding(writer.Encoding.CodePage);
71
72 foreach (Row row in table.Rows)
73 {
74 if (row.Redundant)
75 {
76 continue;
77 }
78
79 string rowString = this.RowToIdtDefinition(row, keepAddedColumns);
80 byte[] rowBytes;
81
82 try
83 {
84 // GetBytes will throw an exception if any character doesn't match our current encoding
85 rowBytes = writer.Encoding.GetBytes(rowString);
86 }
87 catch (EncoderFallbackException)
88 {
89 this.Messaging.Write(ErrorMessages.InvalidStringForCodepage(row.SourceLineNumbers, Convert.ToString(writer.Encoding.WindowsCodePage, CultureInfo.InvariantCulture)));
90
91 rowBytes = convertEncoding.GetBytes(rowString);
92 }
93
94 binary.Write(rowBytes, 0, rowBytes.Length);
95 }
96 }
97 }
98
99 private string TableDefinitionToIdtDefinition(TableDefinition definition, bool keepAddedColumns)
100 {
101 var first = true;
102 var columnString = new StringBuilder();
103 var dataString = new StringBuilder();
104 var tableString = new StringBuilder();
105
106 tableString.Append(definition.Name);
107 foreach (var column in definition.Columns)
108 {
109 // Conditionally keep columns added in a transform; otherwise,
110 // break because columns can only be added at the end.
111 if (column.Added && !keepAddedColumns)
112 {
113 break;
114 }
115
116 if (column.Unreal)
117 {
118 continue;
119 }
120
121 if (!first)
122 {
123 columnString.Append('\t');
124 dataString.Append('\t');
125 }
126
127 columnString.Append(column.Name);
128 dataString.Append(ColumnIdtType(column));
129
130 if (column.PrimaryKey)
131 {
132 tableString.AppendFormat("\t{0}", column.Name);
133 }
134
135 first = false;
136 }
137 columnString.Append("\r\n");
138 columnString.Append(dataString);
139 columnString.Append("\r\n");
140 columnString.Append(tableString);
141 columnString.Append("\r\n");
142
143 return columnString.ToString();
144 }
145
146 private string RowToIdtDefinition(Row row, bool keepAddedColumns)
147 {
148 var first = true;
149 var sb = new StringBuilder();
150
151 foreach (var field in row.Fields)
152 {
153 // Conditionally keep columns added in a transform; otherwise,
154 // break because columns can only be added at the end.
155 if (field.Column.Added && !keepAddedColumns)
156 {
157 break;
158 }
159
160 if (field.Column.Unreal)
161 {
162 continue;
163 }
164
165 if (first)
166 {
167 first = false;
168 }
169 else
170 {
171 sb.Append('\t');
172 }
173
174 sb.Append(this.FieldToIdtValue(field));
175 }
176 sb.Append("\r\n");
177
178 return sb.ToString();
179 }
180
181 private string FieldToIdtValue(Field field)
182 {
183 var data = field.AsString();
184
185 if (String.IsNullOrEmpty(data))
186 {
187 return data;
188 }
189
190 // Special field value idt-specific escaping.
191 return data.Replace('\t', '\x10')
192 .Replace('\r', '\x11')
193 .Replace('\n', '\x19');
194 }
195
196 private static Encoding GetCodepageEncoding(int codepage)
197 {
198 Encoding encoding;
199
200 // If UTF8 encoding, use the UTF8-specific constructor to avoid writing
201 // the byte order mark at the beginning of the file
202 if (codepage == Encoding.UTF8.CodePage)
203 {
204 encoding = new UTF8Encoding(false, true);
205 }
206 else
207 {
208 if (codepage == 0)
209 {
210 codepage = Encoding.ASCII.CodePage;
211 }
212
213 Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
214
215 encoding = Encoding.GetEncoding(codepage, new EncoderExceptionFallback(), new DecoderExceptionFallback());
216 }
217
218 return encoding;
219 }
220
221 /// <summary>
222 /// Gets the type of the column in IDT format.
223 /// </summary>
224 /// <value>IDT format for column type.</value>
225 private static string ColumnIdtType(ColumnDefinition column)
226 {
227 char typeCharacter;
228 switch (column.Type)
229 {
230 case ColumnType.Number:
231 typeCharacter = column.Nullable ? 'I' : 'i';
232 break;
233 case ColumnType.Preserved:
234 case ColumnType.String:
235 typeCharacter = column.Nullable ? 'S' : 's';
236 break;
237 case ColumnType.Localized:
238 typeCharacter = column.Nullable ? 'L' : 'l';
239 break;
240 case ColumnType.Object:
241 typeCharacter = column.Nullable ? 'V' : 'v';
242 break;
243 default:
244 throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixDataStrings.EXP_UnknownColumnType, column.Type));
245 }
246
247 return String.Concat(typeCharacter, column.Length);
248 }
249 }
250}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using WixToolset.Core.Native.Msi;
9 using WixToolset.Data;
10 using WixToolset.Data.Symbols;
11 using WixToolset.Data.WindowsInstaller;
12 using WixToolset.Data.WindowsInstaller.Rows;
13 using WixToolset.Extensibility.Services;
14
15 internal class CreateInstanceTransformsCommand
16 {
17 public CreateInstanceTransformsCommand(IntermediateSection section, WindowsInstallerData output, TableDefinitionCollection tableDefinitions, IBackendHelper backendHelper)
18 {
19 this.Section = section;
20 this.Output = output;
21 this.TableDefinitions = tableDefinitions;
22 this.BackendHelper = backendHelper;
23 }
24
25 private IntermediateSection Section { get; }
26
27 private WindowsInstallerData Output { get; }
28
29 public TableDefinitionCollection TableDefinitions { get; }
30
31 private IBackendHelper BackendHelper { get; }
32
33 public void Execute()
34 {
35 // Create and add substorages for instance transforms.
36 var wixInstanceTransformsSymbols = this.Section.Symbols.OfType<WixInstanceTransformsSymbol>();
37
38 if (wixInstanceTransformsSymbols.Any())
39 {
40 string targetProductCode = null;
41 string targetUpgradeCode = null;
42 string targetProductVersion = null;
43
44 var targetSummaryInformationTable = this.Output.Tables["_SummaryInformation"];
45 var targetPropertyTable = this.Output.Tables["Property"];
46
47 // Get the data from target database
48 foreach (var propertyRow in targetPropertyTable.Rows)
49 {
50 if ("ProductCode" == (string)propertyRow[0])
51 {
52 targetProductCode = (string)propertyRow[1];
53 }
54 else if ("ProductVersion" == (string)propertyRow[0])
55 {
56 targetProductVersion = (string)propertyRow[1];
57 }
58 else if ("UpgradeCode" == (string)propertyRow[0])
59 {
60 targetUpgradeCode = (string)propertyRow[1];
61 }
62 }
63
64 // Index the Instance Component Rows, we'll get the Components rows from the real Component table.
65 var targetInstanceComponentTable = this.Section.Symbols.OfType<WixInstanceComponentSymbol>();
66 var instanceComponentGuids = targetInstanceComponentTable.ToDictionary(t => t.Id.Id, t => (ComponentRow)null);
67
68 if (instanceComponentGuids.Any())
69 {
70 var targetComponentTable = this.Output.Tables["Component"];
71 foreach (ComponentRow componentRow in targetComponentTable.Rows)
72 {
73 var component = (string)componentRow[0];
74 if (instanceComponentGuids.ContainsKey(component))
75 {
76 instanceComponentGuids[component] = componentRow;
77 }
78 }
79 }
80
81 // Generate the instance transforms
82 foreach (var instanceSymbol in wixInstanceTransformsSymbols)
83 {
84 var instanceId = instanceSymbol.Id.Id;
85
86 var instanceTransform = new WindowsInstallerData(instanceSymbol.SourceLineNumbers);
87 instanceTransform.Type = OutputType.Transform;
88 instanceTransform.Codepage = this.Output.Codepage;
89
90 var instanceSummaryInformationTable = instanceTransform.EnsureTable(this.TableDefinitions["_SummaryInformation"]);
91 string targetPlatformAndLanguage = null;
92
93 foreach (var summaryInformationRow in targetSummaryInformationTable.Rows)
94 {
95 if (7 == (int)summaryInformationRow[0]) // PID_TEMPLATE
96 {
97 targetPlatformAndLanguage = (string)summaryInformationRow[1];
98 }
99
100 // Copy the row's data to the transform.
101 var copyOfSummaryRow = instanceSummaryInformationTable.CreateRow(summaryInformationRow.SourceLineNumbers);
102 copyOfSummaryRow[0] = summaryInformationRow[0];
103 copyOfSummaryRow[1] = summaryInformationRow[1];
104 }
105
106 // Modify the appropriate properties.
107 var propertyTable = instanceTransform.EnsureTable(this.TableDefinitions["Property"]);
108
109 // Change the ProductCode property
110 var productCode = instanceSymbol.ProductCode;
111 if ("*" == productCode)
112 {
113 productCode = this.BackendHelper.CreateGuid();
114 }
115
116 var productCodeRow = propertyTable.CreateRow(instanceSymbol.SourceLineNumbers);
117 productCodeRow.Operation = RowOperation.Modify;
118 productCodeRow.Fields[1].Modified = true;
119 productCodeRow[0] = "ProductCode";
120 productCodeRow[1] = productCode;
121
122 // Change the instance property
123 var instanceIdRow = propertyTable.CreateRow(instanceSymbol.SourceLineNumbers);
124 instanceIdRow.Operation = RowOperation.Modify;
125 instanceIdRow.Fields[1].Modified = true;
126 instanceIdRow[0] = instanceSymbol.PropertyId;
127 instanceIdRow[1] = instanceId;
128
129 if (!String.IsNullOrEmpty(instanceSymbol.ProductName))
130 {
131 // Change the ProductName property
132 var productNameRow = propertyTable.CreateRow(instanceSymbol.SourceLineNumbers);
133 productNameRow.Operation = RowOperation.Modify;
134 productNameRow.Fields[1].Modified = true;
135 productNameRow[0] = "ProductName";
136 productNameRow[1] = instanceSymbol.ProductName;
137 }
138
139 if (!String.IsNullOrEmpty(instanceSymbol.UpgradeCode))
140 {
141 // Change the UpgradeCode property
142 var upgradeCodeRow = propertyTable.CreateRow(instanceSymbol.SourceLineNumbers);
143 upgradeCodeRow.Operation = RowOperation.Modify;
144 upgradeCodeRow.Fields[1].Modified = true;
145 upgradeCodeRow[0] = "UpgradeCode";
146 upgradeCodeRow[1] = instanceSymbol.UpgradeCode;
147
148 // Change the Upgrade table
149 var targetUpgradeTable = this.Output.Tables["Upgrade"];
150 if (null != targetUpgradeTable && 0 <= targetUpgradeTable.Rows.Count)
151 {
152 var upgradeId = instanceSymbol.UpgradeCode;
153 var upgradeTable = instanceTransform.EnsureTable(this.TableDefinitions["Upgrade"]);
154 foreach (var row in targetUpgradeTable.Rows)
155 {
156 // In case they are upgrading other codes to this new product, leave the ones that don't match the
157 // Product.UpgradeCode intact.
158 if (targetUpgradeCode == (string)row[0])
159 {
160 var upgradeRow = upgradeTable.CreateRow(row.SourceLineNumbers);
161 upgradeRow.Operation = RowOperation.Add;
162 upgradeRow.Fields[0].Modified = true;
163 // I was hoping to be able to RowOperation.Modify, but that didn't appear to function.
164 // upgradeRow.Fields[0].PreviousData = (string)row[0];
165
166 // Inserting a new Upgrade record with the updated UpgradeCode
167 upgradeRow[0] = upgradeId;
168 upgradeRow[1] = row[1];
169 upgradeRow[2] = row[2];
170 upgradeRow[3] = row[3];
171 upgradeRow[4] = row[4];
172 upgradeRow[5] = row[5];
173 upgradeRow[6] = row[6];
174
175 // Delete the old row
176 var upgradeRemoveRow = upgradeTable.CreateRow(row.SourceLineNumbers);
177 upgradeRemoveRow.Operation = RowOperation.Delete;
178 upgradeRemoveRow[0] = row[0];
179 upgradeRemoveRow[1] = row[1];
180 upgradeRemoveRow[2] = row[2];
181 upgradeRemoveRow[3] = row[3];
182 upgradeRemoveRow[4] = row[4];
183 upgradeRemoveRow[5] = row[5];
184 upgradeRemoveRow[6] = row[6];
185 }
186 }
187 }
188 }
189
190 // If there are instance Components generate new GUIDs for them.
191 if (0 < instanceComponentGuids.Count)
192 {
193 var componentTable = instanceTransform.EnsureTable(this.TableDefinitions["Component"]);
194 foreach (var targetComponentRow in instanceComponentGuids.Values)
195 {
196 var guid = targetComponentRow.Guid;
197 if (!String.IsNullOrEmpty(guid))
198 {
199 var instanceComponentRow = componentTable.CreateRow(targetComponentRow.SourceLineNumbers);
200 instanceComponentRow.Operation = RowOperation.Modify;
201 instanceComponentRow.Fields[1].Modified = true;
202 instanceComponentRow[0] = targetComponentRow[0];
203 instanceComponentRow[1] = this.BackendHelper.CreateGuid(BindDatabaseCommand.WixComponentGuidNamespace, String.Concat(guid, instanceId));
204 instanceComponentRow[2] = targetComponentRow[2];
205 instanceComponentRow[3] = targetComponentRow[3];
206 instanceComponentRow[4] = targetComponentRow[4];
207 instanceComponentRow[5] = targetComponentRow[5];
208 }
209 }
210 }
211
212 // Update the summary information
213 var summaryRows = new Dictionary<int, Row>(instanceSummaryInformationTable.Rows.Count);
214 foreach (var row in instanceSummaryInformationTable.Rows)
215 {
216 summaryRows[(int)row[0]] = row;
217
218 if ((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == (int)row[0])
219 {
220 row[1] = targetPlatformAndLanguage;
221 }
222 else if ((int)SummaryInformation.Transform.ProductCodes == (int)row[0])
223 {
224 row[1] = String.Concat(targetProductCode, targetProductVersion, ';', productCode, targetProductVersion, ';', targetUpgradeCode);
225 }
226 else if ((int)SummaryInformation.Transform.ValidationFlags == (int)row[0])
227 {
228 row[1] = 0;
229 }
230 else if ((int)SummaryInformation.Transform.Security == (int)row[0])
231 {
232 row[1] = "4";
233 }
234 }
235
236 if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage))
237 {
238 var summaryRow = instanceSummaryInformationTable.CreateRow(instanceSymbol.SourceLineNumbers);
239 summaryRow[0] = (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage;
240 summaryRow[1] = targetPlatformAndLanguage;
241 }
242 else if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.ValidationFlags))
243 {
244 var summaryRow = instanceSummaryInformationTable.CreateRow(instanceSymbol.SourceLineNumbers);
245 summaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags;
246 summaryRow[1] = "0";
247 }
248 else if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.Security))
249 {
250 var summaryRow = instanceSummaryInformationTable.CreateRow(instanceSymbol.SourceLineNumbers);
251 summaryRow[0] = (int)SummaryInformation.Transform.Security;
252 summaryRow[1] = "4";
253 }
254
255 this.Output.SubStorages.Add(new SubStorage(instanceId, instanceTransform));
256 }
257 }
258 }
259 }
260}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 using WixToolset.Core.Native.Msi;
10 using WixToolset.Core.WindowsInstaller.Unbind;
11 using WixToolset.Data;
12 using WixToolset.Data.Symbols;
13 using WixToolset.Data.WindowsInstaller;
14 using WixToolset.Extensibility.Services;
15
16 internal class CreatePatchTransformsCommand
17 {
18 public CreatePatchTransformsCommand(IMessaging messaging, IBackendHelper backendHelper, Intermediate intermediate, string intermediateFolder)
19 {
20 this.Messaging = messaging;
21 this.BackendHelper = backendHelper;
22 this.Intermediate = intermediate;
23 this.IntermediateFolder = intermediateFolder;
24 }
25
26 private IMessaging Messaging { get; }
27
28 private IBackendHelper BackendHelper { get; }
29
30 private Intermediate Intermediate { get; }
31
32 private string IntermediateFolder { get; }
33
34 public IEnumerable<PatchTransform> PatchTransforms { get; private set; }
35
36 public IEnumerable<PatchTransform> Execute()
37 {
38 var patchTransforms = new List<PatchTransform>();
39
40 var symbols = this.Intermediate.Sections.SelectMany(s => s.Symbols).OfType<WixPatchBaselineSymbol>();
41
42 foreach (var symbol in symbols)
43 {
44 WindowsInstallerData transform;
45
46 if (symbol.TransformFile is null)
47 {
48 var baselineData = this.GetData(symbol.BaselineFile.Path);
49 var updateData = this.GetData(symbol.UpdateFile.Path);
50
51 var command = new GenerateTransformCommand(this.Messaging, baselineData, updateData, preserveUnchangedRows: true, showPedanticMessages: false);
52 transform = command.Execute();
53 }
54 else
55 {
56 var exportBasePath = Path.Combine(this.IntermediateFolder, "_trans"); // TODO: come up with a better path.
57
58 var command = new UnbindTransformCommand(this.Messaging, this.BackendHelper, symbol.TransformFile.Path, exportBasePath, this.IntermediateFolder);
59 transform = command.Execute();
60 }
61
62 patchTransforms.Add(new PatchTransform(symbol.Id.Id, transform));
63 }
64
65 this.PatchTransforms = patchTransforms;
66
67 return this.PatchTransforms;
68 }
69
70 private WindowsInstallerData GetData(string path)
71 {
72 var ext = Path.GetExtension(path);
73
74 if (".msi".Equals(ext, StringComparison.OrdinalIgnoreCase))
75 {
76 using (var database = new Database(path, OpenDatabase.ReadOnly))
77 {
78 var exportBasePath = Path.Combine(this.IntermediateFolder, "_msi"); // TODO: come up with a better path.
79
80 var isAdminImage = false; // TODO: need a better way to set this
81
82 var command = new UnbindDatabaseCommand(this.Messaging, this.BackendHelper, database, path, OutputType.Product, exportBasePath, this.IntermediateFolder, isAdminImage, suppressDemodularization: true, skipSummaryInfo: true);
83 return command.Execute();
84 }
85 }
86 else // assume .wixpdb (or .wixout)
87 {
88 var data = WindowsInstallerData.Load(path, true);
89 return data;
90 }
91 }
92 }
93}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using WixToolset.Data;
9 using WixToolset.Data.Symbols;
10
11 internal class CreateSpecialPropertiesCommand
12 {
13 public CreateSpecialPropertiesCommand(IntermediateSection section)
14 {
15 this.Section = section;
16 }
17
18 private IntermediateSection Section { get; }
19
20 public void Execute()
21 {
22 // Create lists of the properties that contribute to the special lists of properties.
23 var adminProperties = new SortedSet<string>();
24 var secureProperties = new SortedSet<string>();
25 var hiddenProperties = new SortedSet<string>();
26
27 foreach (var wixPropertyRow in this.Section.Symbols.OfType<WixPropertySymbol>())
28 {
29 if (wixPropertyRow.Admin)
30 {
31 adminProperties.Add(wixPropertyRow.PropertyRef);
32 }
33
34 if (wixPropertyRow.Hidden)
35 {
36 hiddenProperties.Add(wixPropertyRow.PropertyRef);
37 }
38
39 if (wixPropertyRow.Secure)
40 {
41 secureProperties.Add(wixPropertyRow.PropertyRef);
42 }
43 }
44
45 // Hide properties for in-script custom actions that have HideTarget set.
46 var hideTargetCustomActions = this.Section.Symbols.OfType<CustomActionSymbol>().Where(
47 ca => ca.Hidden
48 && (ca.ExecutionType == CustomActionExecutionType.Deferred
49 || ca.ExecutionType == CustomActionExecutionType.Commit
50 || ca.ExecutionType == CustomActionExecutionType.Rollback))
51 .Select(ca => ca.Id.Id);
52 hiddenProperties.UnionWith(hideTargetCustomActions);
53
54 // Ensure upgrade action properties are secure.
55 var actionProperties = this.Section.Symbols.OfType<UpgradeSymbol>().Select(u => u.ActionProperty);
56 secureProperties.UnionWith(actionProperties);
57
58 if (0 < adminProperties.Count)
59 {
60 this.Section.AddSymbol(new PropertySymbol(null, new Identifier(AccessModifier.Section, "AdminProperties"))
61 {
62 Value = String.Join(";", adminProperties),
63 });
64 }
65
66 if (0 < secureProperties.Count)
67 {
68 this.Section.AddSymbol(new PropertySymbol(null, new Identifier(AccessModifier.Section, "SecureCustomProperties"))
69 {
70 Value = String.Join(";", secureProperties),
71 });
72 }
73
74 if (0 < hiddenProperties.Count)
75 {
76 this.Section.AddSymbol(new PropertySymbol(null, new Identifier(AccessModifier.Section, "MsiHiddenProperties"))
77 {
78 Value = String.Join(";", hiddenProperties)
79 });
80 }
81 }
82 }
83}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.IO;
9 using System.Linq;
10 using System.Security.Cryptography;
11 using System.Text;
12 using WixToolset.Data;
13 using WixToolset.Data.Symbols;
14 using WixToolset.Data.WindowsInstaller;
15 using WixToolset.Data.WindowsInstaller.Rows;
16 using WixToolset.Extensibility;
17 using WixToolset.Extensibility.Services;
18
19 internal class CreateWindowsInstallerDataFromIRCommand
20 {
21 private static readonly char[] PathSeparatorChars = new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar };
22
23 public CreateWindowsInstallerDataFromIRCommand(IMessaging messaging, IntermediateSection section, TableDefinitionCollection tableDefinitions, int codepage, IEnumerable<IWindowsInstallerBackendBinderExtension> backendExtensions, IWindowsInstallerBackendHelper backendHelper)
24 {
25 this.Messaging = messaging;
26 this.Section = section;
27 this.TableDefinitions = tableDefinitions;
28 this.Codepage = codepage;
29 this.BackendExtensions = backendExtensions;
30 this.BackendHelper = backendHelper;
31 this.GeneratedShortNames = new Dictionary<string, List<FileSymbol>>();
32 }
33
34 private IEnumerable<IWindowsInstallerBackendBinderExtension> BackendExtensions { get; }
35
36 private IWindowsInstallerBackendHelper BackendHelper { get; }
37
38 private IMessaging Messaging { get; }
39
40 private TableDefinitionCollection TableDefinitions { get; }
41
42 private int Codepage { get; }
43
44 private IntermediateSection Section { get; }
45
46 private Dictionary<string, List<FileSymbol>> GeneratedShortNames { get; }
47
48 public WindowsInstallerData Data { get; private set; }
49
50 public WindowsInstallerData Execute()
51 {
52 this.Data = new WindowsInstallerData(this.Section.Symbols.First().SourceLineNumbers)
53 {
54 Codepage = this.Codepage,
55 Type = SectionTypeToOutputType(this.Section.Type)
56 };
57
58 this.AddSectionToData();
59
60 return this.Data;
61 }
62
63 private void AddSectionToData()
64 {
65 var cellsByTableAndRowId = new Dictionary<string, List<WixCustomTableCellSymbol>>();
66
67 foreach (var symbol in this.Section.Symbols)
68 {
69 var unknownSymbol = false;
70 switch (symbol.Definition.Type)
71 {
72 case SymbolDefinitionType.AppSearch:
73 this.AddSymbolDefaultly(symbol);
74 this.Data.EnsureTable(this.TableDefinitions["Signature"]);
75 break;
76
77 case SymbolDefinitionType.Assembly:
78 this.AddAssemblySymbol((AssemblySymbol)symbol);
79 break;
80
81 case SymbolDefinitionType.BBControl:
82 this.AddBBControlSymbol((BBControlSymbol)symbol);
83 break;
84
85 case SymbolDefinitionType.Class:
86 this.AddClassSymbol((ClassSymbol)symbol);
87 break;
88
89 case SymbolDefinitionType.Control:
90 this.AddControlSymbol((ControlSymbol)symbol);
91 break;
92
93 case SymbolDefinitionType.ControlEvent:
94 this.AddControlEventSymbol((ControlEventSymbol)symbol);
95 break;
96
97 case SymbolDefinitionType.Component:
98 this.AddComponentSymbol((ComponentSymbol)symbol);
99 break;
100
101 case SymbolDefinitionType.CustomAction:
102 this.AddCustomActionSymbol((CustomActionSymbol)symbol);
103 break;
104
105 case SymbolDefinitionType.Dialog:
106 this.AddDialogSymbol((DialogSymbol)symbol);
107 break;
108
109 case SymbolDefinitionType.Directory:
110 this.AddDirectorySymbol((DirectorySymbol)symbol);
111 break;
112
113 case SymbolDefinitionType.DuplicateFile:
114 this.AddDuplicateFileSymbol((DuplicateFileSymbol)symbol);
115 break;
116
117 case SymbolDefinitionType.Environment:
118 this.AddEnvironmentSymbol((EnvironmentSymbol)symbol);
119 break;
120
121 case SymbolDefinitionType.Error:
122 this.AddErrorSymbol((ErrorSymbol)symbol);
123 break;
124
125 case SymbolDefinitionType.Feature:
126 this.AddFeatureSymbol((FeatureSymbol)symbol);
127 break;
128
129 case SymbolDefinitionType.File:
130 this.AddFileSymbol((FileSymbol)symbol);
131 break;
132
133 case SymbolDefinitionType.IniFile:
134 this.AddIniFileSymbol((IniFileSymbol)symbol);
135 break;
136
137 case SymbolDefinitionType.IniLocator:
138 this.AddIniLocatorSymbol((IniLocatorSymbol)symbol);
139 break;
140
141 case SymbolDefinitionType.Media:
142 this.AddMediaSymbol((MediaSymbol)symbol);
143 break;
144
145 case SymbolDefinitionType.ModuleConfiguration:
146 this.AddModuleConfigurationSymbol((ModuleConfigurationSymbol)symbol);
147 this.EnsureModuleIgnoredTable(symbol, "ModuleConfiguration");
148 break;
149
150 case SymbolDefinitionType.ModuleSubstitution:
151 this.EnsureModuleIgnoredTable(symbol, "ModuleSubstitution");
152 break;
153
154 case SymbolDefinitionType.MsiEmbeddedUI:
155 this.AddMsiEmbeddedUISymbol((MsiEmbeddedUISymbol)symbol);
156 break;
157
158 case SymbolDefinitionType.MsiServiceConfig:
159 this.AddMsiServiceConfigSymbol((MsiServiceConfigSymbol)symbol);
160 break;
161
162 case SymbolDefinitionType.MsiServiceConfigFailureActions:
163 this.AddMsiServiceConfigFailureActionsSymbol((MsiServiceConfigFailureActionsSymbol)symbol);
164 break;
165
166 case SymbolDefinitionType.MoveFile:
167 this.AddMoveFileSymbol((MoveFileSymbol)symbol);
168 break;
169
170 case SymbolDefinitionType.ProgId:
171 this.AddSymbolDefaultly(symbol);
172 this.Data.EnsureTable(this.TableDefinitions["Extension"]);
173 break;
174
175 case SymbolDefinitionType.Property:
176 this.AddPropertySymbol((PropertySymbol)symbol);
177 break;
178
179 case SymbolDefinitionType.RemoveFile:
180 this.AddRemoveFileSymbol((RemoveFileSymbol)symbol);
181 break;
182
183 case SymbolDefinitionType.Registry:
184 this.AddRegistrySymbol((RegistrySymbol)symbol);
185 break;
186
187 case SymbolDefinitionType.RegLocator:
188 this.AddRegLocatorSymbol((RegLocatorSymbol)symbol);
189 break;
190
191 case SymbolDefinitionType.RemoveRegistry:
192 this.AddRemoveRegistrySymbol((RemoveRegistrySymbol)symbol);
193 break;
194
195 case SymbolDefinitionType.ServiceControl:
196 this.AddServiceControlSymbol((ServiceControlSymbol)symbol);
197 break;
198
199 case SymbolDefinitionType.ServiceInstall:
200 this.AddServiceInstallSymbol((ServiceInstallSymbol)symbol);
201 break;
202
203 case SymbolDefinitionType.Shortcut:
204 this.AddShortcutSymbol((ShortcutSymbol)symbol);
205 break;
206
207 case SymbolDefinitionType.TextStyle:
208 this.AddTextStyleSymbol((TextStyleSymbol)symbol);
209 break;
210
211 case SymbolDefinitionType.Upgrade:
212 this.AddUpgradeSymbol((UpgradeSymbol)symbol);
213 break;
214
215 case SymbolDefinitionType.WixAction:
216 this.AddWixActionSymbol((WixActionSymbol)symbol);
217 break;
218
219 case SymbolDefinitionType.WixCustomTableCell:
220 this.IndexCustomTableCellSymbol((WixCustomTableCellSymbol)symbol, cellsByTableAndRowId);
221 break;
222
223 case SymbolDefinitionType.WixEnsureTable:
224 this.AddWixEnsureTableSymbol((WixEnsureTableSymbol)symbol);
225 break;
226
227 case SymbolDefinitionType.WixPackage:
228 this.AddWixPackageSymbol((WixPackageSymbol)symbol);
229 break;
230
231 // Symbols used internally and are not added to the output.
232 case SymbolDefinitionType.WixBuildInfo:
233 case SymbolDefinitionType.WixBindUpdatedFiles:
234 case SymbolDefinitionType.WixComponentGroup:
235 case SymbolDefinitionType.WixComplexReference:
236 case SymbolDefinitionType.WixDeltaPatchFile:
237 case SymbolDefinitionType.WixDeltaPatchSymbolPaths:
238 case SymbolDefinitionType.WixFragment:
239 case SymbolDefinitionType.WixFeatureGroup:
240 case SymbolDefinitionType.WixInstanceComponent:
241 case SymbolDefinitionType.WixInstanceTransforms:
242 case SymbolDefinitionType.WixFeatureModules:
243 case SymbolDefinitionType.WixGroup:
244 case SymbolDefinitionType.WixMediaTemplate:
245 case SymbolDefinitionType.WixMerge:
246 case SymbolDefinitionType.WixOrdering:
247 case SymbolDefinitionType.WixPatchBaseline:
248 case SymbolDefinitionType.WixPatchFamilyGroup:
249 case SymbolDefinitionType.WixPatch:
250 case SymbolDefinitionType.WixPatchRef:
251 case SymbolDefinitionType.WixPatchTarget:
252 case SymbolDefinitionType.WixProperty:
253 case SymbolDefinitionType.WixProductTag:
254 case SymbolDefinitionType.WixSimpleReference:
255 case SymbolDefinitionType.WixSuppressAction:
256 case SymbolDefinitionType.WixSuppressModularization:
257 case SymbolDefinitionType.WixUI:
258 case SymbolDefinitionType.WixVariable:
259 break;
260
261 // Already processed by LoadTableDefinitions.
262 case SymbolDefinitionType.WixCustomTable:
263 case SymbolDefinitionType.WixCustomTableColumn:
264 break;
265
266 case SymbolDefinitionType.MustBeFromAnExtension:
267 unknownSymbol = !this.AddSymbolFromExtension(symbol);
268 break;
269
270 default:
271 unknownSymbol = !this.AddSymbolDefaultly(symbol);
272 break;
273 }
274
275 if (unknownSymbol)
276 {
277 this.Messaging.Write(WarningMessages.SymbolNotTranslatedToOutput(symbol));
278 }
279 }
280
281 this.AddIndexedCellSymbols(cellsByTableAndRowId);
282 this.EnsureRequiredTables();
283 this.ReportGeneratedShortFileNameConflicts();
284 this.ReportIllegalTables();
285 this.ReportMismatchedModularizations();
286 this.ReportWindowsInstallerDataInconsistencies();
287 }
288
289 private void AddAssemblySymbol(AssemblySymbol symbol)
290 {
291 var attributes = symbol.Type == AssemblyType.Win32Assembly ? 1 : (int?)null;
292
293 var row = this.CreateRow(symbol, "MsiAssembly");
294 row[0] = symbol.ComponentRef;
295 row[1] = symbol.FeatureRef;
296 row[2] = symbol.ManifestFileRef;
297 row[3] = symbol.ApplicationFileRef;
298 row[4] = attributes;
299 }
300
301 private void AddBBControlSymbol(BBControlSymbol symbol)
302 {
303 var attributes = symbol.Attributes;
304 attributes |= symbol.Enabled ? WindowsInstallerConstants.MsidbControlAttributesEnabled : 0;
305 attributes |= symbol.Indirect ? WindowsInstallerConstants.MsidbControlAttributesIndirect : 0;
306 attributes |= symbol.Integer ? WindowsInstallerConstants.MsidbControlAttributesInteger : 0;
307 attributes |= symbol.LeftScroll ? WindowsInstallerConstants.MsidbControlAttributesLeftScroll : 0;
308 attributes |= symbol.RightAligned ? WindowsInstallerConstants.MsidbControlAttributesRightAligned : 0;
309 attributes |= symbol.RightToLeft ? WindowsInstallerConstants.MsidbControlAttributesRTLRO : 0;
310 attributes |= symbol.Sunken ? WindowsInstallerConstants.MsidbControlAttributesSunken : 0;
311 attributes |= symbol.Visible ? WindowsInstallerConstants.MsidbControlAttributesVisible : 0;
312
313 var row = this.CreateRow(symbol, "BBControl");
314 row[0] = symbol.BillboardRef;
315 row[1] = symbol.BBControl;
316 row[2] = symbol.Type;
317 row[3] = symbol.X;
318 row[4] = symbol.Y;
319 row[5] = symbol.Width;
320 row[6] = symbol.Height;
321 row[7] = attributes;
322 row[8] = symbol.Text;
323 }
324
325 private void AddClassSymbol(ClassSymbol symbol)
326 {
327 var row = this.CreateRow(symbol, "Class");
328 row[0] = symbol.CLSID;
329 row[1] = symbol.Context;
330 row[2] = symbol.ComponentRef;
331 row[3] = symbol.DefaultProgIdRef;
332 row[4] = symbol.Description;
333 row[5] = symbol.AppIdRef;
334 row[6] = symbol.FileTypeMask;
335 row[7] = symbol.IconRef;
336 row[8] = symbol.IconIndex;
337 row[9] = symbol.DefInprocHandler;
338 row[10] = symbol.Argument;
339 row[11] = symbol.FeatureRef;
340 row[12] = symbol.RelativePath ? (int?)1 : null;
341 }
342
343 private void AddControlSymbol(ControlSymbol symbol)
344 {
345 var text = symbol.Text;
346 var attributes = symbol.Attributes;
347 attributes |= symbol.Enabled ? WindowsInstallerConstants.MsidbControlAttributesEnabled : 0;
348 attributes |= symbol.Indirect ? WindowsInstallerConstants.MsidbControlAttributesIndirect : 0;
349 attributes |= symbol.Integer ? WindowsInstallerConstants.MsidbControlAttributesInteger : 0;
350 attributes |= symbol.LeftScroll ? WindowsInstallerConstants.MsidbControlAttributesLeftScroll : 0;
351 attributes |= symbol.RightAligned ? WindowsInstallerConstants.MsidbControlAttributesRightAligned : 0;
352 attributes |= symbol.RightToLeft ? WindowsInstallerConstants.MsidbControlAttributesRTLRO : 0;
353 attributes |= symbol.Sunken ? WindowsInstallerConstants.MsidbControlAttributesSunken : 0;
354 attributes |= symbol.Visible ? WindowsInstallerConstants.MsidbControlAttributesVisible : 0;
355
356 // If we're tracking disk space, and this is a non-FormatSize Text control,
357 // and the text attribute starts with '[' and ends with ']', add a space.
358 // It is not necessary for the whole string to be a property, just those
359 // two characters matter.
360 if (symbol.TrackDiskSpace &&
361 "Text" == symbol.Type &&
362 WindowsInstallerConstants.MsidbControlAttributesFormatSize != (attributes & WindowsInstallerConstants.MsidbControlAttributesFormatSize) &&
363 null != text && text.StartsWith("[", StringComparison.Ordinal) && text.EndsWith("]", StringComparison.Ordinal))
364 {
365 text = String.Concat(text, " ");
366 }
367
368 var row = this.CreateRow(symbol, "Control");
369 row[0] = symbol.DialogRef;
370 row[1] = symbol.Control;
371 row[2] = symbol.Type;
372 row[3] = symbol.X;
373 row[4] = symbol.Y;
374 row[5] = symbol.Width;
375 row[6] = symbol.Height;
376 row[7] = attributes;
377 row[8] = symbol.Property;
378 row[9] = text;
379 row[10] = symbol.NextControlRef;
380 row[11] = symbol.Help;
381 }
382
383 private void AddControlEventSymbol(ControlEventSymbol symbol)
384 {
385 var row = this.CreateRow(symbol, "ControlEvent");
386 row[0] = symbol.DialogRef;
387 row[1] = symbol.ControlRef;
388 row[2] = symbol.Event;
389 row[3] = symbol.Argument;
390 row[4] = String.IsNullOrEmpty(symbol.Condition) ? "1" : symbol.Condition;
391 row[5] = symbol.Ordering;
392 }
393
394 private void AddComponentSymbol(ComponentSymbol symbol)
395 {
396 var attributes = ComponentLocation.Either == symbol.Location ? WindowsInstallerConstants.MsidbComponentAttributesOptional : 0;
397 attributes |= ComponentLocation.SourceOnly == symbol.Location ? WindowsInstallerConstants.MsidbComponentAttributesSourceOnly : 0;
398 attributes |= ComponentKeyPathType.Registry == symbol.KeyPathType ? WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath : 0;
399 attributes |= ComponentKeyPathType.OdbcDataSource == symbol.KeyPathType ? WindowsInstallerConstants.MsidbComponentAttributesODBCDataSource : 0;
400 attributes |= symbol.DisableRegistryReflection ? WindowsInstallerConstants.MsidbComponentAttributesDisableRegistryReflection : 0;
401 attributes |= symbol.NeverOverwrite ? WindowsInstallerConstants.MsidbComponentAttributesNeverOverwrite : 0;
402 attributes |= symbol.Permanent ? WindowsInstallerConstants.MsidbComponentAttributesPermanent : 0;
403 attributes |= symbol.SharedDllRefCount ? WindowsInstallerConstants.MsidbComponentAttributesSharedDllRefCount : 0;
404 attributes |= symbol.Shared ? WindowsInstallerConstants.MsidbComponentAttributesShared : 0;
405 attributes |= symbol.Transitive ? WindowsInstallerConstants.MsidbComponentAttributesTransitive : 0;
406 attributes |= symbol.UninstallWhenSuperseded ? WindowsInstallerConstants.MsidbComponentAttributesUninstallOnSupersedence : 0;
407 attributes |= symbol.Win64 ? WindowsInstallerConstants.MsidbComponentAttributes64bit : 0;
408
409 var row = this.CreateRow(symbol, "Component");
410 row[0] = symbol.Id.Id;
411 row[1] = symbol.ComponentId;
412 row[2] = symbol.DirectoryRef;
413 row[3] = attributes;
414 row[4] = symbol.Condition;
415 row[5] = symbol.KeyPath;
416 }
417
418 private void AddCustomActionSymbol(CustomActionSymbol symbol)
419 {
420 var type = symbol.Win64 ? WindowsInstallerConstants.MsidbCustomActionType64BitScript : 0;
421 type |= symbol.IgnoreResult ? WindowsInstallerConstants.MsidbCustomActionTypeContinue : 0;
422 type |= symbol.Hidden ? WindowsInstallerConstants.MsidbCustomActionTypeHideTarget : 0;
423 type |= symbol.Async ? WindowsInstallerConstants.MsidbCustomActionTypeAsync : 0;
424 type |= CustomActionExecutionType.FirstSequence == symbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeFirstSequence : 0;
425 type |= CustomActionExecutionType.OncePerProcess == symbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeOncePerProcess : 0;
426 type |= CustomActionExecutionType.ClientRepeat == symbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeClientRepeat : 0;
427 type |= CustomActionExecutionType.Deferred == symbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeInScript : 0;
428 type |= CustomActionExecutionType.Rollback == symbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeInScript | WindowsInstallerConstants.MsidbCustomActionTypeRollback : 0;
429 type |= CustomActionExecutionType.Commit == symbol.ExecutionType ? WindowsInstallerConstants.MsidbCustomActionTypeInScript | WindowsInstallerConstants.MsidbCustomActionTypeCommit : 0;
430 type |= CustomActionSourceType.File == symbol.SourceType ? WindowsInstallerConstants.MsidbCustomActionTypeSourceFile : 0;
431 type |= CustomActionSourceType.Directory == symbol.SourceType ? WindowsInstallerConstants.MsidbCustomActionTypeDirectory : 0;
432 type |= CustomActionSourceType.Property == symbol.SourceType ? WindowsInstallerConstants.MsidbCustomActionTypeProperty : 0;
433 type |= CustomActionTargetType.Dll == symbol.TargetType ? WindowsInstallerConstants.MsidbCustomActionTypeDll : 0;
434 type |= CustomActionTargetType.Exe == symbol.TargetType ? WindowsInstallerConstants.MsidbCustomActionTypeExe : 0;
435 type |= CustomActionTargetType.TextData == symbol.TargetType ? WindowsInstallerConstants.MsidbCustomActionTypeTextData : 0;
436 type |= CustomActionTargetType.JScript == symbol.TargetType ? WindowsInstallerConstants.MsidbCustomActionTypeJScript : 0;
437 type |= CustomActionTargetType.VBScript == symbol.TargetType ? WindowsInstallerConstants.MsidbCustomActionTypeVBScript : 0;
438
439 if (WindowsInstallerConstants.MsidbCustomActionTypeInScript == (type & WindowsInstallerConstants.MsidbCustomActionTypeInScript))
440 {
441 type |= symbol.Impersonate ? 0 : WindowsInstallerConstants.MsidbCustomActionTypeNoImpersonate;
442 type |= symbol.TSAware ? WindowsInstallerConstants.MsidbCustomActionTypeTSAware : 0;
443 }
444
445 var row = this.CreateRow(symbol, "CustomAction");
446 row[0] = symbol.Id.Id;
447 row[1] = type;
448 row[2] = symbol.Source;
449 row[3] = symbol.Target;
450 row[4] = symbol.PatchUninstall ? (int?)WindowsInstallerConstants.MsidbCustomActionTypePatchUninstall : null;
451
452 if (OutputType.Module == this.Data.Type)
453 {
454 this.Data.EnsureTable(this.TableDefinitions["AdminExecuteSequence"]);
455 this.Data.EnsureTable(this.TableDefinitions["AdminUISequence"]);
456 this.Data.EnsureTable(this.TableDefinitions["AdvtExecuteSequence"]);
457 this.Data.EnsureTable(this.TableDefinitions["InstallExecuteSequence"]);
458 this.Data.EnsureTable(this.TableDefinitions["InstallUISequence"]);
459 }
460 }
461
462 private void AddDialogSymbol(DialogSymbol symbol)
463 {
464 var attributes = symbol.Visible ? WindowsInstallerConstants.MsidbDialogAttributesVisible : 0;
465 attributes |= symbol.Modal ? WindowsInstallerConstants.MsidbDialogAttributesModal : 0;
466 attributes |= symbol.Minimize ? WindowsInstallerConstants.MsidbDialogAttributesMinimize : 0;
467 attributes |= symbol.CustomPalette ? WindowsInstallerConstants.MsidbDialogAttributesUseCustomPalette : 0;
468 attributes |= symbol.ErrorDialog ? WindowsInstallerConstants.MsidbDialogAttributesError : 0;
469 attributes |= symbol.LeftScroll ? WindowsInstallerConstants.MsidbDialogAttributesLeftScroll : 0;
470 attributes |= symbol.KeepModeless ? WindowsInstallerConstants.MsidbDialogAttributesKeepModeless : 0;
471 attributes |= symbol.RightAligned ? WindowsInstallerConstants.MsidbDialogAttributesRightAligned : 0;
472 attributes |= symbol.RightToLeft ? WindowsInstallerConstants.MsidbDialogAttributesRTLRO : 0;
473 attributes |= symbol.SystemModal ? WindowsInstallerConstants.MsidbDialogAttributesSysModal : 0;
474 attributes |= symbol.TrackDiskSpace ? WindowsInstallerConstants.MsidbDialogAttributesTrackDiskSpace : 0;
475
476 var row = this.CreateRow(symbol, "Dialog");
477 row[0] = symbol.Id.Id;
478 row[1] = symbol.HCentering;
479 row[2] = symbol.VCentering;
480 row[3] = symbol.Width;
481 row[4] = symbol.Height;
482 row[5] = attributes;
483 row[6] = symbol.Title;
484 row[7] = symbol.FirstControlRef;
485 row[8] = symbol.DefaultControlRef;
486 row[9] = symbol.CancelControlRef;
487
488 this.Data.EnsureTable(this.TableDefinitions["ListBox"]);
489 }
490
491 private void AddDirectorySymbol(DirectorySymbol symbol)
492 {
493 (var name, var parentDir) = this.AddDirectorySubdirectories(symbol);
494
495 var shortName = symbol.ShortName;
496 var sourceShortname = symbol.SourceShortName;
497
498 if (String.IsNullOrEmpty(shortName) && name != null && name != "." && name != "SourceDir" && !this.BackendHelper.IsValidShortFilename(name, false))
499 {
500 shortName = this.CreateShortName(name, false, "Directory", symbol.ParentDirectoryRef);
501 }
502
503 if (String.IsNullOrEmpty(sourceShortname) && !String.IsNullOrEmpty(symbol.SourceName) && !this.BackendHelper.IsValidShortFilename(symbol.SourceName, false))
504 {
505 sourceShortname = this.CreateShortName(symbol.SourceName, false, "Directory", symbol.ParentDirectoryRef);
506 }
507
508 var sourceName = CreateMsiFilename(sourceShortname, symbol.SourceName);
509 var targetName = CreateMsiFilename(shortName, name);
510
511 if (String.IsNullOrEmpty(targetName))
512 {
513 targetName = ".";
514 }
515
516 var defaultDir = String.IsNullOrEmpty(sourceName) || sourceName == targetName ? targetName : targetName + ":" + sourceName;
517
518 var row = this.CreateRow(symbol, "Directory");
519 row[0] = symbol.Id.Id;
520 row[1] = parentDir;
521 row[2] = defaultDir;
522
523 if (OutputType.Module == this.Data.Type)
524 {
525 var directoryId = symbol.Id.Id;
526
527 if (WindowsInstallerStandard.IsStandardDirectory(directoryId))
528 {
529 // If the directory table contains references to standard windows folders
530 // mergemod.dll will add customactions to set the MSM directory to
531 // the same directory as the standard windows folder and will add references to
532 // custom action to all the standard sequence tables. A problem will occur
533 // if the MSI does not have these tables as mergemod.dll does not add these
534 // tables to the MSI if absent. This code adds the tables in case mergemod.dll
535 // needs them.
536 this.Data.EnsureTable(this.TableDefinitions["CustomAction"]);
537 this.Data.EnsureTable(this.TableDefinitions["AdminExecuteSequence"]);
538 this.Data.EnsureTable(this.TableDefinitions["AdminUISequence"]);
539 this.Data.EnsureTable(this.TableDefinitions["AdvtExecuteSequence"]);
540 this.Data.EnsureTable(this.TableDefinitions["InstallExecuteSequence"]);
541 this.Data.EnsureTable(this.TableDefinitions["InstallUISequence"]);
542 }
543 else
544 {
545 foreach (var standardDirectory in WindowsInstallerStandard.StandardDirectories())
546 {
547 if (directoryId.StartsWith(standardDirectory.Id.Id, StringComparison.Ordinal))
548 {
549 this.Messaging.Write(WarningMessages.StandardDirectoryConflictInMergeModule(symbol.SourceLineNumbers, directoryId, standardDirectory.Id.Id));
550 }
551 }
552 }
553 }
554 }
555
556 private void AddDuplicateFileSymbol(DuplicateFileSymbol symbol)
557 {
558 var name = symbol.DestinationName;
559 if (null == symbol.DestinationShortName && null != name && !this.BackendHelper.IsValidShortFilename(name, false))
560 {
561 symbol.DestinationShortName = this.CreateShortName(name, true, "CopyFile", symbol.ComponentRef, symbol.FileRef);
562 }
563
564 var row = this.CreateRow(symbol, "DuplicateFile");
565 row[0] = symbol.Id.Id;
566 row[1] = symbol.ComponentRef;
567 row[2] = symbol.FileRef;
568 row[3] = CreateMsiFilename(symbol.DestinationShortName, symbol.DestinationName);
569 row[4] = symbol.DestinationFolder;
570 }
571
572 private void AddEnvironmentSymbol(EnvironmentSymbol symbol)
573 {
574 var action = String.Empty;
575 var system = symbol.System ? "*" : String.Empty;
576 var uninstall = symbol.Permanent ? String.Empty : "-";
577 var value = symbol.Value;
578
579 switch (symbol.Action)
580 {
581 case EnvironmentActionType.Create:
582 action = "+";
583 break;
584 case EnvironmentActionType.Set:
585 action = "=";
586 break;
587 case EnvironmentActionType.Remove:
588 action = "!";
589 break;
590 }
591
592 switch (symbol.Part)
593 {
594 case EnvironmentPartType.First:
595 value = String.Concat(value, symbol.Separator, "[~]");
596 break;
597 case EnvironmentPartType.Last:
598 value = String.Concat("[~]", symbol.Separator, value);
599 break;
600 }
601
602 var row = this.CreateRow(symbol, "Environment");
603 row[0] = symbol.Id.Id;
604 row[1] = String.Concat(action, uninstall, system, symbol.Name);
605 row[2] = value;
606 row[3] = symbol.ComponentRef;
607 }
608
609 private void AddErrorSymbol(ErrorSymbol symbol)
610 {
611 var row = this.CreateRow(symbol, "Error");
612 row[0] = Convert.ToInt32(symbol.Id.Id);
613 row[1] = symbol.Message;
614 }
615
616 private void AddFeatureSymbol(FeatureSymbol symbol)
617 {
618 var attributes = symbol.DisallowAbsent ? WindowsInstallerConstants.MsidbFeatureAttributesUIDisallowAbsent : 0;
619 attributes |= symbol.DisallowAdvertise ? WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise : 0;
620 attributes |= FeatureInstallDefault.FollowParent == symbol.InstallDefault ? WindowsInstallerConstants.MsidbFeatureAttributesFollowParent : 0;
621 attributes |= FeatureInstallDefault.Source == symbol.InstallDefault ? WindowsInstallerConstants.MsidbFeatureAttributesFavorSource : 0;
622 attributes |= FeatureTypicalDefault.Advertise == symbol.TypicalDefault ? WindowsInstallerConstants.MsidbFeatureAttributesFavorAdvertise : 0;
623
624 var row = this.CreateRow(symbol, "Feature");
625 row[0] = symbol.Id.Id;
626 row[1] = symbol.ParentFeatureRef;
627 row[2] = symbol.Title;
628 row[3] = symbol.Description;
629 row[4] = symbol.Display;
630 row[5] = symbol.Level;
631 row[6] = symbol.DirectoryRef;
632 row[7] = attributes;
633 }
634
635 private void AddFileSymbol(FileSymbol symbol)
636 {
637 var name = symbol.Name;
638 if (null == symbol.ShortName && null != name && !this.BackendHelper.IsValidShortFilename(name, false))
639 {
640 symbol.ShortName = this.CreateShortName(name, true, "File", symbol.DirectoryRef);
641
642 if (!this.GeneratedShortNames.TryGetValue(symbol.ShortName, out var potentialConflicts))
643 {
644 potentialConflicts = new List<FileSymbol>();
645 this.GeneratedShortNames.Add(symbol.ShortName, potentialConflicts);
646 }
647
648 potentialConflicts.Add(symbol);
649 }
650
651 var row = (FileRow)this.CreateRow(symbol, "File");
652 row.File = symbol.Id.Id;
653 row.Component = symbol.ComponentRef;
654 row.FileName = CreateMsiFilename(symbol.ShortName, name);
655 row.FileSize = symbol.FileSize;
656 row.Version = symbol.Version;
657 row.Language = symbol.Language;
658 row.DiskId = symbol.DiskId ?? 1; // TODO: is 1 the correct thing to default here
659 row.Sequence = symbol.Sequence;
660 row.Source = symbol.Source.Path;
661
662 var attributes = (symbol.Attributes & FileSymbolAttributes.Checksum) == FileSymbolAttributes.Checksum ? WindowsInstallerConstants.MsidbFileAttributesChecksum : 0;
663 attributes |= (symbol.Attributes & FileSymbolAttributes.Compressed) == FileSymbolAttributes.Compressed ? WindowsInstallerConstants.MsidbFileAttributesCompressed : 0;
664 attributes |= (symbol.Attributes & FileSymbolAttributes.Uncompressed) == FileSymbolAttributes.Uncompressed ? WindowsInstallerConstants.MsidbFileAttributesNoncompressed : 0;
665 attributes |= (symbol.Attributes & FileSymbolAttributes.Hidden) == FileSymbolAttributes.Hidden ? WindowsInstallerConstants.MsidbFileAttributesHidden : 0;
666 attributes |= (symbol.Attributes & FileSymbolAttributes.ReadOnly) == FileSymbolAttributes.ReadOnly ? WindowsInstallerConstants.MsidbFileAttributesReadOnly : 0;
667 attributes |= (symbol.Attributes & FileSymbolAttributes.System) == FileSymbolAttributes.System ? WindowsInstallerConstants.MsidbFileAttributesSystem : 0;
668 attributes |= (symbol.Attributes & FileSymbolAttributes.Vital) == FileSymbolAttributes.Vital ? WindowsInstallerConstants.MsidbFileAttributesVital : 0;
669 row.Attributes = attributes;
670
671 if (symbol.FontTitle != null)
672 {
673 var fontRow = this.CreateRow(symbol, "Font");
674 fontRow[0] = symbol.Id.Id;
675 fontRow[1] = symbol.FontTitle;
676 }
677
678 if (symbol.SelfRegCost.HasValue)
679 {
680 var selfRegRow = this.CreateRow(symbol, "SelfReg");
681 selfRegRow[0] = symbol.Id.Id;
682 selfRegRow[1] = symbol.SelfRegCost.Value;
683 }
684 }
685
686 private void AddIniFileSymbol(IniFileSymbol symbol)
687 {
688 var tableName = (IniFileActionType.AddLine == symbol.Action || IniFileActionType.AddTag == symbol.Action || IniFileActionType.CreateLine == symbol.Action) ? "IniFile" : "RemoveIniFile";
689
690 var name = symbol.FileName;
691 if (null == symbol.ShortFileName && null != name && !this.BackendHelper.IsValidShortFilename(name, false))
692 {
693 symbol.ShortFileName = this.CreateShortName(name, true, "IniFile", symbol.ComponentRef);
694 }
695
696 var row = this.CreateRow(symbol, tableName);
697 row[0] = symbol.Id.Id;
698 row[1] = CreateMsiFilename(symbol.ShortFileName, name);
699 row[2] = symbol.DirProperty;
700 row[3] = symbol.Section;
701 row[4] = symbol.Key;
702 row[5] = symbol.Value;
703 row[6] = symbol.Action;
704 row[7] = symbol.ComponentRef;
705 }
706
707 private void AddIniLocatorSymbol(IniLocatorSymbol symbol)
708 {
709 var name = symbol.FileName;
710 if (null == symbol.ShortFileName && null != name && !this.BackendHelper.IsValidShortFilename(name, false))
711 {
712 symbol.ShortFileName = this.CreateShortName(name, true, "IniFileSearch");
713 }
714
715 var row = this.CreateRow(symbol, "IniLocator");
716 row[0] = symbol.Id.Id;
717 row[1] = CreateMsiFilename(symbol.ShortFileName, name);
718 row[2] = symbol.Section;
719 row[3] = symbol.Key;
720 row[4] = symbol.Field;
721 row[5] = symbol.Type;
722 }
723
724 private void AddMediaSymbol(MediaSymbol symbol)
725 {
726 if (this.Section.Type != SectionType.Module)
727 {
728 var row = (MediaRow)this.CreateRow(symbol, "Media");
729 row.DiskId = symbol.DiskId;
730 row.LastSequence = symbol.LastSequence ?? 0;
731 row.DiskPrompt = symbol.DiskPrompt;
732 row.Cabinet = symbol.Cabinet;
733 row.VolumeLabel = symbol.VolumeLabel;
734 row.Source = symbol.Source;
735 }
736 }
737
738 private void AddModuleConfigurationSymbol(ModuleConfigurationSymbol symbol)
739 {
740 var row = this.CreateRow(symbol, "ModuleConfiguration");
741 row[0] = symbol.Id.Id;
742 row[1] = symbol.Format;
743 row[2] = symbol.Type;
744 row[3] = symbol.ContextData;
745 row[4] = symbol.DefaultValue;
746 row[5] = (symbol.KeyNoOrphan ? WindowsInstallerConstants.MsidbMsmConfigurableOptionKeyNoOrphan : 0) |
747 (symbol.NonNullable ? WindowsInstallerConstants.MsidbMsmConfigurableOptionNonNullable : 0);
748 row[6] = symbol.DisplayName;
749 row[7] = symbol.Description;
750 row[8] = symbol.HelpLocation;
751 row[9] = symbol.HelpKeyword;
752 }
753
754 private void AddMsiEmbeddedUISymbol(MsiEmbeddedUISymbol symbol)
755 {
756 var attributes = symbol.EntryPoint ? WindowsInstallerConstants.MsidbEmbeddedUI : 0;
757 attributes |= symbol.SupportsBasicUI ? WindowsInstallerConstants.MsidbEmbeddedHandlesBasic : 0;
758
759 var row = this.CreateRow(symbol, "MsiEmbeddedUI");
760 row[0] = symbol.Id.Id;
761 row[1] = symbol.FileName;
762 row[2] = attributes;
763 row[3] = symbol.MessageFilter;
764 row[4] = symbol.Source;
765 }
766
767 private void AddMsiServiceConfigSymbol(MsiServiceConfigSymbol symbol)
768 {
769 var events = symbol.OnInstall ? WindowsInstallerConstants.MsidbServiceConfigEventInstall : 0;
770 events |= symbol.OnReinstall ? WindowsInstallerConstants.MsidbServiceConfigEventReinstall : 0;
771 events |= symbol.OnUninstall ? WindowsInstallerConstants.MsidbServiceConfigEventUninstall : 0;
772
773 var row = this.CreateRow(symbol, "MsiServiceConfigFailureActions");
774 row[0] = symbol.Id.Id;
775 row[1] = symbol.Name;
776 row[2] = events;
777 row[3] = symbol.ConfigType;
778 row[4] = symbol.Argument;
779 row[5] = symbol.ComponentRef;
780 }
781
782 private void AddMsiServiceConfigFailureActionsSymbol(MsiServiceConfigFailureActionsSymbol symbol)
783 {
784 var events = symbol.OnInstall ? WindowsInstallerConstants.MsidbServiceConfigEventInstall : 0;
785 events |= symbol.OnReinstall ? WindowsInstallerConstants.MsidbServiceConfigEventReinstall : 0;
786 events |= symbol.OnUninstall ? WindowsInstallerConstants.MsidbServiceConfigEventUninstall : 0;
787
788 var row = this.CreateRow(symbol, "MsiServiceConfig");
789 row[0] = symbol.Id.Id;
790 row[1] = symbol.Name;
791 row[2] = events;
792 row[3] = symbol.ResetPeriod.HasValue ? symbol.ResetPeriod : null;
793 row[4] = symbol.RebootMessage ?? "[~]";
794 row[5] = symbol.Command ?? "[~]";
795 row[6] = symbol.Actions;
796 row[7] = symbol.DelayActions;
797 row[8] = symbol.ComponentRef;
798 }
799
800 private void AddMoveFileSymbol(MoveFileSymbol symbol)
801 {
802 var name = symbol.DestinationName;
803 if (null == symbol.DestinationShortName && null != name && !this.BackendHelper.IsValidShortFilename(name, false))
804 {
805 symbol.DestinationShortName = this.CreateShortName(name, true, "MoveFile", symbol.ComponentRef);
806 }
807
808 var row = this.CreateRow(symbol, "MoveFile");
809 row[0] = symbol.Id.Id;
810 row[1] = symbol.ComponentRef;
811 row[2] = symbol.SourceName;
812 row[3] = CreateMsiFilename(symbol.DestinationShortName, symbol.DestinationName);
813 row[4] = symbol.SourceFolder;
814 row[5] = symbol.DestFolder;
815 row[6] = symbol.Delete ? WindowsInstallerConstants.MsidbMoveFileOptionsMove : 0;
816 }
817
818 private void AddPropertySymbol(PropertySymbol symbol)
819 {
820 if (String.IsNullOrEmpty(symbol.Value))
821 {
822 return;
823 }
824
825 var row = (PropertyRow)this.CreateRow(symbol, "Property");
826 row.Property = symbol.Id.Id;
827 row.Value = symbol.Value;
828 }
829
830 private void AddRemoveFileSymbol(RemoveFileSymbol symbol)
831 {
832 var name = symbol.FileName;
833 if (null == symbol.ShortFileName && null != name && !this.BackendHelper.IsValidShortFilename(name, false))
834 {
835 symbol.ShortFileName = this.CreateShortName(name, true, "RemoveFile", symbol.ComponentRef);
836 }
837
838 var installMode = symbol.OnInstall == true ? WindowsInstallerConstants.MsidbRemoveFileInstallModeOnInstall : 0;
839 installMode |= symbol.OnUninstall == true ? WindowsInstallerConstants.MsidbRemoveFileInstallModeOnRemove : 0;
840
841 var row = this.CreateRow(symbol, "RemoveFile");
842 row[0] = symbol.Id.Id;
843 row[1] = symbol.ComponentRef;
844 row[2] = CreateMsiFilename(symbol.ShortFileName, symbol.FileName);
845 row[3] = symbol.DirPropertyRef;
846 row[4] = installMode;
847 }
848
849 private void AddRegistrySymbol(RegistrySymbol symbol)
850 {
851 var value = symbol.Value;
852
853 switch (symbol.ValueType)
854 {
855 case RegistryValueType.Binary:
856 value = String.Concat("#x", value);
857 break;
858 case RegistryValueType.Expandable:
859 value = String.Concat("#%", value);
860 break;
861 case RegistryValueType.Integer:
862 value = String.Concat("#", value);
863 break;
864 case RegistryValueType.MultiString:
865 switch (symbol.ValueAction)
866 {
867 case RegistryValueActionType.Append:
868 value = String.Concat("[~]", value);
869 break;
870 case RegistryValueActionType.Prepend:
871 value = String.Concat(value, "[~]");
872 break;
873 case RegistryValueActionType.Write:
874 default:
875 if (null != value && -1 == value.IndexOf("[~]", StringComparison.Ordinal))
876 {
877 value = String.Format(CultureInfo.InvariantCulture, "[~]{0}[~]", value);
878 }
879 break;
880 }
881 break;
882 case RegistryValueType.String:
883 // escape the leading '#' character for string registry keys
884 if (null != value && value.StartsWith("#", StringComparison.Ordinal))
885 {
886 value = String.Concat("#", value);
887 }
888 break;
889 }
890
891 var row = this.CreateRow(symbol, "Registry");
892 row[0] = symbol.Id.Id;
893 row[1] = symbol.Root;
894 row[2] = symbol.Key;
895 row[3] = symbol.Name;
896 row[4] = value;
897 row[5] = symbol.ComponentRef;
898 }
899
900 private void AddRegLocatorSymbol(RegLocatorSymbol symbol)
901 {
902 var type = (int)symbol.Type;
903 type |= symbol.Win64 ? WindowsInstallerConstants.MsidbLocatorType64bit : 0;
904
905 var row = this.CreateRow(symbol, "RegLocator");
906 row[0] = symbol.Id.Id;
907 row[1] = symbol.Root;
908 row[2] = symbol.Key;
909 row[3] = symbol.Name;
910 row[4] = type;
911 }
912
913 private void AddRemoveRegistrySymbol(RemoveRegistrySymbol symbol)
914 {
915 if (symbol.Action == RemoveRegistryActionType.RemoveOnInstall)
916 {
917 var row = this.CreateRow(symbol, "RemoveRegistry");
918 row[0] = symbol.Id.Id;
919 row[1] = symbol.Root;
920 row[2] = symbol.Key;
921 row[3] = symbol.Name;
922 row[4] = symbol.ComponentRef;
923 }
924 else // Registry table is used to remove registry keys on uninstall.
925 {
926 var row = this.CreateRow(symbol, "Registry");
927 row[0] = symbol.Id.Id;
928 row[1] = symbol.Root;
929 row[2] = symbol.Key;
930 row[3] = symbol.Name;
931 row[5] = symbol.ComponentRef;
932 }
933 }
934
935 private void AddServiceControlSymbol(ServiceControlSymbol symbol)
936 {
937 var events = symbol.InstallRemove ? WindowsInstallerConstants.MsidbServiceControlEventDelete : 0;
938 events |= symbol.UninstallRemove ? WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete : 0;
939 events |= symbol.InstallStart ? WindowsInstallerConstants.MsidbServiceControlEventStart : 0;
940 events |= symbol.UninstallStart ? WindowsInstallerConstants.MsidbServiceControlEventUninstallStart : 0;
941 events |= symbol.InstallStop ? WindowsInstallerConstants.MsidbServiceControlEventStop : 0;
942 events |= symbol.UninstallStop ? WindowsInstallerConstants.MsidbServiceControlEventUninstallStop : 0;
943
944 var row = this.CreateRow(symbol, "ServiceControl");
945 row[0] = symbol.Id.Id;
946 row[1] = symbol.Name;
947 row[2] = events;
948 row[3] = symbol.Arguments;
949 if (symbol.Wait.HasValue)
950 {
951 row[4] = symbol.Wait.Value ? 1 : 0;
952 }
953 row[5] = symbol.ComponentRef;
954 }
955
956 private void AddServiceInstallSymbol(ServiceInstallSymbol symbol)
957 {
958 var errorControl = (int)symbol.ErrorControl;
959 errorControl |= symbol.Vital ? WindowsInstallerConstants.MsidbServiceInstallErrorControlVital : 0;
960
961 var serviceType = (int)symbol.ServiceType;
962 serviceType |= symbol.Interactive ? WindowsInstallerConstants.MsidbServiceInstallInteractive : 0;
963
964 var row = this.CreateRow(symbol, "ServiceInstall");
965 row[0] = symbol.Id.Id;
966 row[1] = symbol.Name;
967 row[2] = symbol.DisplayName;
968 row[3] = serviceType;
969 row[4] = (int)symbol.StartType;
970 row[5] = errorControl;
971 row[6] = symbol.LoadOrderGroup;
972 row[7] = symbol.Dependencies;
973 row[8] = symbol.StartName;
974 row[9] = symbol.Password;
975 row[10] = symbol.Arguments;
976 row[11] = symbol.ComponentRef;
977 row[12] = symbol.Description;
978 }
979
980 private void AddShortcutSymbol(ShortcutSymbol symbol)
981 {
982 var name = symbol.Name;
983 if (null == symbol.ShortName && null != name && !this.BackendHelper.IsValidShortFilename(name, false))
984 {
985 symbol.ShortName = this.CreateShortName(name, true, "Shortcut", symbol.ComponentRef, symbol.DirectoryRef);
986 }
987
988 var row = this.CreateRow(symbol, "Shortcut");
989 row[0] = symbol.Id.Id;
990 row[1] = symbol.DirectoryRef;
991 row[2] = CreateMsiFilename(symbol.ShortName, name);
992 row[3] = symbol.ComponentRef;
993 row[4] = symbol.Target;
994 row[5] = symbol.Arguments;
995 row[6] = symbol.Description;
996 row[7] = symbol.Hotkey;
997 row[8] = symbol.IconRef;
998 row[9] = symbol.IconIndex;
999 row[10] = (int?)symbol.Show;
1000 row[11] = symbol.WorkingDirectory;
1001 row[12] = symbol.DisplayResourceDll;
1002 row[13] = symbol.DisplayResourceId;
1003 row[14] = symbol.DescriptionResourceDll;
1004 row[15] = symbol.DescriptionResourceId;
1005 }
1006
1007 private void AddTextStyleSymbol(TextStyleSymbol symbol)
1008 {
1009 var styleBits = symbol.Bold ? WindowsInstallerConstants.MsidbTextStyleStyleBitsBold : 0;
1010 styleBits |= symbol.Italic ? WindowsInstallerConstants.MsidbTextStyleStyleBitsItalic : 0;
1011 styleBits |= symbol.Strike ? WindowsInstallerConstants.MsidbTextStyleStyleBitsStrike : 0;
1012 styleBits |= symbol.Underline ? WindowsInstallerConstants.MsidbTextStyleStyleBitsUnderline : 0;
1013
1014 long? color = null;
1015
1016 if (symbol.Red.HasValue || symbol.Green.HasValue || symbol.Blue.HasValue)
1017 {
1018 color = symbol.Red ?? 0;
1019 color += (long)(symbol.Green ?? 0) * 256;
1020 color += (long)(symbol.Blue ?? 0) * 65536;
1021 }
1022
1023 var row = this.CreateRow(symbol, "TextStyle");
1024 row[0] = symbol.Id.Id;
1025 row[1] = symbol.FaceName;
1026 row[2] = symbol.Size;
1027 row[3] = color;
1028 row[4] = styleBits == 0 ? null : (int?)styleBits;
1029 }
1030
1031 private void AddUpgradeSymbol(UpgradeSymbol symbol)
1032 {
1033 var row = (UpgradeRow)this.CreateRow(symbol, "Upgrade");
1034 row.UpgradeCode = symbol.UpgradeCode;
1035 row.VersionMin = symbol.VersionMin;
1036 row.VersionMax = symbol.VersionMax;
1037 row.Language = symbol.Language;
1038 row.Remove = symbol.Remove;
1039 row.ActionProperty = symbol.ActionProperty;
1040
1041 var attributes = symbol.MigrateFeatures ? WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures : 0;
1042 attributes |= symbol.OnlyDetect ? WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect : 0;
1043 attributes |= symbol.IgnoreRemoveFailures ? WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure : 0;
1044 attributes |= symbol.VersionMinInclusive ? WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive : 0;
1045 attributes |= symbol.VersionMaxInclusive ? WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive : 0;
1046 attributes |= symbol.ExcludeLanguages ? WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive : 0;
1047 row.Attributes = attributes;
1048 }
1049
1050 private void AddWixActionSymbol(WixActionSymbol symbol)
1051 {
1052 // Get the table definition for the action (and ensure the proper table exists for a module).
1053 string sequenceTableName = null;
1054 switch (symbol.SequenceTable)
1055 {
1056 case SequenceTable.AdminExecuteSequence:
1057 if (OutputType.Module == this.Data.Type)
1058 {
1059 this.Data.EnsureTable(this.TableDefinitions["AdminExecuteSequence"]);
1060 sequenceTableName = "ModuleAdminExecuteSequence";
1061 }
1062 else
1063 {
1064 sequenceTableName = "AdminExecuteSequence";
1065 }
1066 break;
1067 case SequenceTable.AdminUISequence:
1068 if (OutputType.Module == this.Data.Type)
1069 {
1070 this.Data.EnsureTable(this.TableDefinitions["AdminUISequence"]);
1071 sequenceTableName = "ModuleAdminUISequence";
1072 }
1073 else
1074 {
1075 sequenceTableName = "AdminUISequence";
1076 }
1077 break;
1078 case SequenceTable.AdvertiseExecuteSequence:
1079 if (OutputType.Module == this.Data.Type)
1080 {
1081 this.Data.EnsureTable(this.TableDefinitions["AdvtExecuteSequence"]);
1082 sequenceTableName = "ModuleAdvtExecuteSequence";
1083 }
1084 else
1085 {
1086 sequenceTableName = "AdvtExecuteSequence";
1087 }
1088 break;
1089 case SequenceTable.InstallExecuteSequence:
1090 if (OutputType.Module == this.Data.Type)
1091 {
1092 this.Data.EnsureTable(this.TableDefinitions["InstallExecuteSequence"]);
1093 sequenceTableName = "ModuleInstallExecuteSequence";
1094 }
1095 else
1096 {
1097 sequenceTableName = "InstallExecuteSequence";
1098 }
1099 break;
1100 case SequenceTable.InstallUISequence:
1101 if (OutputType.Module == this.Data.Type)
1102 {
1103 this.Data.EnsureTable(this.TableDefinitions["InstallUISequence"]);
1104 sequenceTableName = "ModuleInstallUISequence";
1105 }
1106 else
1107 {
1108 sequenceTableName = "InstallUISequence";
1109 }
1110 break;
1111 }
1112
1113 // create the action sequence row in the output
1114 var row = this.CreateRow(symbol, sequenceTableName);
1115
1116 if (SectionType.Module == this.Section.Type)
1117 {
1118 row[0] = symbol.Action;
1119 if (0 != symbol.Sequence)
1120 {
1121 row[1] = symbol.Sequence;
1122 }
1123 else
1124 {
1125 var after = (null == symbol.Before);
1126 row[2] = after ? symbol.After : symbol.Before;
1127 row[3] = after ? 1 : 0;
1128 }
1129 row[4] = symbol.Condition;
1130 }
1131 else
1132 {
1133 row[0] = symbol.Action;
1134 row[1] = symbol.Condition;
1135 row[2] = symbol.Sequence;
1136 }
1137 }
1138
1139 private void IndexCustomTableCellSymbol(WixCustomTableCellSymbol wixCustomTableCellSymbol, Dictionary<string, List<WixCustomTableCellSymbol>> cellsByTableAndRowId)
1140 {
1141 var tableAndRowId = wixCustomTableCellSymbol.TableRef + "/" + wixCustomTableCellSymbol.RowId;
1142 if (!cellsByTableAndRowId.TryGetValue(tableAndRowId, out var cells))
1143 {
1144 cells = new List<WixCustomTableCellSymbol>();
1145 cellsByTableAndRowId.Add(tableAndRowId, cells);
1146 }
1147
1148 cells.Add(wixCustomTableCellSymbol);
1149 }
1150
1151 private void AddIndexedCellSymbols(Dictionary<string, List<WixCustomTableCellSymbol>> cellsByTableAndRowId)
1152 {
1153 foreach (var rowOfCells in cellsByTableAndRowId.Values)
1154 {
1155 var firstCellSymbol = rowOfCells[0];
1156 var customTableDefinition = this.TableDefinitions[firstCellSymbol.TableRef];
1157
1158 if (customTableDefinition.Unreal)
1159 {
1160 continue;
1161 }
1162
1163 var customRow = this.CreateRow(firstCellSymbol, customTableDefinition);
1164 var customRowFieldsByColumnName = customRow.Fields.ToDictionary(f => f.Column.Name);
1165
1166#if TODO // SectionId seems like a good thing to preserve.
1167 customRow.SectionId = symbol.SectionId;
1168#endif
1169 foreach (var cell in rowOfCells)
1170 {
1171 var data = cell.Data;
1172
1173 if (customRowFieldsByColumnName.TryGetValue(cell.ColumnRef, out var rowField))
1174 {
1175 if (!String.IsNullOrEmpty(data))
1176 {
1177 if (rowField.Column.Type == ColumnType.Number)
1178 {
1179 try
1180 {
1181 rowField.Data = Convert.ToInt32(data, CultureInfo.InvariantCulture);
1182 }
1183 catch (FormatException)
1184 {
1185 this.Messaging.Write(ErrorMessages.IllegalIntegerValue(cell.SourceLineNumbers, rowField.Column.Name, customTableDefinition.Name, data));
1186 }
1187 catch (OverflowException)
1188 {
1189 this.Messaging.Write(ErrorMessages.IllegalIntegerValue(cell.SourceLineNumbers, rowField.Column.Name, customTableDefinition.Name, data));
1190 }
1191 }
1192 else if (rowField.Column.Category == ColumnCategory.Identifier)
1193 {
1194 if (this.BackendHelper.IsValidIdentifier(data) || this.BackendHelper.IsValidBinderVariable(data) || ColumnCategory.Formatted == rowField.Column.Category)
1195 {
1196 rowField.Data = data;
1197 }
1198 else
1199 {
1200 this.Messaging.Write(ErrorMessages.IllegalIdentifier(cell.SourceLineNumbers, "Data", data));
1201 }
1202 }
1203 else
1204 {
1205 rowField.Data = data;
1206 }
1207 }
1208 }
1209 else
1210 {
1211 this.Messaging.Write(ErrorMessages.UnexpectedCustomTableColumn(cell.SourceLineNumbers, cell.ColumnRef));
1212 }
1213 }
1214
1215 for (var i = 0; i < customTableDefinition.Columns.Length; ++i)
1216 {
1217 if (!customTableDefinition.Columns[i].Nullable && (null == customRow.Fields[i].Data || 0 == customRow.Fields[i].Data.ToString().Length))
1218 {
1219 this.Messaging.Write(ErrorMessages.NoDataForColumn(firstCellSymbol.SourceLineNumbers, customTableDefinition.Columns[i].Name, customTableDefinition.Name));
1220 }
1221 }
1222 }
1223 }
1224
1225 private void AddWixEnsureTableSymbol(WixEnsureTableSymbol symbol)
1226 {
1227 var tableDefinition = this.TableDefinitions[symbol.Table];
1228 this.Data.EnsureTable(tableDefinition);
1229 }
1230
1231 private void AddWixPackageSymbol(WixPackageSymbol symbol)
1232 {
1233 // TODO: Remove the following from the compiler and do it here instead.
1234 //this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "Manufacturer"), manufacturer, false, false, false, true);
1235 //this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductCode"), productCode, false, false, false, true);
1236 //this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductLanguage"), productLanguage, false, false, false, true);
1237 //this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductName"), this.activeName, false, false, false, true);
1238 //this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductVersion"), version, false, false, false, true);
1239 //if (null != upgradeCode)
1240 //{
1241 // this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "UpgradeCode"), upgradeCode, false, false, false, true);
1242 //}
1243
1244 //if (isPerMachine)
1245 //{
1246 // this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ALLUSERS"), "1", false, false, false, false);
1247 //}
1248 }
1249
1250 private bool AddSymbolFromExtension(IntermediateSymbol symbol)
1251 {
1252 foreach (var extension in this.BackendExtensions)
1253 {
1254 if (extension.TryProcessSymbol(this.Section, symbol, this.Data, this.TableDefinitions))
1255 {
1256 return true;
1257 }
1258 }
1259
1260 return false;
1261 }
1262
1263 private bool AddSymbolDefaultly(IntermediateSymbol symbol) =>
1264 this.BackendHelper.TryAddSymbolToMatchingTableDefinitions(this.Section, symbol, this.Data, this.TableDefinitions);
1265
1266 private void EnsureModuleIgnoredTable(IntermediateSymbol symbol, string ignoredTable)
1267 {
1268 var tableDefinition = this.TableDefinitions["ModuleIgnoreTable"];
1269 var table = this.Data.EnsureTable(tableDefinition);
1270 if (!table.Rows.Any(r => r.FieldAsString(0) == ignoredTable))
1271 {
1272 var row = this.CreateRow(symbol, tableDefinition);
1273 row[0] = ignoredTable;
1274 }
1275 }
1276
1277 private (string, string) AddDirectorySubdirectories(DirectorySymbol symbol)
1278 {
1279 var directory = symbol.Name.Trim(PathSeparatorChars);
1280 var parentDir = symbol.ParentDirectoryRef ?? (symbol.Id.Id == "TARGETDIR" ? null : "TARGETDIR");
1281
1282 var start = 0;
1283 var end = directory.IndexOfAny(PathSeparatorChars);
1284 var path = String.Empty;
1285
1286 while (start <= end)
1287 {
1288 var subdirectoryName = directory.Substring(start, end - start);
1289
1290 if (!String.IsNullOrEmpty(subdirectoryName))
1291 {
1292 path = Path.Combine(path, subdirectoryName);
1293
1294 var id = this.BackendHelper.GenerateIdentifier("d", symbol.ParentDirectoryRef, path);
1295 var shortnameSubdirectory = this.BackendHelper.IsValidShortFilename(subdirectoryName, false) ? null : this.CreateShortName(subdirectoryName, false, "Directory", symbol.ParentDirectoryRef);
1296
1297 var subdirectoryRow = this.CreateRow(symbol, "Directory");
1298 subdirectoryRow[0] = id;
1299 subdirectoryRow[1] = parentDir;
1300 subdirectoryRow[2] = CreateMsiFilename(shortnameSubdirectory, subdirectoryName);
1301
1302 parentDir = id;
1303 }
1304
1305 start = end + 1;
1306 end = symbol.Name.IndexOfAny(PathSeparatorChars, start);
1307 }
1308
1309 var name = (start == 0) ? directory : directory.Substring(start);
1310
1311 return (name, parentDir);
1312 }
1313
1314 private void EnsureRequiredTables()
1315 {
1316 // check for missing table and add them or display an error as appropriate
1317 switch (this.Data.Type)
1318 {
1319 case OutputType.Module:
1320 this.Data.EnsureTable(this.TableDefinitions["Component"]);
1321 this.Data.EnsureTable(this.TableDefinitions["Directory"]);
1322 this.Data.EnsureTable(this.TableDefinitions["FeatureComponents"]);
1323 this.Data.EnsureTable(this.TableDefinitions["File"]);
1324 this.Data.EnsureTable(this.TableDefinitions["ModuleComponents"]);
1325 this.Data.EnsureTable(this.TableDefinitions["ModuleSignature"]);
1326 break;
1327
1328 case OutputType.PatchCreation:
1329 var imageFamiliesCount = this.Data.Tables["ImageFamilies"]?.Rows.Count ?? 0;
1330 var targetImagesCount = this.Data.Tables["TargetImages"]?.Rows.Count ?? 0;
1331 var upgradedImagesCount = this.Data.Tables["UpgradedImages"]?.Rows.Count ?? 0;
1332
1333 if (imageFamiliesCount < 1)
1334 {
1335 this.Messaging.Write(ErrorMessages.ExpectedRowInPatchCreationPackage("ImageFamilies"));
1336 }
1337
1338 if (targetImagesCount < 1)
1339 {
1340 this.Messaging.Write(ErrorMessages.ExpectedRowInPatchCreationPackage("TargetImages"));
1341 }
1342
1343 if (upgradedImagesCount < 1)
1344 {
1345 this.Messaging.Write(ErrorMessages.ExpectedRowInPatchCreationPackage("UpgradedImages"));
1346 }
1347
1348 this.Data.EnsureTable(this.TableDefinitions["Properties"]);
1349 break;
1350
1351 case OutputType.Product:
1352 this.Data.EnsureTable(this.TableDefinitions["File"]);
1353 this.Data.EnsureTable(this.TableDefinitions["Media"]);
1354 break;
1355 }
1356 }
1357
1358 private void ReportGeneratedShortFileNameConflicts()
1359 {
1360 foreach (var conflicts in this.GeneratedShortNames.Values.Where(l => l.Count > 1))
1361 {
1362 this.Messaging.Write(WarningMessages.GeneratedShortFileNameConflict(conflicts[0].SourceLineNumbers, conflicts[0].ShortName));
1363 for (var i = 1; i < conflicts.Count; ++i)
1364 {
1365 this.Messaging.Write(WarningMessages.GeneratedShortFileNameConflict2(conflicts[i].SourceLineNumbers));
1366 }
1367 }
1368 }
1369
1370 private void ReportIllegalTables()
1371 {
1372 foreach (var table in this.Data.Tables)
1373 {
1374 switch (this.Data.Type)
1375 {
1376 case OutputType.Module:
1377 if ("BBControl" == table.Name ||
1378 "Billboard" == table.Name ||
1379 "CCPSearch" == table.Name ||
1380 "Feature" == table.Name ||
1381 "LaunchCondition" == table.Name ||
1382 "Media" == table.Name ||
1383 "Patch" == table.Name ||
1384 "Upgrade" == table.Name ||
1385 "WixMerge" == table.Name)
1386 {
1387 foreach (Row row in table.Rows)
1388 {
1389 this.Messaging.Write(ErrorMessages.UnexpectedTableInMergeModule(row.SourceLineNumbers, table.Name));
1390 }
1391 }
1392 else if ("Error" == table.Name)
1393 {
1394 foreach (var row in table.Rows)
1395 {
1396 this.Messaging.Write(WarningMessages.DangerousTableInMergeModule(row.SourceLineNumbers, table.Name));
1397 }
1398 }
1399 break;
1400
1401 case OutputType.PatchCreation:
1402 if (!table.Definition.Unreal &&
1403 "_SummaryInformation" != table.Name &&
1404 "ExternalFiles" != table.Name &&
1405 "FamilyFileRanges" != table.Name &&
1406 "ImageFamilies" != table.Name &&
1407 "PatchMetadata" != table.Name &&
1408 "PatchSequence" != table.Name &&
1409 "Properties" != table.Name &&
1410 "TargetFiles_OptionalData" != table.Name &&
1411 "TargetImages" != table.Name &&
1412 "UpgradedFiles_OptionalData" != table.Name &&
1413 "UpgradedFilesToIgnore" != table.Name &&
1414 "UpgradedImages" != table.Name)
1415 {
1416 foreach (var row in table.Rows)
1417 {
1418 this.Messaging.Write(ErrorMessages.UnexpectedTableInPatchCreationPackage(row.SourceLineNumbers, table.Name));
1419 }
1420 }
1421 break;
1422
1423 case OutputType.Patch:
1424 if (!table.Definition.Unreal &&
1425 "_SummaryInformation" != table.Name &&
1426 "Media" != table.Name &&
1427 "MsiFileHash" != table.Name &&
1428 "MsiPatchMetadata" != table.Name &&
1429 "MsiPatchSequence" != table.Name)
1430 {
1431 foreach (var row in table.Rows)
1432 {
1433 this.Messaging.Write(ErrorMessages.UnexpectedTableInPatch(row.SourceLineNumbers, table.Name));
1434 }
1435 }
1436 break;
1437
1438 case OutputType.Product:
1439 if ("ModuleAdminExecuteSequence" == table.Name ||
1440 "ModuleAdminUISequence" == table.Name ||
1441 "ModuleAdvtExecuteSequence" == table.Name ||
1442 "ModuleAdvtUISequence" == table.Name ||
1443 "ModuleComponents" == table.Name ||
1444 "ModuleConfiguration" == table.Name ||
1445 "ModuleDependency" == table.Name ||
1446 "ModuleExclusion" == table.Name ||
1447 "ModuleIgnoreTable" == table.Name ||
1448 "ModuleInstallExecuteSequence" == table.Name ||
1449 "ModuleInstallUISequence" == table.Name ||
1450 "ModuleSignature" == table.Name ||
1451 "ModuleSubstitution" == table.Name)
1452 {
1453 foreach (var row in table.Rows)
1454 {
1455 this.Messaging.Write(WarningMessages.UnexpectedTableInProduct(row.SourceLineNumbers, table.Name));
1456 }
1457 }
1458 break;
1459 }
1460 }
1461 }
1462
1463 private void ReportMismatchedModularizations()
1464 {
1465 // verify that modularization types match for foreign key relationships
1466 foreach (var tableDefinition in this.TableDefinitions)
1467 {
1468 foreach (var columnDefinition in tableDefinition.Columns)
1469 {
1470 if (null != columnDefinition.KeyTable && 0 > columnDefinition.KeyTable.IndexOf(';') && columnDefinition.KeyColumn.HasValue)
1471 {
1472 if (this.TableDefinitions.TryGet(columnDefinition.KeyTable, out var keyTableDefinition))
1473 {
1474 var keyColumnIndex = columnDefinition.KeyColumn ?? -1;
1475
1476 if (keyColumnIndex <= 0 || keyColumnIndex > keyTableDefinition.Columns.Length)
1477 {
1478 this.Messaging.Write(ErrorMessages.InvalidKeyColumn(tableDefinition.Name, columnDefinition.Name, columnDefinition.KeyTable, keyColumnIndex));
1479 }
1480 else if (keyTableDefinition.Columns[keyColumnIndex - 1].ModularizeType != columnDefinition.ModularizeType && ColumnModularizeType.CompanionFile != columnDefinition.ModularizeType)
1481 {
1482 this.Messaging.Write(WarningMessages.CollidingModularizationTypes(tableDefinition.Name, columnDefinition.Name, columnDefinition.KeyTable, keyColumnIndex, columnDefinition.ModularizeType.ToString(), keyTableDefinition.Columns[keyColumnIndex - 1].ModularizeType.ToString()));
1483 }
1484 }
1485 // else - ignore missing table definitions as that error is caught in other places
1486 }
1487 }
1488 }
1489 }
1490
1491 private void ReportWindowsInstallerDataInconsistencies()
1492 {
1493 // Get the output's minimum installer version
1494 var outputInstallerVersion = Int32.MaxValue;
1495
1496 if (this.Data.Tables.TryGetTable("_SummaryInformation", out var summaryInformationTable))
1497 {
1498 outputInstallerVersion = summaryInformationTable.Rows.FirstOrDefault(r => 14 == r.FieldAsInteger(0))?.FieldAsInteger(1) ?? Int32.MaxValue;
1499 }
1500
1501 // Ensure the Error table exists if output is marked for MSI 1.0 or below (see ICE40).
1502 if (outputInstallerVersion <= 100 && OutputType.Product == this.Data.Type)
1503 {
1504 this.Data.EnsureTable(this.TableDefinitions["Error"]);
1505 }
1506
1507 // Check for the presence of tables/rows/columns that require MSI 1.1 or later.
1508 if (outputInstallerVersion < 110)
1509 {
1510 if (this.Data.Tables.TryGetTable("IsolatedComponent", out var isolatedComponentTable))
1511 {
1512 foreach (var row in isolatedComponentTable.Rows)
1513 {
1514 this.Messaging.Write(WarningMessages.TableIncompatibleWithInstallerVersion(row.SourceLineNumbers, "IsolatedComponent", outputInstallerVersion));
1515 }
1516 }
1517 }
1518
1519 // Check for the presence of tables/rows/columns that require MSI 4.0 or later
1520 if (outputInstallerVersion < 400)
1521 {
1522 if (this.Data.Tables.TryGetTable("Shortcut", out var shortcutTable))
1523 {
1524 foreach (var row in shortcutTable.Rows)
1525 {
1526 if (null != row[12] || null != row[13] || null != row[14] || null != row[15])
1527 {
1528 this.Messaging.Write(WarningMessages.ColumnsIncompatibleWithInstallerVersion(row.SourceLineNumbers, "Shortcut", outputInstallerVersion));
1529 }
1530 }
1531 }
1532 }
1533 }
1534
1535 private static OutputType SectionTypeToOutputType(SectionType type)
1536 {
1537 switch (type)
1538 {
1539 case SectionType.Bundle:
1540 return OutputType.Bundle;
1541 case SectionType.Module:
1542 return OutputType.Module;
1543 case SectionType.Product:
1544 return OutputType.Product;
1545 case SectionType.PatchCreation:
1546 return OutputType.PatchCreation;
1547 case SectionType.Patch:
1548 return OutputType.Patch;
1549
1550 default:
1551 throw new ArgumentOutOfRangeException(nameof(type));
1552 }
1553 }
1554
1555 private Row CreateRow(IntermediateSymbol symbol, string tableDefinitionName) =>
1556 this.CreateRow(symbol, this.TableDefinitions[tableDefinitionName]);
1557
1558 private Row CreateRow(IntermediateSymbol symbol, TableDefinition tableDefinition) =>
1559 this.BackendHelper.CreateRow(this.Section, symbol, this.Data, tableDefinition);
1560
1561
1562 private string CreateShortName(string longName, bool keepExtension, params string[] args)
1563 {
1564 longName = longName.ToLowerInvariant();
1565
1566 // collect all the data
1567 var strings = new List<string>(1 + args.Length);
1568 strings.Add(longName);
1569 strings.AddRange(args);
1570
1571 // prepare for hashing
1572 var stringData = String.Join("|", strings);
1573 var data = Encoding.UTF8.GetBytes(stringData);
1574
1575 // hash the data
1576 byte[] hash;
1577 using (var sha1 = new SHA1CryptoServiceProvider())
1578 {
1579 hash = sha1.ComputeHash(data);
1580 }
1581
1582 // generate the short file/directory name without an extension
1583 var shortName = new StringBuilder(Convert.ToBase64String(hash));
1584 shortName.Length = 8;
1585 shortName.Replace('+', '-').Replace('/', '_');
1586
1587 if (keepExtension)
1588 {
1589 var extension = Path.GetExtension(longName);
1590
1591 if (4 < extension.Length)
1592 {
1593 extension = extension.Substring(0, 4);
1594 }
1595
1596 shortName.Append(extension);
1597
1598 // check the generated short name to ensure its still legal (the extension may not be legal)
1599 if (!this.BackendHelper.IsValidShortFilename(shortName.ToString(), false))
1600 {
1601 // remove the extension (by truncating the generated file name back to the generated characters)
1602 shortName.Length -= extension.Length;
1603 }
1604 }
1605
1606 return shortName.ToString().ToLowerInvariant();
1607 }
1608
1609 private static string CreateMsiFilename(string shortName, string longName)
1610 {
1611 if (String.IsNullOrEmpty(shortName) || String.Equals(shortName, longName, StringComparison.OrdinalIgnoreCase))
1612 {
1613 return longName;
1614 }
1615 else
1616 {
1617 return shortName + "|" + longName;
1618 }
1619 }
1620 }
1621}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.ComponentModel;
8 using System.Globalization;
9 using System.IO;
10 using System.Linq;
11 using System.Runtime.InteropServices;
12 using WixToolset.Core.Native;
13 using WixToolset.Core.Native.Msi;
14 using WixToolset.Core.Native.Msm;
15 using WixToolset.Data;
16 using WixToolset.Data.Symbols;
17 using WixToolset.Extensibility.Data;
18 using WixToolset.Extensibility.Services;
19
20 /// <summary>
21 /// Retrieve files information and extract them from merge modules.
22 /// </summary>
23 internal class ExtractMergeModuleFilesCommand
24 {
25 public ExtractMergeModuleFilesCommand(IMessaging messaging, IWindowsInstallerBackendHelper backendHelper, IEnumerable<WixMergeSymbol> wixMergeSymbols, IEnumerable<IFileFacade> fileFacades, int installerVersion, string intermediateFolder, bool suppressLayout)
26 {
27 this.Messaging = messaging;
28 this.BackendHelper = backendHelper;
29 this.WixMergeSymbols = wixMergeSymbols;
30 this.FileFacades = fileFacades;
31 this.OutputInstallerVersion = installerVersion;
32 this.IntermediateFolder = intermediateFolder;
33 this.SuppressLayout = suppressLayout;
34 }
35
36 private IMessaging Messaging { get; }
37
38 private IWindowsInstallerBackendHelper BackendHelper { get; }
39
40 private IEnumerable<WixMergeSymbol> WixMergeSymbols { get; }
41
42 private IEnumerable<IFileFacade> FileFacades { get; }
43
44 private int OutputInstallerVersion { get; }
45
46 private string IntermediateFolder { get; }
47
48 private bool SuppressLayout { get; }
49
50 public IEnumerable<IFileFacade> MergeModulesFileFacades { get; private set; }
51
52 public void Execute()
53 {
54 var mergeModulesFileFacades = new List<IFileFacade>();
55
56 var merge = MsmInterop.GetMsmMerge();
57
58 // Index all of the file rows to be able to detect collisions with files in the Merge Modules.
59 // It may seem a bit expensive to build up this index solely for the purpose of checking collisions
60 // and you may be thinking, "Surely, we must need the file rows indexed elsewhere." It turns out
61 // there are other cases where we need all the file rows indexed, however they are not common cases.
62 // Now since Merge Modules are already slow and generally less desirable than .wixlibs we'll let
63 // this case be slightly more expensive because the cost of maintaining an indexed file row collection
64 // is a lot more costly for the common cases.
65 var indexedFileFacades = this.FileFacades.ToDictionary(f => f.Id, StringComparer.Ordinal);
66
67 foreach (var wixMergeRow in this.WixMergeSymbols)
68 {
69 var containsFiles = this.CreateFacadesForMergeModuleFiles(wixMergeRow, mergeModulesFileFacades, indexedFileFacades);
70
71 // If the module has files and creating layout
72 if (containsFiles && !this.SuppressLayout)
73 {
74 this.ExtractFilesFromMergeModule(merge, wixMergeRow);
75 }
76 }
77
78 this.MergeModulesFileFacades = mergeModulesFileFacades;
79 }
80
81 private bool CreateFacadesForMergeModuleFiles(WixMergeSymbol wixMergeRow, List<IFileFacade> mergeModulesFileFacades, Dictionary<string, IFileFacade> indexedFileFacades)
82 {
83 var containsFiles = false;
84
85 try
86 {
87 // read the module's File table to get its FileMediaInformation entries and gather any other information needed from the module.
88 using (var db = new Database(wixMergeRow.SourceFile, OpenDatabase.ReadOnly))
89 {
90 if (db.TableExists("File") && db.TableExists("Component"))
91 {
92 var uniqueModuleFileIdentifiers = new Dictionary<string, IFileFacade>(StringComparer.OrdinalIgnoreCase);
93
94 using (var view = db.OpenExecuteView("SELECT `File`, `Directory_` FROM `File`, `Component` WHERE `Component_`=`Component`"))
95 {
96 // add each file row from the merge module into the file row collection (check for errors along the way)
97 foreach (var record in view.Records)
98 {
99 // NOTE: this is very tricky - the merge module file rows are not added to the
100 // file table because they should not be created via idt import. Instead, these
101 // rows are created by merging in the actual modules.
102 var fileSymbol = new FileSymbol(wixMergeRow.SourceLineNumbers, new Identifier(AccessModifier.Section, record[1]));
103 fileSymbol.Attributes = wixMergeRow.FileAttributes;
104 fileSymbol.DirectoryRef = record[2];
105 fileSymbol.DiskId = wixMergeRow.DiskId;
106 fileSymbol.Source = new IntermediateFieldPathValue { Path = Path.Combine(this.IntermediateFolder, wixMergeRow.Id.Id, record[1]) };
107
108 var mergeModuleFileFacade = this.BackendHelper.CreateFileFacadeFromMergeModule(fileSymbol);
109
110 // If case-sensitive collision with another merge module or a user-authored file identifier.
111 if (indexedFileFacades.TryGetValue(mergeModuleFileFacade.Id, out var collidingFacade))
112 {
113 this.Messaging.Write(ErrorMessages.DuplicateModuleFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, collidingFacade.Id));
114 }
115 else if (uniqueModuleFileIdentifiers.TryGetValue(mergeModuleFileFacade.Id, out collidingFacade)) // case-insensitive collision with another file identifier in the same merge module
116 {
117 this.Messaging.Write(ErrorMessages.DuplicateModuleCaseInsensitiveFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, mergeModuleFileFacade.Id, collidingFacade.Id));
118 }
119 else // no collision
120 {
121 mergeModulesFileFacades.Add(mergeModuleFileFacade);
122
123 // Keep updating the indexes as new rows are added.
124 indexedFileFacades.Add(mergeModuleFileFacade.Id, mergeModuleFileFacade);
125 uniqueModuleFileIdentifiers.Add(mergeModuleFileFacade.Id, mergeModuleFileFacade);
126 }
127
128 containsFiles = true;
129 }
130 }
131 }
132
133 // Get the summary information to detect the Schema
134 using (var summaryInformation = new SummaryInformation(db))
135 {
136 var moduleInstallerVersionString = summaryInformation.GetProperty(14);
137
138 try
139 {
140 var moduleInstallerVersion = Convert.ToInt32(moduleInstallerVersionString, CultureInfo.InvariantCulture);
141 if (moduleInstallerVersion > this.OutputInstallerVersion)
142 {
143 this.Messaging.Write(WarningMessages.InvalidHigherInstallerVersionInModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, moduleInstallerVersion, this.OutputInstallerVersion));
144 }
145 }
146 catch (FormatException)
147 {
148 throw new WixException(ErrorMessages.MissingOrInvalidModuleInstallerVersion(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, wixMergeRow.SourceFile, moduleInstallerVersionString));
149 }
150 }
151 }
152 }
153 catch (FileNotFoundException)
154 {
155 throw new WixException(ErrorMessages.FileNotFound(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile));
156 }
157 catch (Win32Exception)
158 {
159 throw new WixException(ErrorMessages.CannotOpenMergeModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, wixMergeRow.SourceFile));
160 }
161
162 return containsFiles;
163 }
164
165 private void ExtractFilesFromMergeModule(IMsmMerge2 merge, WixMergeSymbol wixMergeRow)
166 {
167 var moduleOpen = false;
168 short mergeLanguage;
169
170 var mergeId = wixMergeRow.Id.Id;
171
172 try
173 {
174 mergeLanguage = Convert.ToInt16(wixMergeRow.Language, CultureInfo.InvariantCulture);
175 }
176 catch (FormatException)
177 {
178 this.Messaging.Write(ErrorMessages.InvalidMergeLanguage(wixMergeRow.SourceLineNumbers, mergeId, wixMergeRow.Language.ToString()));
179 return;
180 }
181
182 try
183 {
184 merge.OpenModule(wixMergeRow.SourceFile, mergeLanguage);
185 moduleOpen = true;
186
187 // extract the module cabinet, then explode all of the files to a temp directory
188 var moduleCabPath = Path.Combine(this.IntermediateFolder, mergeId + ".cab");
189 merge.ExtractCAB(moduleCabPath);
190
191 var mergeIdPath = Path.Combine(this.IntermediateFolder, mergeId);
192 Directory.CreateDirectory(mergeIdPath);
193
194 try
195 {
196 var cabinet = new Cabinet(moduleCabPath);
197 cabinet.Extract(mergeIdPath);
198 }
199 catch (FileNotFoundException)
200 {
201 throw new WixException(ErrorMessages.CabFileDoesNotExist(moduleCabPath, wixMergeRow.SourceFile, mergeIdPath));
202 }
203 catch
204 {
205 throw new WixException(ErrorMessages.CabExtractionFailed(moduleCabPath, wixMergeRow.SourceFile, mergeIdPath));
206 }
207 }
208 catch (COMException ce)
209 {
210 throw new WixException(ErrorMessages.UnableToOpenModule(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile, ce.Message));
211 }
212 finally
213 {
214 if (moduleOpen)
215 {
216 merge.CloseModule();
217 }
218 }
219 }
220 }
221}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using WixToolset.Extensibility;
9
10 internal class FileSystemManager
11 {
12 public FileSystemManager(IEnumerable<IFileSystemExtension> fileSystemExtensions)
13 {
14 this.Extensions = fileSystemExtensions;
15 }
16
17 private IEnumerable<IFileSystemExtension> Extensions { get; }
18
19 public bool CompareFiles(string firstPath, string secondPath)
20 {
21 foreach (var extension in this.Extensions)
22 {
23 var compared = extension.CompareFiles(firstPath, secondPath);
24 if (compared.HasValue)
25 {
26 return compared.Value;
27 }
28 }
29
30 return BuiltinCompareFiles(firstPath, secondPath);
31 }
32
33 private static bool BuiltinCompareFiles(string firstPath, string secondPath)
34 {
35 if (String.Equals(firstPath, secondPath, StringComparison.OrdinalIgnoreCase))
36 {
37 return true;
38 }
39
40 using (var firstStream = File.OpenRead(firstPath))
41 using (var secondStream = File.OpenRead(secondPath))
42 {
43 if (firstStream.Length != secondStream.Length)
44 {
45 return false;
46 }
47
48 // Using a larger buffer than the default buffer of 4 * 1024 used by FileStream.ReadByte improves performance.
49 // The buffer size is based on user feedback. Based on performance results, a better buffer size may be determined.
50 var firstBuffer = new byte[16 * 1024];
51 var secondBuffer = new byte[16 * 1024];
52
53 var firstReadLength = 0;
54 do
55 {
56 firstReadLength = firstStream.Read(firstBuffer, 0, firstBuffer.Length);
57 var secondReadLength = secondStream.Read(secondBuffer, 0, secondBuffer.Length);
58
59 if (firstReadLength != secondReadLength)
60 {
61 return false;
62 }
63
64 for (var i = 0; i < firstReadLength; ++i)
65 {
66 if (firstBuffer[i] != secondBuffer[i])
67 {
68 return false;
69 }
70 }
71 } while (0 < firstReadLength);
72 }
73
74 return true;
75 }
76 }
77}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 using WixToolset.Data;
10 using WixToolset.Data.Symbols;
11 using WixToolset.Extensibility.Data;
12 using WixToolset.Extensibility.Services;
13
14 /// <summary>
15 /// Set the guids for components with generatable guids and validate all are appropriately unique.
16 /// </summary>
17 internal class FinalizeComponentGuids
18 {
19 internal FinalizeComponentGuids(IMessaging messaging, IBackendHelper helper, IPathResolver pathResolver, IntermediateSection section, Platform platform)
20 {
21 this.Messaging = messaging;
22 this.BackendHelper = helper;
23 this.PathResolver = pathResolver;
24 this.Section = section;
25 this.Platform = platform;
26 }
27
28 private IMessaging Messaging { get; }
29
30 private IBackendHelper BackendHelper { get; }
31
32 private IPathResolver PathResolver { get; }
33
34 private IntermediateSection Section { get; }
35
36 private Platform Platform { get; }
37
38 private Dictionary<string, string> ComponentIdGenSeeds { get; set; }
39
40 private ILookup<string, FileSymbol> FilesByComponentId { get; set; }
41
42 private Dictionary<string, RegistrySymbol> RegistrySymbolsById { get; set; }
43
44 private Dictionary<string, IResolvedDirectory> TargetPathsByDirectoryId { get; set; }
45
46 public void Execute()
47 {
48 var componentGuidConditions = new Dictionary<string, List<ComponentSymbol>>(StringComparer.OrdinalIgnoreCase);
49 var guidCollisions = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
50
51 foreach (var componentSymbol in this.Section.Symbols.OfType<ComponentSymbol>())
52 {
53 if (componentSymbol.ComponentId == "*")
54 {
55 this.GenerateComponentGuid(componentSymbol);
56 }
57
58 // Now check for GUID collisions, but we don't care about unmanaged components and
59 // if there's a * GUID remaining, there's already an error that explained why it
60 // was not replaced with a real GUID.
61 if (!String.IsNullOrEmpty(componentSymbol.ComponentId) && componentSymbol.ComponentId != "*")
62 {
63 if (!componentGuidConditions.TryGetValue(componentSymbol.ComponentId, out var components))
64 {
65 components = new List<ComponentSymbol>();
66 componentGuidConditions.Add(componentSymbol.ComponentId, components);
67 }
68
69 components.Add(componentSymbol);
70 if (components.Count > 1)
71 {
72 guidCollisions.Add(componentSymbol.ComponentId);
73 }
74 }
75 }
76
77 if (guidCollisions.Count > 0)
78 {
79 this.ReportGuidCollisions(guidCollisions, componentGuidConditions);
80 }
81 }
82
83 private void GenerateComponentGuid(ComponentSymbol componentSymbol)
84 {
85 if (String.IsNullOrEmpty(componentSymbol.KeyPath) || ComponentKeyPathType.OdbcDataSource == componentSymbol.KeyPathType)
86 {
87 this.Messaging.Write(ErrorMessages.IllegalComponentWithAutoGeneratedGuid(componentSymbol.SourceLineNumbers));
88 return;
89 }
90
91 if (ComponentKeyPathType.Registry == componentSymbol.KeyPathType)
92 {
93 if (this.RegistrySymbolsById is null)
94 {
95 this.RegistrySymbolsById = this.Section.Symbols.OfType<RegistrySymbol>().ToDictionary(t => t.Id.Id);
96 }
97
98 if (this.RegistrySymbolsById.TryGetValue(componentSymbol.KeyPath, out var registrySymbol))
99 {
100 var bitness = componentSymbol.Win64 ? "64" : String.Empty;
101 var regkey = String.Concat(bitness, registrySymbol.Root, "\\", registrySymbol.Key, "\\", registrySymbol.Name);
102 componentSymbol.ComponentId = this.BackendHelper.CreateGuid(BindDatabaseCommand.WixComponentGuidNamespace, regkey.ToLowerInvariant());
103 }
104 }
105 else // must be a File KeyPath.
106 {
107 // If the directory table hasn't been loaded into an indexed hash
108 // of directory ids to target names do that now.
109 if (this.TargetPathsByDirectoryId is null)
110 {
111 this.TargetPathsByDirectoryId = this.ResolveDirectoryTargetPaths();
112 }
113
114 // If the component id generation seeds have not been indexed
115 // from the Directory symbols do that now.
116 if (this.ComponentIdGenSeeds is null)
117 {
118 // If there are any Directory symbols, build up the Component Guid
119 // generation seeds indexed by Directory/@Id.
120 this.ComponentIdGenSeeds = this.Section.Symbols.OfType<DirectorySymbol>()
121 .Where(t => !String.IsNullOrEmpty(t.ComponentGuidGenerationSeed))
122 .ToDictionary(t => t.Id.Id, t => t.ComponentGuidGenerationSeed);
123 }
124
125 // If the file symbols have not been indexed by File's ComponentRef yet
126 // then do that now.
127 if (this.FilesByComponentId is null)
128 {
129 this.FilesByComponentId = this.Section.Symbols.OfType<FileSymbol>().ToLookup(f => f.ComponentRef);
130 }
131
132 // validate component meets all the conditions to have a generated guid
133 var currentComponentFiles = this.FilesByComponentId[componentSymbol.Id.Id];
134 var numFilesInComponent = currentComponentFiles.Count();
135 string path = null;
136
137 foreach (var fileSymbol in currentComponentFiles)
138 {
139 if (fileSymbol.Id.Id == componentSymbol.KeyPath)
140 {
141 // calculate the key file's canonical target path
142 var directoryPath = this.PathResolver.GetCanonicalDirectoryPath(this.TargetPathsByDirectoryId, this.ComponentIdGenSeeds, componentSymbol.DirectoryRef, this.Platform);
143 var fileName = this.BackendHelper.GetMsiFileName(fileSymbol.Name, false, true).ToLowerInvariant();
144 path = Path.Combine(directoryPath, fileName);
145
146 // find paths that are not canonicalized
147 if (path.StartsWith(@"PersonalFolder\my pictures", StringComparison.Ordinal) ||
148 path.StartsWith(@"ProgramFilesFolder\common files", StringComparison.Ordinal) ||
149 path.StartsWith(@"ProgramMenuFolder\startup", StringComparison.Ordinal) ||
150 path.StartsWith("TARGETDIR", StringComparison.Ordinal) ||
151 path.StartsWith(@"StartMenuFolder\programs", StringComparison.Ordinal) ||
152 path.StartsWith(@"WindowsFolder\fonts", StringComparison.Ordinal))
153 {
154 this.Messaging.Write(ErrorMessages.IllegalPathForGeneratedComponentGuid(componentSymbol.SourceLineNumbers, fileSymbol.ComponentRef, path));
155 }
156
157 // if component has more than one file, the key path must be versioned
158 if (1 < numFilesInComponent && String.IsNullOrEmpty(fileSymbol.Version))
159 {
160 this.Messaging.Write(ErrorMessages.IllegalGeneratedGuidComponentUnversionedKeypath(componentSymbol.SourceLineNumbers));
161 }
162 }
163 else
164 {
165 // not a key path, so it must be an unversioned file if component has more than one file
166 if (1 < numFilesInComponent && !String.IsNullOrEmpty(fileSymbol.Version))
167 {
168 this.Messaging.Write(ErrorMessages.IllegalGeneratedGuidComponentVersionedNonkeypath(componentSymbol.SourceLineNumbers));
169 }
170 }
171 }
172
173 // if the rules were followed, reward with a generated guid
174 if (!this.Messaging.EncounteredError)
175 {
176 componentSymbol.ComponentId = this.BackendHelper.CreateGuid(BindDatabaseCommand.WixComponentGuidNamespace, path);
177 }
178 }
179 }
180
181 private void ReportGuidCollisions(HashSet<string> guidCollisions, Dictionary<string, List<ComponentSymbol>> componentGuidConditions)
182 {
183 Dictionary<string, FileSymbol> fileSymbolsById = null;
184
185 foreach (var guid in guidCollisions)
186 {
187 var collidingComponents = componentGuidConditions[guid];
188 var allComponentsHaveConditions = collidingComponents.All(c => !String.IsNullOrEmpty(c.Condition));
189
190 foreach (var componentSymbol in collidingComponents)
191 {
192 string path;
193 string type;
194
195 if (componentSymbol.KeyPathType == ComponentKeyPathType.File)
196 {
197 if (fileSymbolsById is null)
198 {
199 fileSymbolsById = this.Section.Symbols.OfType<FileSymbol>().ToDictionary(t => t.Id.Id);
200 }
201
202 path = fileSymbolsById.TryGetValue(componentSymbol.KeyPath, out var fileSymbol) ? fileSymbol.Source.Path : componentSymbol.KeyPath;
203 type = "source path";
204 }
205 else if (componentSymbol.KeyPathType == ComponentKeyPathType.Registry)
206 {
207 if (this.RegistrySymbolsById is null)
208 {
209 this.RegistrySymbolsById = this.Section.Symbols.OfType<RegistrySymbol>().ToDictionary(t => t.Id.Id);
210 }
211
212 path = this.RegistrySymbolsById.TryGetValue(componentSymbol.KeyPath, out var registrySymbol) ? String.Concat(registrySymbol.Key, "\\", registrySymbol.Name) : componentSymbol.KeyPath;
213 type = "registry path";
214 }
215 else
216 {
217 if (this.TargetPathsByDirectoryId is null)
218 {
219 this.TargetPathsByDirectoryId = this.ResolveDirectoryTargetPaths();
220 }
221
222 path = this.PathResolver.GetCanonicalDirectoryPath(this.TargetPathsByDirectoryId, componentIdGenSeeds: null, componentSymbol.DirectoryRef, this.Platform);
223 type = "directory";
224 }
225
226 if (allComponentsHaveConditions)
227 {
228 this.Messaging.Write(WarningMessages.DuplicateComponentGuidsMustHaveMutuallyExclusiveConditions(componentSymbol.SourceLineNumbers, componentSymbol.Id.Id, componentSymbol.ComponentId, type, path));
229 }
230 else
231 {
232 this.Messaging.Write(ErrorMessages.DuplicateComponentGuids(componentSymbol.SourceLineNumbers, componentSymbol.Id.Id, componentSymbol.ComponentId, type, path));
233 }
234 }
235 }
236 }
237
238 private Dictionary<string, IResolvedDirectory> ResolveDirectoryTargetPaths()
239 {
240 var directories = this.Section.Symbols.OfType<DirectorySymbol>().ToList();
241
242 var targetPathsByDirectoryId = new Dictionary<string, IResolvedDirectory>(directories.Count);
243
244 // Get the target paths for all directories.
245 foreach (var directory in directories)
246 {
247 // If the directory Id already exists, we will skip it here since
248 // checking for duplicate primary keys is done later when importing tables
249 // into database
250 if (targetPathsByDirectoryId.ContainsKey(directory.Id.Id))
251 {
252 continue;
253 }
254
255 var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(directory.ParentDirectoryRef, directory.Name);
256 targetPathsByDirectoryId.Add(directory.Id.Id, resolvedDirectory);
257 }
258
259 return targetPathsByDirectoryId;
260 }
261 }
262}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.ComponentModel;
8 using System.IO;
9 using System.Linq;
10 using System.Text;
11 using WixToolset.Core.Native.Msi;
12 using WixToolset.Data;
13 using WixToolset.Data.WindowsInstaller;
14 using WixToolset.Extensibility.Data;
15 using WixToolset.Extensibility.Services;
16
17 internal class GenerateDatabaseCommand
18 {
19 private const string IdtsSubFolder = "_idts";
20
21 public GenerateDatabaseCommand(IMessaging messaging, IBackendHelper backendHelper, FileSystemManager fileSystemManager, WindowsInstallerData data, string outputPath, TableDefinitionCollection tableDefinitions, string intermediateFolder, bool keepAddedColumns, bool suppressAddingValidationRows, bool useSubdirectory)
22 {
23 this.Messaging = messaging;
24 this.BackendHelper = backendHelper;
25 this.FileSystemManager = fileSystemManager;
26 this.Data = data;
27 this.OutputPath = outputPath;
28 this.TableDefinitions = tableDefinitions;
29 this.IntermediateFolder = intermediateFolder;
30 this.KeepAddedColumns = keepAddedColumns;
31 this.SuppressAddingValidationRows = suppressAddingValidationRows;
32 this.UseSubDirectory = useSubdirectory;
33 }
34
35 private IBackendHelper BackendHelper { get; }
36
37 private FileSystemManager FileSystemManager { get; }
38
39 /// <summary>
40 /// Whether to keep columns added in a transform.
41 /// </summary>
42 private bool KeepAddedColumns { get; }
43
44 private IMessaging Messaging { get; }
45
46 private WindowsInstallerData Data { get; }
47
48 private string OutputPath { get; }
49
50 private TableDefinitionCollection TableDefinitions { get; }
51
52 private string IntermediateFolder { get; }
53
54 public List<ITrackedFile> GeneratedTemporaryFiles { get; } = new List<ITrackedFile>();
55
56 /// <summary>
57 /// Whether to use a subdirectory based on the database file name for intermediate files.
58 /// </summary>
59 private bool SuppressAddingValidationRows { get; }
60
61 private bool UseSubDirectory { get; }
62
63 public void Execute()
64 {
65 // Add the _Validation rows.
66 if (!this.SuppressAddingValidationRows)
67 {
68 this.AddValidationRows();
69 }
70
71 var baseDirectory = this.IntermediateFolder;
72
73 if (this.UseSubDirectory)
74 {
75 var filename = Path.GetFileNameWithoutExtension(this.OutputPath);
76 baseDirectory = Path.Combine(baseDirectory, filename);
77 }
78
79 var idtFolder = Path.Combine(baseDirectory, IdtsSubFolder);
80
81 var type = OpenDatabase.CreateDirect;
82
83 if (OutputType.Patch == this.Data.Type)
84 {
85 type |= OpenDatabase.OpenPatchFile;
86 }
87
88 try
89 {
90 Directory.CreateDirectory(Path.GetDirectoryName(this.OutputPath));
91
92 Directory.CreateDirectory(idtFolder);
93
94 using (var db = new Database(this.OutputPath, type))
95 {
96 // If we're not using the default codepage, import a new one into our
97 // database before we add any tables (or the tables would be added
98 // with the wrong codepage).
99 if (0 != this.Data.Codepage)
100 {
101 this.SetDatabaseCodepage(db, this.Data.Codepage, idtFolder);
102 }
103
104 this.ImportTables(db, idtFolder);
105
106 // Insert substorages (usually transforms inside a patch or instance transforms in a package).
107 this.ImportSubStorages(db);
108
109 // We're good, commit the changes to the new database.
110 db.Commit();
111 }
112 }
113 catch (IOException e)
114 {
115 // TODO: this error message doesn't seem specific enough
116 throw new WixException(ErrorMessages.FileNotFound(new SourceLineNumber(this.OutputPath), this.OutputPath), e);
117 }
118 }
119
120 private void AddValidationRows()
121 {
122 var validationTable = this.Data.EnsureTable(this.TableDefinitions["_Validation"]);
123
124 // Add the validation rows for real tables and columns.
125 foreach (var table in this.Data.Tables.Where(t => !t.Definition.Unreal))
126 {
127 foreach (var columnDef in table.Definition.Columns.Where(c => !c.Unreal))
128 {
129 var row = validationTable.CreateRow(null);
130
131 row[0] = table.Name;
132
133 row[1] = columnDef.Name;
134
135 if (columnDef.Nullable)
136 {
137 row[2] = "Y";
138 }
139 else
140 {
141 row[2] = "N";
142 }
143
144 if (columnDef.MinValue.HasValue)
145 {
146 row[3] = columnDef.MinValue.Value;
147 }
148
149 if (columnDef.MaxValue.HasValue)
150 {
151 row[4] = columnDef.MaxValue.Value;
152 }
153
154 row[5] = columnDef.KeyTable;
155
156 if (columnDef.KeyColumn.HasValue)
157 {
158 row[6] = columnDef.KeyColumn.Value;
159 }
160
161 if (ColumnCategory.Unknown != columnDef.Category)
162 {
163 row[7] = columnDef.Category.ToString();
164 }
165
166 row[8] = columnDef.Possibilities;
167
168 row[9] = columnDef.Description;
169 }
170 }
171 }
172
173 private void ImportTables(Database db, string idtDirectory)
174 {
175 foreach (var table in this.Data.Tables)
176 {
177 var importTable = table;
178 var hasBinaryColumn = false;
179
180 // Skip all unreal tables other than _Streams.
181 if (table.Definition.Unreal && "_Streams" != table.Name)
182 {
183 continue;
184 }
185
186 // Do not put the _Validation table in patches, it is not needed.
187 if (OutputType.Patch == this.Data.Type && "_Validation" == table.Name)
188 {
189 continue;
190 }
191
192 // The only way to import binary data is to copy it to a local subdirectory first.
193 // To avoid this extra copying and perf hit, import an empty table with the same
194 // definition and later import the binary data from source using records.
195 foreach (var columnDefinition in table.Definition.Columns)
196 {
197 if (ColumnType.Object == columnDefinition.Type)
198 {
199 importTable = new Table(table.Definition);
200 hasBinaryColumn = true;
201 break;
202 }
203 }
204
205 // Create the table via IDT import.
206 if ("_Streams" != importTable.Name)
207 {
208 try
209 {
210 var command = new CreateIdtFileCommand(this.Messaging, importTable, this.Data.Codepage, idtDirectory, this.KeepAddedColumns);
211 command.Execute();
212
213 var trackIdt = this.BackendHelper.TrackFile(command.IdtPath, TrackedFileType.Temporary);
214 this.GeneratedTemporaryFiles.Add(trackIdt);
215
216 db.Import(command.IdtPath);
217 }
218 catch (WixInvalidIdtException)
219 {
220 // If ValidateRows finds anything it doesn't like, it throws
221 importTable.ValidateRows();
222
223 // Otherwise we rethrow the InvalidIdt
224 throw;
225 }
226 }
227
228 // insert the rows via SQL query if this table contains object fields
229 if (hasBinaryColumn)
230 {
231 var query = new StringBuilder("SELECT ");
232
233 // Build the query for the view.
234 var firstColumn = true;
235 foreach (var columnDefinition in table.Definition.Columns)
236 {
237 if (columnDefinition.Unreal)
238 {
239 continue;
240 }
241
242 if (!firstColumn)
243 {
244 query.Append(",");
245 }
246
247 query.AppendFormat(" `{0}`", columnDefinition.Name);
248 firstColumn = false;
249 }
250 query.AppendFormat(" FROM `{0}`", table.Name);
251
252 using (var tableView = db.OpenExecuteView(query.ToString()))
253 {
254 // Import each row containing a stream
255 foreach (var row in table.Rows)
256 {
257 using (var record = new Record(table.Definition.Columns.Length))
258 {
259 // Stream names are created by concatenating the name of the table with the values
260 // of the primary key (delimited by periods).
261 var streamName = new StringBuilder();
262
263 // the _Streams table doesn't prepend the table name (or a period)
264 if ("_Streams" != table.Name)
265 {
266 streamName.Append(table.Name);
267 }
268
269 var needStream = false;
270
271 for (var i = 0; i < table.Definition.Columns.Length; i++)
272 {
273 var columnDefinition = table.Definition.Columns[i];
274
275 if (columnDefinition.Unreal)
276 {
277 continue;
278 }
279
280 switch (columnDefinition.Type)
281 {
282 case ColumnType.Localized:
283 case ColumnType.Preserved:
284 case ColumnType.String:
285 var str = row.FieldAsString(i);
286
287 if (columnDefinition.PrimaryKey)
288 {
289 if (0 < streamName.Length)
290 {
291 streamName.Append(".");
292 }
293
294 streamName.Append(str);
295 }
296
297 record.SetString(i + 1, str);
298 break;
299 case ColumnType.Number:
300 record.SetInteger(i + 1, row.FieldAsInteger(i));
301 break;
302
303 case ColumnType.Object:
304 var path = row.FieldAsString(i);
305 if (null != path)
306 {
307 needStream = true;
308 try
309 {
310 record.SetStream(i + 1, path);
311 }
312 catch (Win32Exception e)
313 {
314 if (0xA1 == e.NativeErrorCode) // ERROR_BAD_PATHNAME
315 {
316 throw new WixException(ErrorMessages.FileNotFound(row.SourceLineNumbers, path));
317 }
318 else
319 {
320 throw new WixException(ErrorMessages.Win32Exception(e.NativeErrorCode, e.Message));
321 }
322 }
323 }
324 break;
325 }
326 }
327
328 // check for a stream name that is more than 62 characters long (the maximum allowed length)
329 if (needStream && Database.MsiMaxStreamNameLength < streamName.Length)
330 {
331 this.Messaging.Write(ErrorMessages.StreamNameTooLong(row.SourceLineNumbers, table.Name, streamName.ToString(), streamName.Length));
332 }
333 else // add the row to the database
334 {
335 tableView.Modify(ModifyView.Assign, record);
336 }
337 }
338 }
339 }
340
341 // Remove rows from the _Streams table for wixpdbs.
342 if ("_Streams" == table.Name)
343 {
344 table.Rows.Clear();
345 }
346 }
347 }
348 }
349
350 private void ImportSubStorages(Database db)
351 {
352 if (0 < this.Data.SubStorages.Count)
353 {
354 using (var storagesView = new View(db, "SELECT `Name`, `Data` FROM `_Storages`"))
355 {
356 foreach (var subStorage in this.Data.SubStorages)
357 {
358 var transformFile = Path.Combine(this.IntermediateFolder, String.Concat(subStorage.Name, ".mst"));
359
360 // Bind the transform.
361 var command = new BindTransformCommand(this.Messaging, this.BackendHelper, this.FileSystemManager, this.IntermediateFolder, subStorage.Data, transformFile, this.TableDefinitions);
362 command.Execute();
363
364 if (this.Messaging.EncounteredError)
365 {
366 continue;
367 }
368
369 // Add the storage to the database.
370 using (var record = new Record(2))
371 {
372 record.SetString(1, subStorage.Name);
373 record.SetStream(2, transformFile);
374 storagesView.Modify(ModifyView.Assign, record);
375 }
376 }
377 }
378 }
379 }
380
381 private void SetDatabaseCodepage(Database db, int codepage, string idtFolder)
382 {
383 // Write out the _ForceCodepage IDT file.
384 var idtPath = Path.Combine(idtFolder, "_ForceCodepage.idt");
385 using (var idtFile = new StreamWriter(idtPath, false, Encoding.ASCII))
386 {
387 idtFile.WriteLine(); // dummy column name record
388 idtFile.WriteLine(); // dummy column definition record
389 idtFile.Write(codepage);
390 idtFile.WriteLine("\t_ForceCodepage");
391 }
392
393 var trackIdt = this.BackendHelper.TrackFile(idtPath, TrackedFileType.Temporary);
394 this.GeneratedTemporaryFiles.Add(trackIdt);
395
396 // Try to import the table into the MSI.
397 try
398 {
399 db.Import(idtPath);
400 }
401 catch (WixInvalidIdtException)
402 {
403 // The IDT should always be generated correctly, so an invalid code page was given.
404 throw new WixException(ErrorMessages.IllegalCodepage(codepage));
405 }
406 }
407 }
408}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using WixToolset.Core.Native.Msi;
9 using WixToolset.Data;
10 using WixToolset.Data.Symbols;
11 using WixToolset.Data.WindowsInstaller;
12 using WixToolset.Extensibility.Services;
13
14 /// <summary>
15 /// Creates a transform by diffing two outputs.
16 /// </summary>
17 internal class GenerateTransformCommand
18 {
19 private const char sectionDelimiter = '/';
20 private readonly IMessaging messaging;
21 private SummaryInformationStreams transformSummaryInfo;
22
23 /// <summary>
24 /// Instantiates a new Differ class.
25 /// </summary>
26 public GenerateTransformCommand(IMessaging messaging, WindowsInstallerData targetOutput, WindowsInstallerData updatedOutput, bool preserveUnchangedRows, bool showPedanticMessages)
27 {
28 this.messaging = messaging;
29 this.TargetOutput = targetOutput;
30 this.UpdatedOutput = updatedOutput;
31 this.PreserveUnchangedRows = preserveUnchangedRows;
32 this.ShowPedanticMessages = showPedanticMessages;
33 }
34
35 private WindowsInstallerData TargetOutput { get; }
36
37 private WindowsInstallerData UpdatedOutput { get; }
38
39 private TransformFlags ValidationFlags { get; }
40
41 /// <summary>
42 /// Gets or sets the option to show pedantic messages.
43 /// </summary>
44 /// <value>The option to show pedantic messages.</value>
45 private bool ShowPedanticMessages { get; }
46
47 /// <summary>
48 /// Gets or sets the option to suppress keeping special rows.
49 /// </summary>
50 /// <value>The option to suppress keeping special rows.</value>
51 private bool SuppressKeepingSpecialRows { get; }
52
53 /// <summary>
54 /// Gets or sets the flag to determine if all rows, even unchanged ones will be persisted in the output.
55 /// </summary>
56 /// <value>The option to keep all rows including unchanged rows.</value>
57 private bool PreserveUnchangedRows { get; }
58
59 public WindowsInstallerData Transform { get; private set; }
60
61 /// <summary>
62 /// Creates a transform by diffing two outputs.
63 /// </summary>
64 public WindowsInstallerData Execute()
65 {
66 var targetOutput = this.TargetOutput;
67 var updatedOutput = this.UpdatedOutput;
68 var validationFlags = this.ValidationFlags;
69
70 var transform = new WindowsInstallerData(null)
71 {
72 Type = OutputType.Transform,
73 Codepage = updatedOutput.Codepage
74 };
75
76 this.transformSummaryInfo = new SummaryInformationStreams();
77
78 // compare the codepages
79 if (targetOutput.Codepage != updatedOutput.Codepage && 0 == (TransformFlags.ErrorChangeCodePage & validationFlags))
80 {
81 this.messaging.Write(ErrorMessages.OutputCodepageMismatch(targetOutput.SourceLineNumbers, targetOutput.Codepage, updatedOutput.Codepage));
82 if (null != updatedOutput.SourceLineNumbers)
83 {
84 this.messaging.Write(ErrorMessages.OutputCodepageMismatch2(updatedOutput.SourceLineNumbers));
85 }
86 }
87
88 // compare the output types
89 if (targetOutput.Type != updatedOutput.Type)
90 {
91 throw new WixException(ErrorMessages.OutputTypeMismatch(targetOutput.SourceLineNumbers, targetOutput.Type.ToString(), updatedOutput.Type.ToString()));
92 }
93
94 // compare the contents of the tables
95 foreach (var targetTable in targetOutput.Tables)
96 {
97 var updatedTable = updatedOutput.Tables[targetTable.Name];
98 var operation = TableOperation.None;
99
100 var rows = this.CompareTables(targetOutput, targetTable, updatedTable, out operation);
101
102 if (TableOperation.Drop == operation)
103 {
104 var droppedTable = transform.EnsureTable(targetTable.Definition);
105 droppedTable.Operation = TableOperation.Drop;
106 }
107 else if (TableOperation.None == operation)
108 {
109 var modifiedTable = transform.EnsureTable(updatedTable.Definition);
110 foreach (var row in rows)
111 {
112 modifiedTable.Rows.Add(row);
113 }
114 }
115 }
116
117 // added tables
118 foreach (var updatedTable in updatedOutput.Tables)
119 {
120 if (null == targetOutput.Tables[updatedTable.Name])
121 {
122 var addedTable = transform.EnsureTable(updatedTable.Definition);
123 addedTable.Operation = TableOperation.Add;
124
125 foreach (var updatedRow in updatedTable.Rows)
126 {
127 updatedRow.Operation = RowOperation.Add;
128 updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId;
129 addedTable.Rows.Add(updatedRow);
130 }
131 }
132 }
133
134 // set summary information properties
135 if (!this.SuppressKeepingSpecialRows)
136 {
137 var summaryInfoTable = transform.Tables["_SummaryInformation"];
138 this.UpdateTransformSummaryInformationTable(summaryInfoTable, validationFlags);
139 }
140
141 this.Transform = transform;
142 return this.Transform;
143 }
144
145 /// <summary>
146 /// Add a row to the <paramref name="index"/> using the primary key.
147 /// </summary>
148 /// <param name="index">The indexed rows.</param>
149 /// <param name="row">The row to index.</param>
150 private void AddIndexedRow(Dictionary<string, Row> index, Row row)
151 {
152 var primaryKey = row.GetPrimaryKey();
153
154 if (null != primaryKey)
155 {
156 if (index.TryGetValue(primaryKey, out var collisionRow))
157 {
158#if TODO_PATCH // This case doesn't seem like it can happen any longer.
159 // Overriding WixActionRows have a primary key defined and take precedence in the index.
160 if (row is WixActionRow actionRow)
161 {
162 // If the current row is not overridable, see if the indexed row is.
163 if (!actionRow.Overridable)
164 {
165 if (collisionRow is WixActionRow indexedRow && indexedRow.Overridable)
166 {
167 // The indexed key is overridable and should be replaced.
168 index[primaryKey] = actionRow;
169 }
170 }
171
172 // If we got this far, the row does not need to be indexed.
173 return;
174 }
175#endif
176
177 if (this.ShowPedanticMessages)
178 {
179 this.messaging.Write(ErrorMessages.DuplicatePrimaryKey(row.SourceLineNumbers, primaryKey, row.Table.Name));
180 }
181 }
182 else
183 {
184 index.Add(primaryKey, row);
185 }
186 }
187 else // use the string representation of the row as its primary key (it may not be unique)
188 {
189 // this is provided for compatibility with unreal tables with no primary key
190 // all real tables must specify at least one column as the primary key
191 primaryKey = row.ToString();
192 index[primaryKey] = row;
193 }
194 }
195
196 private bool CompareRows(Table targetTable, Row targetRow, Row updatedRow, out Row comparedRow)
197 {
198 comparedRow = null;
199
200 var keepRow = false;
201
202 if (null == targetRow ^ null == updatedRow)
203 {
204 if (null == targetRow)
205 {
206 updatedRow.Operation = RowOperation.Add;
207 comparedRow = updatedRow;
208 }
209 else if (null == updatedRow)
210 {
211 targetRow.Operation = RowOperation.Delete;
212 targetRow.SectionId += sectionDelimiter;
213
214 comparedRow = targetRow;
215 keepRow = true;
216 }
217 }
218 else // possibly modified
219 {
220 updatedRow.Operation = RowOperation.None;
221 if (!this.SuppressKeepingSpecialRows && "_SummaryInformation" == targetTable.Name)
222 {
223 // ignore rows that shouldn't be in a transform
224 if (Enum.IsDefined(typeof(SummaryInformation.Transform), (int)updatedRow[0]))
225 {
226 updatedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId;
227 comparedRow = updatedRow;
228 keepRow = true;
229 }
230 }
231 else
232 {
233 if (this.PreserveUnchangedRows)
234 {
235 keepRow = true;
236 }
237
238 for (var i = 0; i < updatedRow.Fields.Length; i++)
239 {
240 var columnDefinition = updatedRow.Fields[i].Column;
241
242 if (!columnDefinition.PrimaryKey)
243 {
244 var modified = false;
245
246 if (i >= targetRow.Fields.Length)
247 {
248 columnDefinition.Added = true;
249 modified = true;
250 }
251 else if (ColumnType.Number == columnDefinition.Type && !columnDefinition.IsLocalizable)
252 {
253 if (null == targetRow[i] ^ null == updatedRow[i])
254 {
255 modified = true;
256 }
257 else if (null != targetRow[i] && null != updatedRow[i])
258 {
259 modified = (targetRow.FieldAsInteger(i) != updatedRow.FieldAsInteger(i));
260 }
261 }
262 else if (ColumnType.Preserved == columnDefinition.Type)
263 {
264 updatedRow.Fields[i].PreviousData = targetRow.FieldAsString(i);
265
266 // keep rows containing preserved fields so the historical data is available to the binder
267 keepRow = !this.SuppressKeepingSpecialRows;
268 }
269 else if (ColumnType.Object == columnDefinition.Type)
270 {
271 var targetObjectField = (ObjectField)targetRow.Fields[i];
272 var updatedObjectField = (ObjectField)updatedRow.Fields[i];
273
274 updatedObjectField.PreviousEmbeddedFileIndex = targetObjectField.EmbeddedFileIndex;
275 updatedObjectField.PreviousBaseUri = targetObjectField.BaseUri;
276
277 // always keep a copy of the previous data even if they are identical
278 // This makes diff.wixmst clean and easier to control patch logic
279 updatedObjectField.PreviousData = (string)targetObjectField.Data;
280
281 // always remember the unresolved data for target build
282 updatedObjectField.UnresolvedPreviousData = targetObjectField.UnresolvedData;
283
284 // keep rows containing object fields so the files can be compared in the binder
285 keepRow = !this.SuppressKeepingSpecialRows;
286 }
287 else
288 {
289 modified = (targetRow.FieldAsString(i) != updatedRow.FieldAsString(i));
290 }
291
292 if (modified)
293 {
294 if (null != updatedRow.Fields[i].PreviousData)
295 {
296 updatedRow.Fields[i].PreviousData = targetRow.FieldAsString(i);
297 }
298
299 updatedRow.Fields[i].Modified = true;
300 updatedRow.Operation = RowOperation.Modify;
301 keepRow = true;
302 }
303 }
304 }
305
306 if (keepRow)
307 {
308 comparedRow = updatedRow;
309 comparedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId;
310 }
311 }
312 }
313
314 return keepRow;
315 }
316
317 private List<Row> CompareTables(WindowsInstallerData targetOutput, Table targetTable, Table updatedTable, out TableOperation operation)
318 {
319 var rows = new List<Row>();
320 operation = TableOperation.None;
321
322 // dropped tables
323 if (null == updatedTable ^ null == targetTable)
324 {
325 if (null == targetTable)
326 {
327 operation = TableOperation.Add;
328 rows.AddRange(updatedTable.Rows);
329 }
330 else if (null == updatedTable)
331 {
332 operation = TableOperation.Drop;
333 }
334 }
335 else // possibly modified tables
336 {
337 var updatedPrimaryKeys = new Dictionary<string, Row>();
338 var targetPrimaryKeys = new Dictionary<string, Row>();
339
340 // compare the table definitions
341 if (0 != targetTable.Definition.CompareTo(updatedTable.Definition))
342 {
343 // continue to the next table; may be more mismatches
344 this.messaging.Write(ErrorMessages.DatabaseSchemaMismatch(targetOutput.SourceLineNumbers, targetTable.Name));
345 }
346 else
347 {
348 this.IndexPrimaryKeys(targetTable, targetPrimaryKeys, updatedTable, updatedPrimaryKeys);
349
350 // diff the target and updated rows
351 foreach (var targetPrimaryKeyEntry in targetPrimaryKeys)
352 {
353 var targetPrimaryKey = targetPrimaryKeyEntry.Key;
354 var targetRow = targetPrimaryKeyEntry.Value;
355 updatedPrimaryKeys.TryGetValue(targetPrimaryKey, out var updatedRow);
356
357 var keepRow = this.CompareRows(targetTable, targetRow, updatedRow, out var compared);
358
359 if (keepRow)
360 {
361 rows.Add(compared);
362 }
363 }
364
365 // find the inserted rows
366 foreach (var updatedPrimaryKeyEntry in updatedPrimaryKeys)
367 {
368 var updatedPrimaryKey = updatedPrimaryKeyEntry.Key;
369
370 if (!targetPrimaryKeys.ContainsKey(updatedPrimaryKey))
371 {
372 var updatedRow = updatedPrimaryKeyEntry.Value;
373
374 updatedRow.Operation = RowOperation.Add;
375 updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId;
376 rows.Add(updatedRow);
377 }
378 }
379 }
380 }
381
382 return rows;
383 }
384
385 private void IndexPrimaryKeys(Table targetTable, Dictionary<string, Row> targetPrimaryKeys, Table updatedTable, Dictionary<string, Row> updatedPrimaryKeys)
386 {
387 // index the target rows
388 foreach (var row in targetTable.Rows)
389 {
390 this.AddIndexedRow(targetPrimaryKeys, row);
391
392 if ("Property" == targetTable.Name)
393 {
394 var id = row.FieldAsString(0);
395
396 if ("ProductCode" == id)
397 {
398 this.transformSummaryInfo.TargetProductCode = row.FieldAsString(1);
399
400 if ("*" == this.transformSummaryInfo.TargetProductCode)
401 {
402 this.messaging.Write(ErrorMessages.ProductCodeInvalidForTransform(row.SourceLineNumbers));
403 }
404 }
405 else if ("ProductVersion" == id)
406 {
407 this.transformSummaryInfo.TargetProductVersion = row.FieldAsString(1);
408 }
409 else if ("UpgradeCode" == id)
410 {
411 this.transformSummaryInfo.TargetUpgradeCode = row.FieldAsString(1);
412 }
413 }
414 else if ("_SummaryInformation" == targetTable.Name)
415 {
416 var id = row.FieldAsInteger(0);
417
418 if (1 == id) // PID_CODEPAGE
419 {
420 this.transformSummaryInfo.TargetSummaryInfoCodepage = row.FieldAsString(1);
421 }
422 else if (7 == id) // PID_TEMPLATE
423 {
424 this.transformSummaryInfo.TargetPlatformAndLanguage = row.FieldAsString(1);
425 }
426 else if (14 == id) // PID_PAGECOUNT
427 {
428 this.transformSummaryInfo.TargetMinimumVersion = row.FieldAsString(1);
429 }
430 }
431 }
432
433 // index the updated rows
434 foreach (var row in updatedTable.Rows)
435 {
436 this.AddIndexedRow(updatedPrimaryKeys, row);
437
438 if ("Property" == updatedTable.Name)
439 {
440 var id = row.FieldAsString(0);
441
442 if ("ProductCode" == id)
443 {
444 this.transformSummaryInfo.UpdatedProductCode = row.FieldAsString(1);
445
446 if ("*" == this.transformSummaryInfo.UpdatedProductCode)
447 {
448 this.messaging.Write(ErrorMessages.ProductCodeInvalidForTransform(row.SourceLineNumbers));
449 }
450 }
451 else if ("ProductVersion" == id)
452 {
453 this.transformSummaryInfo.UpdatedProductVersion = row.FieldAsString(1);
454 }
455 }
456 else if ("_SummaryInformation" == updatedTable.Name)
457 {
458 var id = row.FieldAsInteger(0);
459
460 if (1 == id) // PID_CODEPAGE
461 {
462 this.transformSummaryInfo.UpdatedSummaryInfoCodepage = row.FieldAsString(1);
463 }
464 else if (7 == id) // PID_TEMPLATE
465 {
466 this.transformSummaryInfo.UpdatedPlatformAndLanguage = row.FieldAsString(1);
467 }
468 else if (14 == id) // PID_PAGECOUNT
469 {
470 this.transformSummaryInfo.UpdatedMinimumVersion = row.FieldAsString(1);
471 }
472 }
473 }
474 }
475
476 private void UpdateTransformSummaryInformationTable(Table summaryInfoTable, TransformFlags validationFlags)
477 {
478 // calculate the minimum version of MSI required to process the transform
479 var minimumVersion = 100;
480
481 if (Int32.TryParse(this.transformSummaryInfo.TargetMinimumVersion, out var targetMin) && Int32.TryParse(this.transformSummaryInfo.UpdatedMinimumVersion, out var updatedMin))
482 {
483 minimumVersion = Math.Max(targetMin, updatedMin);
484 }
485
486 var summaryRows = new Dictionary<int, Row>(summaryInfoTable.Rows.Count);
487
488 foreach (var row in summaryInfoTable.Rows)
489 {
490 var id = row.FieldAsInteger(0);
491
492 summaryRows[id] = row;
493
494 if ((int)SummaryInformation.Transform.CodePage == id)
495 {
496 row.Fields[1].Data = this.transformSummaryInfo.UpdatedSummaryInfoCodepage;
497 row.Fields[1].PreviousData = this.transformSummaryInfo.TargetSummaryInfoCodepage;
498 }
499 else if ((int)SummaryInformation.Transform.TargetPlatformAndLanguage == id)
500 {
501 row[1] = this.transformSummaryInfo.TargetPlatformAndLanguage;
502 }
503 else if ((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == id)
504 {
505 row[1] = this.transformSummaryInfo.UpdatedPlatformAndLanguage;
506 }
507 else if ((int)SummaryInformation.Transform.ProductCodes == id)
508 {
509 row[1] = String.Concat(this.transformSummaryInfo.TargetProductCode, this.transformSummaryInfo.TargetProductVersion, ';', this.transformSummaryInfo.UpdatedProductCode, this.transformSummaryInfo.UpdatedProductVersion, ';', this.transformSummaryInfo.TargetUpgradeCode);
510 }
511 else if ((int)SummaryInformation.Transform.InstallerRequirement == id)
512 {
513 row[1] = minimumVersion.ToString(CultureInfo.InvariantCulture);
514 }
515 else if ((int)SummaryInformation.Transform.Security == id)
516 {
517 row[1] = "4";
518 }
519 }
520
521 if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.TargetPlatformAndLanguage))
522 {
523 var summaryRow = summaryInfoTable.CreateRow(null);
524 summaryRow[0] = (int)SummaryInformation.Transform.TargetPlatformAndLanguage;
525 summaryRow[1] = this.transformSummaryInfo.TargetPlatformAndLanguage;
526 }
527
528 if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage))
529 {
530 var summaryRow = summaryInfoTable.CreateRow(null);
531 summaryRow[0] = (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage;
532 summaryRow[1] = this.transformSummaryInfo.UpdatedPlatformAndLanguage;
533 }
534
535 if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.ValidationFlags))
536 {
537 var summaryRow = summaryInfoTable.CreateRow(null);
538 summaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags;
539 summaryRow[1] = ((int)validationFlags).ToString(CultureInfo.InvariantCulture);
540 }
541
542 if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.InstallerRequirement))
543 {
544 var summaryRow = summaryInfoTable.CreateRow(null);
545 summaryRow[0] = (int)SummaryInformation.Transform.InstallerRequirement;
546 summaryRow[1] = minimumVersion.ToString(CultureInfo.InvariantCulture);
547 }
548
549 if (!summaryRows.ContainsKey((int)SummaryInformation.Transform.Security))
550 {
551 var summaryRow = summaryInfoTable.CreateRow(null);
552 summaryRow[0] = (int)SummaryInformation.Transform.Security;
553 summaryRow[1] = "4";
554 }
555 }
556
557 private class SummaryInformationStreams
558 {
559 public string TargetSummaryInfoCodepage { get; set; }
560
561 public string TargetPlatformAndLanguage { get; set; }
562
563 public string TargetProductCode { get; set; }
564
565 public string TargetProductVersion { get; set; }
566
567 public string TargetUpgradeCode { get; set; }
568
569 public string TargetMinimumVersion { get; set; }
570
571 public string UpdatedSummaryInfoCodepage { get; set; }
572
573 public string UpdatedPlatformAndLanguage { get; set; }
574
575 public string UpdatedProductCode { get; set; }
576
577 public string UpdatedProductVersion { get; set; }
578
579 public string UpdatedMinimumVersion { get; set; }
580 }
581 }
582}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.Linq;
9 using WixToolset.Data;
10 using WixToolset.Data.Symbols;
11 using WixToolset.Extensibility.Data;
12 using WixToolset.Extensibility.Services;
13
14 internal class GetFileFacadesCommand
15 {
16 public GetFileFacadesCommand(IntermediateSection section, IWindowsInstallerBackendHelper backendHelper)
17 {
18 this.Section = section;
19 this.BackendHelper = backendHelper;
20 }
21
22 private IntermediateSection Section { get; }
23
24 private IWindowsInstallerBackendHelper BackendHelper { get; }
25
26 public List<IFileFacade> FileFacades { get; private set; }
27
28 public void Execute()
29 {
30 var facades = new List<IFileFacade>();
31
32 var assemblyFile = this.Section.Symbols.OfType<AssemblySymbol>().ToDictionary(t => t.Id.Id);
33#if TODO_PATCHING_DELTA
34 //var deltaPatchFiles = this.Section.Symbols.OfType<WixDeltaPatchFileSymbol>().ToDictionary(t => t.Id.Id);
35#endif
36
37 foreach (var file in this.Section.Symbols.OfType<FileSymbol>())
38 {
39 assemblyFile.TryGetValue(file.Id.Id, out var assembly);
40
41#if TODO_PATCHING_DELTA
42 //deltaPatchFiles.TryGetValue(file.Id.Id, out var deltaPatchFile);
43 // TODO: should we be passing along delta information to the file facade? Probably, right?
44#endif
45 var fileFacade = this.BackendHelper.CreateFileFacade(file, assembly);
46
47 facades.Add(fileFacade);
48 }
49
50#if TODO_PATCHING_DELTA
51 this.ResolveDeltaPatchSymbolPaths(deltaPatchFiles, facades);
52#endif
53
54 this.FileFacades = facades;
55 }
56
57#if TODO_PATCHING_DELTA
58 /// <summary>
59 /// Merge data from the WixPatchSymbolPaths rows into the WixDeltaPatchFile rows.
60 /// </summary>
61 public void ResolveDeltaPatchSymbolPaths(Dictionary<string, WixDeltaPatchFileSymbol> deltaPatchFiles, IEnumerable<FileFacade> facades)
62 {
63 ILookup<string, FileFacade> filesByComponent = null;
64 ILookup<string, FileFacade> filesByDirectory = null;
65 ILookup<string, FileFacade> filesByDiskId = null;
66
67 foreach (var row in this.Section.Symbols.OfType<WixDeltaPatchSymbolPathsSymbol>().OrderBy(r => r.SymbolType))
68 {
69 switch (row.SymbolType)
70 {
71 case SymbolPathType.File:
72 this.MergeSymbolPaths(row, deltaPatchFiles[row.SymbolId]);
73 break;
74
75 case SymbolPathType.Component:
76 if (null == filesByComponent)
77 {
78 filesByComponent = facades.ToLookup(f => f.File.ComponentRef);
79 }
80
81 foreach (var facade in filesByComponent[row.SymbolId])
82 {
83 this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.Id.Id]);
84 }
85 break;
86
87 case SymbolPathType.Directory:
88 if (null == filesByDirectory)
89 {
90 filesByDirectory = facades.ToLookup(f => f.File.DirectoryRef);
91 }
92
93 foreach (var facade in filesByDirectory[row.SymbolId])
94 {
95 this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.Id.Id]);
96 }
97 break;
98
99 case SymbolPathType.Media:
100 if (null == filesByDiskId)
101 {
102 filesByDiskId = facades.ToLookup(f => f.File.DiskId.ToString(CultureInfo.InvariantCulture));
103 }
104
105 foreach (var facade in filesByDiskId[row.SymbolId])
106 {
107 this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.Id.Id]);
108 }
109 break;
110
111 case SymbolPathType.Product:
112 foreach (var fileRow in deltaPatchFiles.Values)
113 {
114 this.MergeSymbolPaths(row, fileRow);
115 }
116 break;
117
118 default:
119 // error
120 break;
121 }
122 }
123 }
124
125 /// <summary>
126 /// Merge data from a row in the WixPatchSymbolsPaths table into an associated WixDeltaPatchFile row.
127 /// </summary>
128 /// <param name="row">Row from the WixPatchSymbolsPaths table.</param>
129 /// <param name="file">FileRow into which to set symbol information.</param>
130 /// <comment>This includes PreviousData as well.</comment>
131 private void MergeSymbolPaths(WixDeltaPatchSymbolPathsSymbol row, WixDeltaPatchFileSymbol file)
132 {
133 if (file.SymbolPaths is null)
134 {
135 file.SymbolPaths = row.SymbolPaths;
136 }
137 else
138 {
139 file.SymbolPaths = String.Concat(file.SymbolPaths, ";", row.SymbolPaths);
140 }
141
142 Field field = row.Fields[2];
143 if (null != field.PreviousData)
144 {
145 if (null == file.PreviousSymbols)
146 {
147 file.PreviousSymbols = field.PreviousData;
148 }
149 else
150 {
151 file.PreviousSymbols = String.Concat(file.PreviousSymbols, ";", field.PreviousData);
152 }
153 }
154 }
155#endif
156 }
157}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using WixToolset.Data;
9 using WixToolset.Data.WindowsInstaller;
10 using WixToolset.Data.WindowsInstaller.Rows;
11 using WixToolset.Extensibility.Data;
12 using WixToolset.Extensibility.Services;
13
14 internal class GetFileFacadesFromTransforms
15 {
16 public GetFileFacadesFromTransforms(IMessaging messaging, IWindowsInstallerBackendHelper backendHelper, FileSystemManager fileSystemManager, IEnumerable<SubStorage> subStorages)
17 {
18 this.Messaging = messaging;
19 this.BackendHelper = backendHelper;
20 this.FileSystemManager = fileSystemManager;
21 this.SubStorages = subStorages;
22 }
23
24 private IMessaging Messaging { get; }
25
26 private IWindowsInstallerBackendHelper BackendHelper { get; }
27
28 private FileSystemManager FileSystemManager { get; }
29
30 private IEnumerable<SubStorage> SubStorages { get; }
31
32 public List<IFileFacade> FileFacades { get; private set; }
33
34 public void Execute()
35 {
36 var allFileRows = new List<IFileFacade>();
37
38 var patchMediaFileRows = new Dictionary<int, RowDictionary<FileRow>>();
39
40 //var patchActualFileTable = this.Output.EnsureTable(this.TableDefinitions["File"]);
41
42 // Index paired transforms by name without their "#" prefix.
43 var pairedTransforms = this.SubStorages.Where(s => s.Name.StartsWith("#")).ToDictionary(s => s.Name, s => s.Data);
44
45 // Enumerate through main transforms.
46 foreach (var substorage in this.SubStorages.Where(s => !s.Name.StartsWith("#")))
47 {
48 var mainTransform = substorage.Data;
49 var mainFileTable = mainTransform.Tables["File"];
50
51 if (null == mainFileTable)
52 {
53 continue;
54 }
55
56 // Index File table of pairedTransform
57 var pairedTransform = pairedTransforms["#" + substorage.Name];
58 var pairedFileRows = new RowDictionary<FileRow>(pairedTransform.Tables["File"]);
59
60 foreach (FileRow mainFileRow in mainFileTable.Rows.Where(f => f.Operation != RowOperation.Delete))
61 {
62 var mainFileId = mainFileRow.File;
63
64 // We need compare the underlying files and include all file changes.
65 var objectField = (ObjectField)mainFileRow.Fields[9];
66 var pairedFileRow = pairedFileRows.Get(mainFileId);
67
68 // If the file is new, we always need to add it to the patch.
69 if (mainFileRow.Operation == RowOperation.Add)
70 {
71 if (null != pairedFileRow) // RowOperation.Add
72 {
73 // Always patch-added, but never non-compressed.
74 pairedFileRow.Attributes |= WindowsInstallerConstants.MsidbFileAttributesPatchAdded;
75 pairedFileRow.Attributes &= ~WindowsInstallerConstants.MsidbFileAttributesNoncompressed;
76 pairedFileRow.Fields[6].Modified = true;
77 pairedFileRow.Operation = RowOperation.Add;
78 }
79 }
80 else
81 {
82 // If PreviousData doesn't exist, target and upgrade layout point to the same location. No need to compare.
83 if (null == objectField.PreviousData)
84 {
85 if (mainFileRow.Operation == RowOperation.None)
86 {
87 continue;
88 }
89 }
90 else
91 {
92 // TODO: should this entire condition be placed in the binder file manager?
93 if (/*(0 == (PatchAttributeType.Ignore & mainWixFileRow.PatchAttributes)) &&*/
94 !this.FileSystemManager.CompareFiles(objectField.PreviousData, objectField.Data.ToString()))
95 {
96 // If the file is different, we need to mark the mainFileRow and pairedFileRow as modified.
97 mainFileRow.Operation = RowOperation.Modify;
98 if (null != pairedFileRow)
99 {
100 // Always patch-added, but never non-compressed.
101 pairedFileRow.Attributes |= WindowsInstallerConstants.MsidbFileAttributesPatchAdded;
102 pairedFileRow.Attributes &= ~WindowsInstallerConstants.MsidbFileAttributesNoncompressed;
103 pairedFileRow.Fields[6].Modified = true;
104 pairedFileRow.Operation = RowOperation.Modify;
105 }
106 }
107 else
108 {
109 // The File is same. We need mark all the attributes as unchanged.
110 mainFileRow.Operation = RowOperation.None;
111 foreach (var field in mainFileRow.Fields)
112 {
113 field.Modified = false;
114 }
115
116 if (null != pairedFileRow)
117 {
118 pairedFileRow.Attributes &= ~WindowsInstallerConstants.MsidbFileAttributesPatchAdded;
119 pairedFileRow.Fields[6].Modified = false;
120 pairedFileRow.Operation = RowOperation.None;
121 }
122 continue;
123 }
124 }
125 }
126
127 // index patch files by diskId+fileId
128 var diskId = mainFileRow.DiskId;
129
130 if (!patchMediaFileRows.TryGetValue(diskId, out var mediaFileRows))
131 {
132 mediaFileRows = new RowDictionary<FileRow>();
133 patchMediaFileRows.Add(diskId, mediaFileRows);
134 }
135
136 var patchFileRow = mediaFileRows.Get(mainFileId);
137
138 if (null == patchFileRow)
139 {
140 //patchFileRow = (FileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers);
141 patchFileRow = (FileRow)mainFileRow.TableDefinition.CreateRow(mainFileRow.SourceLineNumbers);
142 mainFileRow.CopyTo(patchFileRow);
143
144 mediaFileRows.Add(patchFileRow);
145
146#if TODO_PATCHING_DELTA
147 // TODO: should we be passing along delta information to the file facade? Probably, right?
148#endif
149 var fileFacade = this.BackendHelper.CreateFileFacade(patchFileRow);
150
151 allFileRows.Add(fileFacade);
152 }
153 else
154 {
155 // TODO: confirm the rest of data is identical?
156
157 // make sure Source is same. Otherwise we are silently ignoring a file.
158 if (0 != String.Compare(patchFileRow.Source, mainFileRow.Source, StringComparison.OrdinalIgnoreCase))
159 {
160 this.Messaging.Write(ErrorMessages.SameFileIdDifferentSource(mainFileRow.SourceLineNumbers, mainFileId, patchFileRow.Source, mainFileRow.Source));
161 }
162
163#if TODO_PATCHING_DELTA
164 // capture the previous file versions (and associated data) from this targeted instance of the baseline into the current filerow.
165 patchFileRow.AppendPreviousDataFrom(mainFileRow);
166#endif
167 }
168 }
169 }
170
171 this.FileFacades = allFileRows;
172 }
173 }
174}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.Linq;
9 using WixToolset.Data;
10 using WixToolset.Data.Symbols;
11 using WixToolset.Data.WindowsInstaller;
12 using WixToolset.Extensibility;
13 using WixToolset.Extensibility.Services;
14
15 internal class LoadTableDefinitionsCommand
16 {
17 public LoadTableDefinitionsCommand(IMessaging messaging, IntermediateSection section, IEnumerable<IWindowsInstallerBackendBinderExtension> backendExtensions)
18 {
19 this.Messaging = messaging;
20 this.Section = section;
21 this.BackendExtensions = backendExtensions;
22 }
23
24 public IMessaging Messaging { get; }
25
26 private IntermediateSection Section { get; }
27
28 private IEnumerable<IWindowsInstallerBackendBinderExtension> BackendExtensions { get; }
29
30 public TableDefinitionCollection TableDefinitions { get; private set; }
31
32 public TableDefinitionCollection Execute()
33 {
34 var tableDefinitions = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All);
35 var customColumnsById = this.Section.Symbols.OfType<WixCustomTableColumnSymbol>().ToDictionary(t => t.Id.Id);
36
37 if (customColumnsById.Any())
38 {
39 foreach (var symbol in this.Section.Symbols.OfType<WixCustomTableSymbol>())
40 {
41 var customTableDefinition = this.CreateCustomTable(symbol, customColumnsById);
42 tableDefinitions.Add(customTableDefinition);
43 }
44 }
45
46 foreach (var backendExtension in this.BackendExtensions)
47 {
48 foreach (var tableDefinition in backendExtension.TableDefinitions)
49 {
50 if (tableDefinitions.Contains(tableDefinition.Name))
51 {
52 this.Messaging.Write(ErrorMessages.DuplicateExtensionTable(backendExtension.GetType().Assembly.Location, tableDefinition.Name));
53 }
54
55 tableDefinitions.Add(tableDefinition);
56 }
57 }
58
59 this.TableDefinitions = tableDefinitions;
60 return this.TableDefinitions;
61 }
62
63 private TableDefinition CreateCustomTable(WixCustomTableSymbol symbol, Dictionary<string, WixCustomTableColumnSymbol> customColumnsById)
64 {
65 var columnNames = symbol.ColumnNamesSeparated;
66 var columns = new List<ColumnDefinition>(columnNames.Length);
67
68 foreach (var name in columnNames)
69 {
70 var column = customColumnsById[symbol.Id.Id + "/" + name];
71
72 var type = ColumnType.Unknown;
73
74 if (column.Type == IntermediateFieldType.String)
75 {
76 type = column.Localizable ? ColumnType.Localized : ColumnType.String;
77 }
78 else if (column.Type == IntermediateFieldType.Number)
79 {
80 type = ColumnType.Number;
81 }
82 else if (column.Type == IntermediateFieldType.Path)
83 {
84 type = ColumnType.Object;
85 }
86
87 var category = ColumnCategory.Unknown;
88 switch (column.Category)
89 {
90 case WixCustomTableColumnCategoryType.Text:
91 category = ColumnCategory.Text;
92 break;
93 case WixCustomTableColumnCategoryType.UpperCase:
94 category = ColumnCategory.UpperCase;
95 break;
96 case WixCustomTableColumnCategoryType.LowerCase:
97 category = ColumnCategory.LowerCase;
98 break;
99 case WixCustomTableColumnCategoryType.Integer:
100 category = ColumnCategory.Integer;
101 break;
102 case WixCustomTableColumnCategoryType.DoubleInteger:
103 category = ColumnCategory.DoubleInteger;
104 break;
105 case WixCustomTableColumnCategoryType.TimeDate:
106 category = ColumnCategory.TimeDate;
107 break;
108 case WixCustomTableColumnCategoryType.Identifier:
109 category = ColumnCategory.Identifier;
110 break;
111 case WixCustomTableColumnCategoryType.Property:
112 category = ColumnCategory.Property;
113 break;
114 case WixCustomTableColumnCategoryType.Filename:
115 category = ColumnCategory.Filename;
116 break;
117 case WixCustomTableColumnCategoryType.WildCardFilename:
118 category = ColumnCategory.WildCardFilename;
119 break;
120 case WixCustomTableColumnCategoryType.Path:
121 category = ColumnCategory.Path;
122 break;
123 case WixCustomTableColumnCategoryType.Paths:
124 category = ColumnCategory.Paths;
125 break;
126 case WixCustomTableColumnCategoryType.AnyPath:
127 category = ColumnCategory.AnyPath;
128 break;
129 case WixCustomTableColumnCategoryType.DefaultDir:
130 category = ColumnCategory.DefaultDir;
131 break;
132 case WixCustomTableColumnCategoryType.RegPath:
133 category = ColumnCategory.RegPath;
134 break;
135 case WixCustomTableColumnCategoryType.Formatted:
136 category = ColumnCategory.Formatted;
137 break;
138 case WixCustomTableColumnCategoryType.FormattedSddl:
139 category = ColumnCategory.FormattedSDDLText;
140 break;
141 case WixCustomTableColumnCategoryType.Template:
142 category = ColumnCategory.Template;
143 break;
144 case WixCustomTableColumnCategoryType.Condition:
145 category = ColumnCategory.Condition;
146 break;
147 case WixCustomTableColumnCategoryType.Guid:
148 category = ColumnCategory.Guid;
149 break;
150 case WixCustomTableColumnCategoryType.Version:
151 category = ColumnCategory.Version;
152 break;
153 case WixCustomTableColumnCategoryType.Language:
154 category = ColumnCategory.Language;
155 break;
156 case WixCustomTableColumnCategoryType.Binary:
157 category = ColumnCategory.Binary;
158 break;
159 case WixCustomTableColumnCategoryType.CustomSource:
160 category = ColumnCategory.CustomSource;
161 break;
162 case WixCustomTableColumnCategoryType.Cabinet:
163 category = ColumnCategory.Cabinet;
164 break;
165 case WixCustomTableColumnCategoryType.Shortcut:
166 category = ColumnCategory.Shortcut;
167 break;
168 case null:
169 default:
170 break;
171 }
172
173 var modularization = ColumnModularizeType.None;
174
175 switch (column.Modularize)
176 {
177 case null:
178 case WixCustomTableColumnModularizeType.None:
179 modularization = ColumnModularizeType.None;
180 break;
181 case WixCustomTableColumnModularizeType.Column:
182 modularization = ColumnModularizeType.Column;
183 break;
184 case WixCustomTableColumnModularizeType.CompanionFile:
185 modularization = ColumnModularizeType.CompanionFile;
186 break;
187 case WixCustomTableColumnModularizeType.Condition:
188 modularization = ColumnModularizeType.Condition;
189 break;
190 case WixCustomTableColumnModularizeType.ControlEventArgument:
191 modularization = ColumnModularizeType.ControlEventArgument;
192 break;
193 case WixCustomTableColumnModularizeType.ControlText:
194 modularization = ColumnModularizeType.ControlText;
195 break;
196 case WixCustomTableColumnModularizeType.Icon:
197 modularization = ColumnModularizeType.Icon;
198 break;
199 case WixCustomTableColumnModularizeType.Property:
200 modularization = ColumnModularizeType.Property;
201 break;
202 case WixCustomTableColumnModularizeType.SemicolonDelimited:
203 modularization = ColumnModularizeType.SemicolonDelimited;
204 break;
205 }
206
207 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);
208 columns.Add(columnDefinition);
209 }
210
211 var customTable = new TableDefinition(symbol.Id.Id, null, columns, symbol.Unreal);
212 return customTable;
213 }
214 }
215}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.IO;
9 using System.Linq;
10 using System.Runtime.InteropServices;
11 using System.Text;
12 using WixToolset.Core.Native.Msi;
13 using WixToolset.Core.Native.Msm;
14 using WixToolset.Data;
15 using WixToolset.Data.Symbols;
16 using WixToolset.Data.WindowsInstaller;
17 using WixToolset.Extensibility.Data;
18 using WixToolset.Extensibility.Services;
19
20 /// <summary>
21 /// Merge modules into the database at output path.
22 /// </summary>
23 internal class MergeModulesCommand
24 {
25 public MergeModulesCommand(IMessaging messaging, IEnumerable<IFileFacade> fileFacades, IntermediateSection section, IEnumerable<string> suppressedTableNames, string outputPath, string intermediateFolder)
26 {
27 this.Messaging = messaging;
28 this.FileFacades = fileFacades;
29 this.Section = section;
30 this.SuppressedTableNames = suppressedTableNames ?? Array.Empty<string>();
31 this.OutputPath = outputPath;
32 this.IntermediateFolder = intermediateFolder;
33 }
34
35 private IMessaging Messaging { get; }
36
37 private IEnumerable<IFileFacade> FileFacades { get; }
38
39 private IntermediateSection Section { get; }
40
41 private IEnumerable<string> SuppressedTableNames { get; }
42
43 private string OutputPath { get; }
44
45 private string IntermediateFolder { get; }
46
47 public void Execute()
48 {
49 var wixMergeSymbols = this.Section.Symbols.OfType<WixMergeSymbol>().ToList();
50 if (!wixMergeSymbols.Any())
51 {
52 return;
53 }
54
55 IMsmMerge2 merge = null;
56 var commit = true;
57 var logOpen = false;
58 var databaseOpen = false;
59 var logPath = Path.Combine(this.IntermediateFolder, "merge.log");
60
61 try
62 {
63 merge = MsmInterop.GetMsmMerge();
64
65 merge.OpenLog(logPath);
66 logOpen = true;
67
68 merge.OpenDatabase(this.OutputPath);
69 databaseOpen = true;
70
71 var featureModulesByMergeId = this.Section.Symbols.OfType<WixFeatureModulesSymbol>().GroupBy(t => t.WixMergeRef).ToDictionary(g => g.Key);
72
73 // process all the merge rows
74 foreach (var wixMergeRow in wixMergeSymbols)
75 {
76 var moduleOpen = false;
77
78 try
79 {
80 short mergeLanguage;
81
82 try
83 {
84 mergeLanguage = Convert.ToInt16(wixMergeRow.Language, CultureInfo.InvariantCulture);
85 }
86 catch (FormatException)
87 {
88 this.Messaging.Write(ErrorMessages.InvalidMergeLanguage(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, wixMergeRow.Language.ToString()));
89 continue;
90 }
91
92 this.Messaging.Write(VerboseMessages.OpeningMergeModule(wixMergeRow.SourceFile, mergeLanguage));
93 merge.OpenModule(wixMergeRow.SourceFile, mergeLanguage);
94 moduleOpen = true;
95
96 // If there is merge configuration data, create a callback object to contain it all.
97 ConfigurationCallback callback = null;
98 if (!String.IsNullOrEmpty(wixMergeRow.ConfigurationData))
99 {
100 callback = new ConfigurationCallback(wixMergeRow.ConfigurationData);
101 }
102
103 // Merge the module into the database that's being built.
104 this.Messaging.Write(VerboseMessages.MergingMergeModule(wixMergeRow.SourceFile));
105 merge.MergeEx(wixMergeRow.FeatureRef, wixMergeRow.DirectoryRef, callback);
106
107 // Connect any non-primary features.
108 if (featureModulesByMergeId.TryGetValue(wixMergeRow.Id.Id, out var featureModules))
109 {
110 foreach (var featureModule in featureModules)
111 {
112 this.Messaging.Write(VerboseMessages.ConnectingMergeModule(wixMergeRow.SourceFile, featureModule.FeatureRef));
113 merge.Connect(featureModule.FeatureRef);
114 }
115 }
116 }
117 catch (COMException)
118 {
119 commit = false;
120 }
121 finally
122 {
123 var mergeErrors = merge.Errors;
124
125 // display all the errors encountered during the merge operations for this module
126 for (var i = 1; i <= mergeErrors.Count; i++)
127 {
128 var mergeError = mergeErrors[i];
129 var databaseKeys = new StringBuilder();
130 var moduleKeys = new StringBuilder();
131
132 // build a string of the database keys
133 for (var j = 1; j <= mergeError.DatabaseKeys.Count; j++)
134 {
135 if (1 != j)
136 {
137 databaseKeys.Append(';');
138 }
139 databaseKeys.Append(mergeError.DatabaseKeys[j]);
140 }
141
142 // build a string of the module keys
143 for (var j = 1; j <= mergeError.ModuleKeys.Count; j++)
144 {
145 if (1 != j)
146 {
147 moduleKeys.Append(';');
148 }
149 moduleKeys.Append(mergeError.ModuleKeys[j]);
150 }
151
152 // display the merge error based on the msm error type
153 switch (mergeError.Type)
154 {
155 case MsmErrorType.msmErrorExclusion:
156 this.Messaging.Write(ErrorMessages.MergeExcludedModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, moduleKeys.ToString()));
157 break;
158 case MsmErrorType.msmErrorFeatureRequired:
159 this.Messaging.Write(ErrorMessages.MergeFeatureRequired(wixMergeRow.SourceLineNumbers, mergeError.ModuleTable, moduleKeys.ToString(), wixMergeRow.SourceFile, wixMergeRow.Id.Id));
160 break;
161 case MsmErrorType.msmErrorLanguageFailed:
162 this.Messaging.Write(ErrorMessages.MergeLanguageFailed(wixMergeRow.SourceLineNumbers, mergeError.Language, wixMergeRow.SourceFile));
163 break;
164 case MsmErrorType.msmErrorLanguageUnsupported:
165 this.Messaging.Write(ErrorMessages.MergeLanguageUnsupported(wixMergeRow.SourceLineNumbers, mergeError.Language, wixMergeRow.SourceFile));
166 break;
167 case MsmErrorType.msmErrorResequenceMerge:
168 this.Messaging.Write(WarningMessages.MergeRescheduledAction(wixMergeRow.SourceLineNumbers, mergeError.DatabaseTable, databaseKeys.ToString(), wixMergeRow.SourceFile));
169 break;
170 case MsmErrorType.msmErrorTableMerge:
171 if ("_Validation" != mergeError.DatabaseTable) // ignore merge errors in the _Validation table
172 {
173 this.Messaging.Write(WarningMessages.MergeTableFailed(wixMergeRow.SourceLineNumbers, mergeError.DatabaseTable, databaseKeys.ToString(), wixMergeRow.SourceFile));
174 }
175 break;
176 case MsmErrorType.msmErrorPlatformMismatch:
177 this.Messaging.Write(ErrorMessages.MergePlatformMismatch(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile));
178 break;
179 default:
180 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));
181 break;
182 }
183 }
184
185 if (0 >= mergeErrors.Count && !commit)
186 {
187 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));
188 }
189
190 if (moduleOpen)
191 {
192 merge.CloseModule();
193 }
194 }
195 }
196 }
197 finally
198 {
199 if (databaseOpen)
200 {
201 merge.CloseDatabase(commit);
202 }
203
204 if (logOpen)
205 {
206 merge.CloseLog();
207 }
208 }
209
210 // stop processing if an error previously occurred
211 if (this.Messaging.EncounteredError)
212 {
213 return;
214 }
215
216 using (var db = new Database(this.OutputPath, OpenDatabase.Direct))
217 {
218 // Suppress individual actions.
219 foreach (var suppressAction in this.Section.Symbols.OfType<WixSuppressActionSymbol>())
220 {
221 var tableName = suppressAction.SequenceTable.WindowsInstallerTableName();
222 if (db.TableExists(tableName))
223 {
224 var query = $"SELECT * FROM {tableName} WHERE `Action` = '{suppressAction.Action}'";
225
226 using (var view = db.OpenExecuteView(query))
227 using (var record = view.Fetch())
228 {
229 if (null != record)
230 {
231 this.Messaging.Write(WarningMessages.SuppressMergedAction(suppressAction.Action, tableName));
232 view.Modify(ModifyView.Delete, record);
233 }
234 }
235 }
236 }
237
238 // Query for merge module actions in suppressed sequences and drop them.
239 foreach (var tableName in this.SuppressedTableNames)
240 {
241 if (!db.TableExists(tableName))
242 {
243 continue;
244 }
245
246 using (var view = db.OpenExecuteView(String.Concat("SELECT `Action` FROM ", tableName)))
247 {
248 foreach (var resultRecord in view.Records)
249 {
250 this.Messaging.Write(WarningMessages.SuppressMergedAction(resultRecord.GetString(1), tableName));
251 }
252 }
253
254 // drop suppressed sequences
255 using (var view = db.OpenExecuteView(String.Concat("DROP TABLE ", tableName)))
256 {
257 }
258
259 // delete the validation rows
260 using (var view = db.OpenView(String.Concat("DELETE FROM _Validation WHERE `Table` = ?")))
261 using (var record = new Record(1))
262 {
263 record.SetString(1, tableName);
264 view.Execute(record);
265 }
266 }
267
268 // now update the Attributes column for the files from the Merge Modules
269 this.Messaging.Write(VerboseMessages.ResequencingMergeModuleFiles());
270 using (var view = db.OpenView("SELECT `Sequence`, `Attributes` FROM `File` WHERE `File`=?"))
271 {
272 foreach (var file in this.FileFacades)
273 {
274 if (!file.FromModule)
275 {
276 continue;
277 }
278
279 using (var record = new Record(1))
280 {
281 record.SetString(1, file.Id);
282 view.Execute(record);
283 }
284
285 using (var recordUpdate = view.Fetch())
286 {
287 if (null == recordUpdate)
288 {
289 throw new InvalidOperationException("Failed to fetch a File row from the database that was merged in from a module.");
290 }
291
292 recordUpdate.SetInteger(1, file.Sequence);
293
294 // Update the file attributes to match the compression specified
295 // on the Merge element or on the Package element.
296 var attributes = 0;
297
298 // Get the current value if its not null.
299 if (!recordUpdate.IsNull(2))
300 {
301 attributes = recordUpdate.GetInteger(2);
302 }
303
304 if (file.Compressed)
305 {
306 attributes |= WindowsInstallerConstants.MsidbFileAttributesCompressed;
307 attributes &= ~WindowsInstallerConstants.MsidbFileAttributesNoncompressed;
308 }
309 else if (file.Uncompressed)
310 {
311 attributes |= WindowsInstallerConstants.MsidbFileAttributesNoncompressed;
312 attributes &= ~WindowsInstallerConstants.MsidbFileAttributesCompressed;
313 }
314 else // clear all compression bits.
315 {
316 attributes &= ~WindowsInstallerConstants.MsidbFileAttributesCompressed;
317 attributes &= ~WindowsInstallerConstants.MsidbFileAttributesNoncompressed;
318 }
319
320 recordUpdate.SetInteger(2, attributes);
321
322 view.Modify(ModifyView.Update, recordUpdate);
323 }
324 }
325 }
326
327 db.Commit();
328 }
329 }
330 }
331}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.Globalization;
9 using System.Linq;
10 using System.Text;
11 using System.Text.RegularExpressions;
12 using WixToolset.Data;
13 using WixToolset.Data.Symbols;
14 using WixToolset.Data.WindowsInstaller;
15 using WixToolset.Extensibility.Services;
16
17 internal class ModularizeCommand
18 {
19 public ModularizeCommand(IBackendHelper backendHelper, WindowsInstallerData output, string modularizationSuffix, IEnumerable<WixSuppressModularizationSymbol> suppressSymbols)
20 {
21 this.BackendHelper = backendHelper;
22 this.Output = output;
23 this.ModularizationSuffix = modularizationSuffix;
24
25 // Gather all the unique suppress modularization identifiers.
26 this.SuppressModularizationIdentifiers = new HashSet<string>(suppressSymbols.Select(s => s.SuppressIdentifier));
27 }
28
29 private IBackendHelper BackendHelper { get; }
30
31 private WindowsInstallerData Output { get; }
32
33 private string ModularizationSuffix { get; }
34
35 private HashSet<string> SuppressModularizationIdentifiers { get; }
36
37 public void Execute()
38 {
39 foreach (var table in this.Output.Tables)
40 {
41 this.ModularizeTable(table);
42 }
43 }
44
45 private void ModularizeTable(Table table)
46 {
47 var modularizedColumns = new List<int>();
48
49 // find the modularized columns
50 for (var i = 0; i < table.Definition.Columns.Length; ++i)
51 {
52 if (ColumnModularizeType.None != table.Definition.Columns[i].ModularizeType)
53 {
54 modularizedColumns.Add(i);
55 }
56 }
57
58 if (0 < modularizedColumns.Count)
59 {
60 foreach (var row in table.Rows)
61 {
62 foreach (var modularizedColumn in modularizedColumns)
63 {
64 var field = row.Fields[modularizedColumn];
65
66 if (field.Data != null)
67 {
68 field.Data = this.ModularizedRowFieldValue(row, field);
69 }
70 }
71 }
72 }
73 }
74
75 private string ModularizedRowFieldValue(Row row, Field field)
76 {
77 var fieldData = field.AsString();
78
79 if (!(WindowsInstallerStandard.IsStandardAction(fieldData) || WindowsInstallerStandard.IsStandardProperty(fieldData)))
80 {
81 var modularizeType = field.Column.ModularizeType;
82
83 // special logic for the ControlEvent table's Argument column
84 // this column requires different modularization methods depending upon the value of the Event column
85 if (ColumnModularizeType.ControlEventArgument == field.Column.ModularizeType)
86 {
87 switch (row[2].ToString())
88 {
89 case "CheckExistingTargetPath": // redirectable property name
90 case "CheckTargetPath":
91 case "DoAction": // custom action name
92 case "NewDialog": // dialog name
93 case "SelectionBrowse":
94 case "SetTargetPath":
95 case "SpawnDialog":
96 case "SpawnWaitDialog":
97 if (this.BackendHelper.IsValidIdentifier(fieldData))
98 {
99 modularizeType = ColumnModularizeType.Column;
100 }
101 else
102 {
103 modularizeType = ColumnModularizeType.Property;
104 }
105 break;
106 default: // formatted
107 modularizeType = ColumnModularizeType.Property;
108 break;
109 }
110 }
111 else if (ColumnModularizeType.ControlText == field.Column.ModularizeType)
112 {
113 // icons are stored in the Binary table, so they get column-type modularization
114 if (("Bitmap" == row[2].ToString() || "Icon" == row[2].ToString()) && this.BackendHelper.IsValidIdentifier(fieldData))
115 {
116 modularizeType = ColumnModularizeType.Column;
117 }
118 else
119 {
120 modularizeType = ColumnModularizeType.Property;
121 }
122 }
123
124 switch (modularizeType)
125 {
126 case ColumnModularizeType.Column:
127 // ensure the value is an identifier (otherwise it shouldn't be modularized this way)
128 if (!this.BackendHelper.IsValidIdentifier(fieldData))
129 {
130 throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixDataStrings.EXP_CannotModularizeIllegalID, fieldData));
131 }
132
133 // if we're not supposed to suppress modularization of this identifier
134 if (!this.SuppressModularizationIdentifiers.Contains(fieldData))
135 {
136 fieldData = String.Concat(fieldData, this.ModularizationSuffix);
137 }
138 break;
139
140 case ColumnModularizeType.Property:
141 case ColumnModularizeType.Condition:
142 Regex regex;
143 if (ColumnModularizeType.Property == modularizeType)
144 {
145 regex = new Regex(@"\[(?<identifier>[#$!]?[a-zA-Z_][a-zA-Z0-9_\.]*)]", RegexOptions.Singleline | RegexOptions.ExplicitCapture);
146 }
147 else
148 {
149 Debug.Assert(ColumnModularizeType.Condition == modularizeType);
150
151 // This heinous looking regular expression is actually quite an elegant way
152 // to shred the entire condition into the identifiers that need to be
153 // modularized. Let's break it down piece by piece:
154 //
155 // 1. Look for the operators: NOT, EQV, XOR, OR, AND, IMP (plus a space). Note that the
156 // regular expression is case insensitive so we don't have to worry about
157 // all the permutations of these strings.
158 // 2. Look for quoted strings. Quoted strings are just text and are ignored
159 // outright.
160 // 3. Look for environment variables. These look like identifiers we might
161 // otherwise be interested in but start with a percent sign. Like quoted
162 // strings these enviroment variable references are ignored outright.
163 // 4. Match all identifiers that are things that need to be modularized. Note
164 // the special characters (!, $, ?, &) that denote Component and Feature states.
165 regex = new Regex(@"NOT\s|EQV\s|XOR\s|OR\s|AND\s|IMP\s|"".*?""|%[a-zA-Z_][a-zA-Z0-9_\.]*|(?<identifier>[!$\?&]?[a-zA-Z_][a-zA-Z0-9_\.]*)", RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture);
166
167 // less performant version of the above with captures showing where everything lives
168 // regex = new Regex(@"(?<operator>NOT|EQV|XOR|OR|AND|IMP)|(?<string>"".*?"")|(?<environment>%[a-zA-Z_][a-zA-Z0-9_\.]*)|(?<identifier>[!$\?&]?[a-zA-Z_][a-zA-Z0-9_\.]*)",RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture);
169 }
170
171 var matches = regex.Matches(fieldData);
172
173 var sb = new StringBuilder(fieldData);
174
175 // Notice how this code walks backward through the list
176 // because it modifies the string as we through it.
177 for (var i = matches.Count - 1; 0 <= i; i--)
178 {
179 var group = matches[i].Groups["identifier"];
180 if (group.Success)
181 {
182 var identifier = group.Value;
183 if (!WindowsInstallerStandard.IsStandardProperty(identifier) && !this.SuppressModularizationIdentifiers.Contains(identifier))
184 {
185 sb.Insert(group.Index + group.Length, this.ModularizationSuffix);
186 }
187 }
188 }
189
190 fieldData = sb.ToString();
191 break;
192
193 case ColumnModularizeType.CompanionFile:
194 // if we're not supposed to ignore this identifier and the value does not start with
195 // a digit, we must have a companion file so modularize it
196 if (!this.SuppressModularizationIdentifiers.Contains(fieldData) &&
197 0 < fieldData.Length && !Char.IsDigit(fieldData, 0))
198 {
199 fieldData = String.Concat(fieldData, this.ModularizationSuffix);
200 }
201 break;
202
203 case ColumnModularizeType.Icon:
204 if (!this.SuppressModularizationIdentifiers.Contains(fieldData))
205 {
206 var start = fieldData.LastIndexOf(".", StringComparison.Ordinal);
207 if (-1 == start)
208 {
209 fieldData = String.Concat(fieldData, this.ModularizationSuffix);
210 }
211 else
212 {
213 fieldData = String.Concat(fieldData.Substring(0, start), this.ModularizationSuffix, fieldData.Substring(start));
214 }
215 }
216 break;
217
218 case ColumnModularizeType.SemicolonDelimited:
219 var keys = fieldData.Split(';');
220 for (var i = 0; i < keys.Length; ++i)
221 {
222 if (!String.IsNullOrEmpty(keys[i]))
223 {
224 keys[i] = String.Concat(keys[i], this.ModularizationSuffix);
225 }
226 }
227
228 fieldData = String.Join(";", keys);
229 break;
230 }
231 }
232
233 return fieldData;
234 }
235 }
236}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using WixToolset.Data;
9 using WixToolset.Data.Symbols;
10 using WixToolset.Extensibility.Data;
11 using WixToolset.Extensibility.Services;
12
13 internal class OptimizeFileFacadesOrderCommand
14 {
15 public OptimizeFileFacadesOrderCommand(IBackendHelper helper, IPathResolver pathResolver, IntermediateSection section, Platform platform, List<IFileFacade> fileFacades)
16 {
17 this.BackendHelper = helper;
18 this.PathResolver = pathResolver;
19 this.Section = section;
20 this.Platform = platform;
21 this.FileFacades = fileFacades;
22 }
23
24 public List<IFileFacade> FileFacades { get; private set; }
25
26 private IBackendHelper BackendHelper { get; }
27
28 private IPathResolver PathResolver { get; }
29
30 private IntermediateSection Section { get; }
31
32 private Platform Platform { get; }
33
34 public List<IFileFacade> Execute()
35 {
36 var canonicalComponentTargetPaths = this.ComponentTargetPaths();
37
38 this.FileFacades.Sort(new FileFacadeOptimizer(canonicalComponentTargetPaths, this.Section.Type == SectionType.Module));
39
40 return this.FileFacades;
41 }
42
43 private Dictionary<string, string> ComponentTargetPaths()
44 {
45 var directories = this.ResolveDirectories();
46
47 var canonicalPathsByDirectoryId = new Dictionary<string, string>();
48 foreach (var component in this.Section.Symbols.OfType<ComponentSymbol>())
49 {
50 var directoryPath = this.PathResolver.GetCanonicalDirectoryPath(directories, null, component.DirectoryRef, this.Platform);
51 canonicalPathsByDirectoryId.Add(component.Id.Id, directoryPath);
52 }
53
54 return canonicalPathsByDirectoryId;
55 }
56
57 private Dictionary<string, IResolvedDirectory> ResolveDirectories()
58 {
59 var targetPathsByDirectoryId = new Dictionary<string, IResolvedDirectory>();
60
61 // Get the target paths for all directories.
62 foreach (var directory in this.Section.Symbols.OfType<DirectorySymbol>())
63 {
64 var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(directory.ParentDirectoryRef, directory.Name);
65 targetPathsByDirectoryId.Add(directory.Id.Id, resolvedDirectory);
66 }
67
68 return targetPathsByDirectoryId;
69 }
70
71 private class FileFacadeOptimizer : IComparer<IFileFacade>
72 {
73 public FileFacadeOptimizer(Dictionary<string, string> componentTargetPaths, bool optimizingMergeModule)
74 {
75 this.ComponentTargetPaths = componentTargetPaths;
76 this.OptimizingMergeModule = optimizingMergeModule;
77 }
78
79 private Dictionary<string, string> ComponentTargetPaths { get; }
80
81 private bool OptimizingMergeModule { get; }
82
83 public int Compare(IFileFacade x, IFileFacade y)
84 {
85 // First group files by DiskId but ignore if processing a Merge Module
86 // because Merge Modules don't have separate disks.
87 var compare = this.OptimizingMergeModule ? 0 : x.DiskId.CompareTo(y.DiskId);
88
89 if (compare != 0)
90 {
91 return compare;
92 }
93
94 // Next try to group files by target install directory.
95 if (this.ComponentTargetPaths.TryGetValue(x.ComponentRef, out var canonicalX) &&
96 this.ComponentTargetPaths.TryGetValue(y.ComponentRef, out var canonicalY))
97 {
98 compare = String.Compare(canonicalX, canonicalY, StringComparison.Ordinal);
99
100 if (compare != 0)
101 {
102 return compare;
103 }
104 }
105
106 // TODO: Consider sorting these facades even smarter by file size or file extension
107 // or other creative ideas to get optimal install speed out of MSI.
108 compare = String.Compare(x.FileName, y.FileName, StringComparison.Ordinal);
109
110 if (compare != 0)
111 {
112 return compare;
113 }
114
115 return String.Compare(x.Id, y.Id, StringComparison.Ordinal);
116 }
117 }
118 }
119}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using WixToolset.Data.WindowsInstaller;
6
7 internal class PatchTransform
8 {
9 public PatchTransform(string baseline, WindowsInstallerData transform)
10 {
11 this.Baseline = baseline;
12 this.Transform = transform;
13 }
14
15 public string Baseline { get; }
16
17 public WindowsInstallerData Transform { get; }
18 }
19}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using WixToolset.Data;
9 using WixToolset.Data.Symbols;
10 using WixToolset.Extensibility.Services;
11
12 internal class ProcessDependencyReferencesCommand
13 {
14 // The root registry key for the dependency extension. We write to Software\Classes explicitly
15 // based on the current security context instead of HKCR. See
16 // http://msdn.microsoft.com/en-us/library/ms724475(VS.85).aspx for more information.
17 private const string DependencyRegistryRoot = @"Software\Classes\Installer\Dependencies\";
18 private const string RegistryDependents = "Dependents";
19
20 public ProcessDependencyReferencesCommand(IBackendHelper backendHelper, IntermediateSection section, IEnumerable<WixDependencyRefSymbol> dependencyRefSymbols)
21 {
22 this.BackendHelper = backendHelper;
23 this.Section = section;
24 this.DependencyRefSymbols = dependencyRefSymbols;
25 }
26
27 private IBackendHelper BackendHelper { get; }
28
29 private IntermediateSection Section { get; }
30
31 private IEnumerable<WixDependencyRefSymbol> DependencyRefSymbols { get; }
32
33 public void Execute()
34 {
35 var wixDependencyRows = this.Section.Symbols.OfType<WixDependencySymbol>().ToDictionary(d => d.Id.Id);
36 var wixDependencyProviderRows = this.Section.Symbols.OfType<WixDependencyProviderSymbol>().ToDictionary(d => d.Id.Id);
37
38 // For each relationship, get the provides and requires rows to generate registry values.
39 foreach (var wixDependencyRefRow in this.DependencyRefSymbols)
40 {
41 var providesId = wixDependencyRefRow.WixDependencyProviderRef;
42 var requiresId = wixDependencyRefRow.WixDependencyRef;
43
44 // If we do not find both symbols, skip the registry key generation.
45 if (!wixDependencyRows.TryGetValue(requiresId, out var wixDependencyRow))
46 {
47 continue;
48 }
49
50 if (!wixDependencyProviderRows.TryGetValue(providesId, out var wixDependencyProviderRow))
51 {
52 continue;
53 }
54
55 // Format the root registry key using the required provider key and the current provider key.
56 var requiresKey = wixDependencyRow.Id.Id;
57 var providesKey = wixDependencyRow.ProviderKey;
58 var keyRequires = String.Format(@"{0}{1}\{2}\{3}", DependencyRegistryRoot, requiresKey, RegistryDependents, providesKey);
59
60 // Get the component ID from the provider.
61 var componentId = wixDependencyProviderRow.ParentRef;
62
63 var id = this.BackendHelper.GenerateIdentifier("reg", providesId, requiresId, "(Default)");
64 this.Section.AddSymbol(new RegistrySymbol(wixDependencyRefRow.SourceLineNumbers, new Identifier(AccessModifier.Section, id))
65 {
66 ComponentRef = componentId,
67 Root = RegistryRootType.MachineUser,
68 Key = keyRequires,
69 Name = "*",
70 });
71
72 if (!String.IsNullOrEmpty(wixDependencyRow.MinVersion))
73 {
74 id = this.BackendHelper.GenerateIdentifier("reg", providesId, requiresId, "MinVersion");
75 this.Section.AddSymbol(new RegistrySymbol(wixDependencyRefRow.SourceLineNumbers, new Identifier(AccessModifier.Section, id))
76 {
77 ComponentRef = componentId,
78 Root = RegistryRootType.MachineUser,
79 Key = keyRequires,
80 Name = "MinVersion",
81 Value = wixDependencyRow.MinVersion
82 });
83 }
84
85 var maxVersion = (string)wixDependencyRow[3];
86 if (!String.IsNullOrEmpty(wixDependencyRow.MaxVersion))
87 {
88 id = this.BackendHelper.GenerateIdentifier("reg", providesId, requiresId, "MaxVersion");
89 this.Section.AddSymbol(new RegistrySymbol(wixDependencyRefRow.SourceLineNumbers, new Identifier(AccessModifier.Section, id))
90 {
91 ComponentRef = componentId,
92 Root = RegistryRootType.MachineUser,
93 Key = keyRequires,
94 Name = "MaxVersion",
95 Value = wixDependencyRow.MaxVersion
96 });
97 }
98
99 if (wixDependencyRow.Attributes != WixDependencySymbolAttributes.None)
100 {
101 id = this.BackendHelper.GenerateIdentifier("reg", providesId, requiresId, "Attributes");
102 this.Section.AddSymbol(new RegistrySymbol(wixDependencyRefRow.SourceLineNumbers, new Identifier(AccessModifier.Section, id))
103 {
104 ComponentRef = componentId,
105 Root = RegistryRootType.MachineUser,
106 Key = keyRequires,
107 Name = "Attributes",
108 Value = String.Concat("#", (int)wixDependencyRow.Attributes)
109 });
110 }
111 }
112 }
113 }
114}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 using System.Xml;
10 using WixToolset.Data;
11 using WixToolset.Data.Symbols;
12
13 internal class ProcessPackageSoftwareTagsCommand
14 {
15 public ProcessPackageSoftwareTagsCommand(IntermediateSection section, IEnumerable<WixProductTagSymbol> softwareTags, string intermediateFolder)
16 {
17 this.Section = section;
18 this.SoftwareTags = softwareTags;
19 this.IntermediateFolder = intermediateFolder;
20 }
21
22 private string IntermediateFolder { get; }
23
24 private IntermediateSection Section { get; }
25
26 private IEnumerable<WixProductTagSymbol> SoftwareTags { get; }
27
28 public void Execute()
29 {
30 string productName = null;
31 string productVersion = null;
32 string manufacturer = null;
33 string upgradeCode = null;
34
35 var summaryInfo = this.Section.Symbols.OfType<SummaryInformationSymbol>().FirstOrDefault(s => s.PropertyId == SummaryInformationType.PackageCode);
36 var packageCode = NormalizeGuid(summaryInfo?.Value);
37
38 foreach (var property in this.Section.Symbols.OfType<PropertySymbol>())
39 {
40 switch (property.Id.Id)
41 {
42 case "ProductName":
43 productName = property.Value;
44 break;
45 case "ProductVersion":
46 productVersion = property.Value;
47 break;
48 case "Manufacturer":
49 manufacturer = property.Value;
50 break;
51 case "UpgradeCode":
52 upgradeCode = NormalizeGuid(property.Value);
53 break;
54 }
55 }
56
57 var fileSymbolsById = this.Section.Symbols.OfType<FileSymbol>().Where(f => f.Id != null).ToDictionary(f => f.Id.Id);
58
59 var workingFolder = Path.Combine(this.IntermediateFolder, "_swidtag");
60
61 Directory.CreateDirectory(workingFolder);
62
63 foreach (var tagRow in this.SoftwareTags)
64 {
65 if (fileSymbolsById.TryGetValue(tagRow.FileRef, out var fileSymbol))
66 {
67 var uniqueId = String.Concat("msi:package/", packageCode);
68 var persistentId = String.IsNullOrEmpty(upgradeCode) ? null : String.Concat("msi:upgrade/", upgradeCode);
69
70 // Write the tag file.
71 fileSymbol.Source = new IntermediateFieldPathValue { Path = Path.Combine(workingFolder, fileSymbol.Name) };
72
73 using (var fs = new FileStream(fileSymbol.Source.Path, FileMode.Create))
74 {
75 CreateTagFile(fs, uniqueId, productName, productVersion, tagRow.Regid, manufacturer, persistentId);
76 }
77
78 // Ensure the matching "SoftwareIdentificationTag" row exists and
79 // is populated correctly.
80 this.Section.AddSymbol(new SoftwareIdentificationTagSymbol(tagRow.SourceLineNumbers, tagRow.Id)
81 {
82 FileRef = fileSymbol.Id.Id,
83 Regid = tagRow.Regid,
84 TagId = uniqueId,
85 PersistentId = persistentId
86 });
87 }
88 }
89 }
90
91 private static string NormalizeGuid(string guidString)
92 {
93 if (Guid.TryParse(guidString, out var guid))
94 {
95 return guid.ToString("D").ToUpperInvariant();
96 }
97
98 return guidString;
99 }
100
101 private static void CreateTagFile(Stream stream, string uniqueId, string name, string version, string regid, string manufacturer, string persistendId)
102 {
103 var versionScheme = Version.TryParse(version, out _) ? "multipartnumeric" : "alphanumeric";
104
105 using (var writer = XmlWriter.Create(stream, new XmlWriterSettings { Indent = true }))
106 {
107 writer.WriteStartDocument();
108 writer.WriteStartElement("SoftwareIdentity", "http://standards.iso.org/iso/19770/-2/2015/schema.xsd");
109 writer.WriteAttributeString("tagId", uniqueId);
110 writer.WriteAttributeString("name", name);
111 writer.WriteAttributeString("version", version);
112 writer.WriteAttributeString("versionScheme", versionScheme);
113
114 writer.WriteStartElement("Entity");
115 writer.WriteAttributeString("name", manufacturer);
116 writer.WriteAttributeString("regid", regid);
117 writer.WriteAttributeString("role", "softwareCreator tagCreator");
118 writer.WriteEndElement(); // </Entity>
119
120 if (!String.IsNullOrEmpty(persistendId))
121 {
122 writer.WriteStartElement("Meta");
123 writer.WriteAttributeString("persistentId", persistendId);
124 writer.WriteEndElement(); // </Meta>
125 }
126
127 writer.WriteEndElement(); // </SoftwareIdentity>
128 }
129 }
130 }
131}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using WixToolset.Data;
9 using WixToolset.Data.Symbols;
10 using WixToolset.Extensibility.Services;
11
12 internal class ProcessPropertiesCommand
13 {
14 public ProcessPropertiesCommand(IntermediateSection section, WixPackageSymbol packageSymbol, int fallbackLcid, bool populateDelayedVariables, IBackendHelper backendHelper)
15 {
16 this.Section = section;
17 this.PackageSymbol = packageSymbol;
18 this.FallbackLcid = fallbackLcid;
19 this.PopulateDelayedVariables = populateDelayedVariables;
20 this.BackendHelper = backendHelper;
21 }
22
23 private IntermediateSection Section { get; }
24
25 private WixPackageSymbol PackageSymbol { get; }
26
27 private int FallbackLcid { get; }
28
29 private bool PopulateDelayedVariables { get; }
30
31 private IBackendHelper BackendHelper { get; }
32
33 public Dictionary<string, string> DelayedVariablesCache { get; private set; }
34
35 public string ProductLanguage { get; private set; }
36
37 public void Execute()
38 {
39 PropertySymbol languageSymbol = null;
40 var variableCache = this.PopulateDelayedVariables ? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) : null;
41
42 if (SectionType.Product == this.Section.Type || variableCache != null)
43 {
44 foreach (var propertySymbol in this.Section.Symbols.OfType<PropertySymbol>())
45 {
46 // Set the ProductCode if it is to be generated.
47 if ("ProductCode" == propertySymbol.Id.Id && "*".Equals(propertySymbol.Value, StringComparison.Ordinal))
48 {
49 propertySymbol.Value = this.BackendHelper.CreateGuid();
50
51#if TODO_PATCHING // Is this still necessary?
52 // Update the target ProductCode in any instance transforms.
53 foreach (SubStorage subStorage in this.Output.SubStorages)
54 {
55 Output subStorageOutput = subStorage.Data;
56 if (OutputType.Transform != subStorageOutput.Type)
57 {
58 continue;
59 }
60
61 Table instanceSummaryInformationTable = subStorageOutput.Tables["_SummaryInformation"];
62 foreach (Row row in instanceSummaryInformationTable.Rows)
63 {
64 if ((int)SummaryInformation.Transform.ProductCodes == row.FieldAsInteger(0))
65 {
66 row[1] = row.FieldAsString(1).Replace("*", propertyRow.Value);
67 break;
68 }
69 }
70 }
71#endif
72 }
73 else if ("ProductLanguage" == propertySymbol.Id.Id)
74 {
75 languageSymbol = propertySymbol;
76 }
77
78 // Add the property name and value to the variableCache.
79 if (variableCache != null)
80 {
81 variableCache[$"property.{propertySymbol.Id.Id}"] = propertySymbol.Value;
82 }
83 }
84
85 if (this.Section.Type == SectionType.Product && String.IsNullOrEmpty(languageSymbol?.Value))
86 {
87 if (languageSymbol == null)
88 {
89 languageSymbol = this.Section.AddSymbol(new PropertySymbol(this.PackageSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, "ProductLanguage")));
90 }
91
92 this.PackageSymbol.Language = this.FallbackLcid.ToString();
93 languageSymbol.Value = this.FallbackLcid.ToString();
94 }
95 }
96
97 this.DelayedVariablesCache = variableCache;
98 this.ProductLanguage = languageSymbol?.Value;
99 }
100 }
101}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 using WixToolset.Core.Native.Msi;
10 using WixToolset.Data;
11 using WixToolset.Data.Symbols;
12 using WixToolset.Extensibility.Data;
13 using WixToolset.Extensibility.Services;
14
15 /// <summary>
16 /// Defines the file transfers necessary to layout the uncompressed files.
17 /// </summary>
18 internal class ProcessUncompressedFilesCommand
19 {
20 public ProcessUncompressedFilesCommand(IntermediateSection section, IBackendHelper backendHelper, IPathResolver pathResolver)
21 {
22 this.Section = section;
23 this.BackendHelper = backendHelper;
24 this.PathResolver = pathResolver;
25 }
26
27 private IntermediateSection Section { get; }
28
29 public IBackendHelper BackendHelper { get; }
30
31 public IPathResolver PathResolver { get; }
32
33 public string DatabasePath { private get; set; }
34
35 public IEnumerable<IFileFacade> FileFacades { private get; set; }
36
37 public string LayoutDirectory { private get; set; }
38
39 public bool Compressed { private get; set; }
40
41 public bool LongNamesInImage { private get; set; }
42
43 public Func<MediaSymbol, string, string, string> ResolveMedia { private get; set; }
44
45 public IEnumerable<IFileTransfer> FileTransfers { get; private set; }
46
47 public IEnumerable<ITrackedFile> TrackedFiles { get; private set; }
48
49 public void Execute()
50 {
51 var fileTransfers = new List<IFileTransfer>();
52
53 var trackedFiles = new List<ITrackedFile>();
54
55 var directories = new Dictionary<string, IResolvedDirectory>();
56
57 var mediaRows = this.Section.Symbols.OfType<MediaSymbol>().ToDictionary(t => t.DiskId);
58
59 using (var db = new Database(this.DatabasePath, OpenDatabase.ReadOnly))
60 {
61 using (var directoryView = db.OpenExecuteView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`"))
62 {
63 foreach (var directoryRecord in directoryView.Records)
64 {
65 var sourceName = this.BackendHelper.GetMsiFileName(directoryRecord.GetString(3), true, this.LongNamesInImage);
66
67 var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(directoryRecord.GetString(2), sourceName);
68
69 directories.Add(directoryRecord.GetString(1), resolvedDirectory);
70 }
71 }
72
73 using (var fileView = db.OpenView("SELECT `Directory_`, `FileName` FROM `Component`, `File` WHERE `Component`.`Component`=`File`.`Component_` AND `File`.`File`=?"))
74 {
75 using (var fileQueryRecord = new Record(1))
76 {
77 // for each file in the array of uncompressed files
78 foreach (var facade in this.FileFacades)
79 {
80 var mediaSymbol = mediaRows[facade.DiskId];
81 string relativeFileLayoutPath = null;
82 var mediaLayoutFolder = mediaSymbol.Layout;
83
84 var mediaLayoutDirectory = this.ResolveMedia(mediaSymbol, mediaLayoutFolder, this.LayoutDirectory);
85
86 // setup up the query record and find the appropriate file in the
87 // previously executed file view
88 fileQueryRecord[1] = facade.Id;
89 fileView.Execute(fileQueryRecord);
90
91 using (var fileRecord = fileView.Fetch())
92 {
93 if (null == fileRecord)
94 {
95 throw new WixException(ErrorMessages.FileIdentifierNotFound(facade.SourceLineNumber, facade.Id));
96 }
97
98 relativeFileLayoutPath = this.PathResolver.GetFileSourcePath(directories, fileRecord[1], fileRecord[2], this.Compressed, this.LongNamesInImage);
99 }
100
101 // finally put together the base media layout path and the relative file layout path
102 var fileLayoutPath = Path.Combine(mediaLayoutDirectory, relativeFileLayoutPath);
103
104 var transfer = this.BackendHelper.CreateFileTransfer(facade.SourcePath, fileLayoutPath, false, facade.SourceLineNumber);
105 fileTransfers.Add(transfer);
106
107 // Track the location where the cabinet will be placed. If the transfer is
108 // redundant then then the file should not be cleaned. This is important
109 // because if the source and destination of the transfer is the same, we
110 // don't want to clean the file because we'd be deleting the original
111 // (and that would be bad).
112 var tracked = this.BackendHelper.TrackFile(transfer.Destination, TrackedFileType.Final, facade.SourceLineNumber);
113 tracked.Clean = !transfer.Redundant;
114
115 trackedFiles.Add(tracked);
116 }
117 }
118 }
119 }
120
121 this.FileTransfers = fileTransfers;
122 this.TrackedFiles = trackedFiles;
123 }
124 }
125}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.Linq;
9 using WixToolset.Data;
10 using WixToolset.Data.Symbols;
11 using WixToolset.Data.WindowsInstaller;
12 using WixToolset.Extensibility.Services;
13
14 /// <summary>
15 /// Set sequence numbers for all the actions and create symbols in the output object.
16 /// </summary>
17 internal class SequenceActionsCommand
18 {
19 public SequenceActionsCommand(IMessaging messaging, IntermediateSection section)
20 {
21 this.Messaging = messaging;
22 this.Section = section;
23
24 this.RelativeActionsForActions = new Dictionary<string, RelativeActions>();
25 }
26
27 private IMessaging Messaging { get; }
28
29 private IntermediateSection Section { get; }
30
31 private Dictionary<string, RelativeActions> RelativeActionsForActions { get; }
32
33 public void Execute()
34 {
35 var requiredActionSymbols = new Dictionary<string, WixActionSymbol>();
36
37 // Index all the action symbols and look for collisions.
38 foreach (var actionSymbol in this.Section.Symbols.OfType<WixActionSymbol>())
39 {
40 if (actionSymbol.Overridable) // overridable action
41 {
42 if (requiredActionSymbols.TryGetValue(actionSymbol.Id.Id, out var collidingActionSymbol))
43 {
44 if (collidingActionSymbol.Overridable)
45 {
46 this.Messaging.Write(ErrorMessages.OverridableActionCollision(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action));
47 if (null != collidingActionSymbol.SourceLineNumbers)
48 {
49 this.Messaging.Write(ErrorMessages.OverridableActionCollision2(collidingActionSymbol.SourceLineNumbers));
50 }
51 }
52 }
53 else
54 {
55 requiredActionSymbols.Add(actionSymbol.Id.Id, actionSymbol);
56 }
57 }
58 else // unsequenced or sequenced action.
59 {
60 // Unsequenced action (allowed for certain standard actions).
61 if (null == actionSymbol.Before && null == actionSymbol.After && !actionSymbol.Sequence.HasValue)
62 {
63 if (WindowsInstallerStandard.TryGetStandardAction(actionSymbol.Id.Id, out var standardAction))
64 {
65 // Populate the sequence from the standard action
66 actionSymbol.Sequence = standardAction.Sequence;
67 }
68 else // not a supported unscheduled action.
69 {
70 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.");
71 }
72 }
73
74 if (requiredActionSymbols.TryGetValue(actionSymbol.Id.Id, out var collidingActionSymbol) && !collidingActionSymbol.Overridable)
75 {
76 this.Messaging.Write(ErrorMessages.ActionCollision(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action));
77 if (null != collidingActionSymbol.SourceLineNumbers)
78 {
79 this.Messaging.Write(ErrorMessages.ActionCollision2(collidingActionSymbol.SourceLineNumbers));
80 }
81 }
82 else
83 {
84 requiredActionSymbols[actionSymbol.Id.Id] = actionSymbol;
85 }
86 }
87 }
88
89 // Get the standard actions required based on symbols in the section.
90 var requiredStandardActions = this.GetRequiredStandardActions();
91
92 // Add the overridable action symbols that are not overridden to the required action symbols.
93 foreach (var actionSymbol in requiredStandardActions.Values)
94 {
95 if (!requiredActionSymbols.ContainsKey(actionSymbol.Id.Id))
96 {
97 requiredActionSymbols.Add(actionSymbol.Id.Id, actionSymbol);
98 }
99 }
100
101 // Suppress the required actions that are overridable.
102 foreach (var suppressActionSymbol in this.Section.Symbols.OfType<WixSuppressActionSymbol>())
103 {
104 var key = suppressActionSymbol.Id.Id;
105
106 // If there is an overridable symbol to suppress; suppress it. There is no warning if there
107 // is no action to suppress because the action may be suppressed from a merge module in
108 // the binder.
109 if (requiredActionSymbols.TryGetValue(key, out var requiredActionSymbol))
110 {
111 if (requiredActionSymbol.Overridable)
112 {
113 this.Messaging.Write(WarningMessages.SuppressAction(suppressActionSymbol.SourceLineNumbers, suppressActionSymbol.Action, suppressActionSymbol.SequenceTable.ToString()));
114 if (null != requiredActionSymbol.SourceLineNumbers)
115 {
116 this.Messaging.Write(WarningMessages.SuppressAction2(requiredActionSymbol.SourceLineNumbers));
117 }
118
119 requiredActionSymbols.Remove(key);
120 }
121 else // suppressing a non-overridable action symbol
122 {
123 this.Messaging.Write(ErrorMessages.SuppressNonoverridableAction(suppressActionSymbol.SourceLineNumbers, suppressActionSymbol.SequenceTable.ToString(), suppressActionSymbol.Action));
124 if (null != requiredActionSymbol.SourceLineNumbers)
125 {
126 this.Messaging.Write(ErrorMessages.SuppressNonoverridableAction2(requiredActionSymbol.SourceLineNumbers));
127 }
128 }
129 }
130 }
131
132 // A dictionary used for detecting cyclic references among action symbols.
133 var firstReference = new Dictionary<WixActionSymbol, WixActionSymbol>();
134
135 // Build up dependency trees of the relatively scheduled actions.
136 // Use ToList() to create a copy of the required action symbols so that new symbols can
137 // be added while enumerating.
138 foreach (var actionSymbol in requiredActionSymbols.Values.ToList())
139 {
140 if (!actionSymbol.Sequence.HasValue)
141 {
142 // check for standard actions that don't have a sequence number in a merge module
143 if (SectionType.Module == this.Section.Type && WindowsInstallerStandard.IsStandardAction(actionSymbol.Action))
144 {
145 this.Messaging.Write(ErrorMessages.StandardActionRelativelyScheduledInModule(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action));
146 }
147
148 this.SequenceActionSymbol(actionSymbol, requiredActionSymbols, firstReference);
149 }
150 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
151 {
152 this.Messaging.Write(ErrorMessages.CustomActionSequencedInModule(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action));
153 }
154 }
155
156 // Look for standard actions with sequence restrictions that aren't necessarily scheduled based
157 // on the presence of a particular table.
158 if (requiredActionSymbols.ContainsKey("InstallExecuteSequence/DuplicateFiles") && !requiredActionSymbols.ContainsKey("InstallExecuteSequence/InstallFiles"))
159 {
160 WindowsInstallerStandard.TryGetStandardAction("InstallExecuteSequence/InstallFiles", out var standardAction);
161 requiredActionSymbols.Add(standardAction.Id.Id, standardAction);
162 }
163
164 // Schedule actions.
165 List<WixActionSymbol> scheduledActionSymbols;
166 if (SectionType.Module == this.Section.Type)
167 {
168 scheduledActionSymbols = requiredActionSymbols.Values.ToList();
169 }
170 else
171 {
172 scheduledActionSymbols = this.ScheduleActions(requiredActionSymbols);
173 }
174
175 // Remove all existing WixActionSymbols from the section then add the
176 // scheduled actions back to the section.
177 var removeActionSymbols = this.Section.Symbols.Where(s => s.Definition.Type == SymbolDefinitionType.WixAction).ToList();
178
179 foreach (var removeSymbol in removeActionSymbols)
180 {
181 this.Section.RemoveSymbol(removeSymbol);
182 }
183
184 foreach (var action in scheduledActionSymbols)
185 {
186 this.Section.AddSymbol(action);
187 }
188 }
189
190 private Dictionary<string, WixActionSymbol> GetRequiredStandardActions()
191 {
192 var overridableActionSymbols = new Dictionary<string, WixActionSymbol>();
193
194 var requiredActionIds = this.GetRequiredActionIds();
195
196 foreach (var actionId in requiredActionIds)
197 {
198 WindowsInstallerStandard.TryGetStandardAction(actionId, out var standardAction);
199 overridableActionSymbols.Add(standardAction.Id.Id, standardAction);
200 }
201
202 return overridableActionSymbols;
203 }
204
205 private List<WixActionSymbol> ScheduleActions(Dictionary<string, WixActionSymbol> requiredActionSymbols)
206 {
207 var scheduledActionSymbols = new List<WixActionSymbol>();
208
209 // Process each sequence table individually.
210 foreach (SequenceTable sequenceTable in Enum.GetValues(typeof(SequenceTable)))
211 {
212 // Create a collection of just the action symbols in this sequence
213 var sequenceActionSymbols = requiredActionSymbols.Values.Where(a => a.SequenceTable == sequenceTable).ToList();
214
215 // Schedule the absolutely scheduled actions (by sorting them by their sequence numbers).
216 var absoluteActionSymbols = new List<WixActionSymbol>();
217 foreach (var actionSymbol in sequenceActionSymbols)
218 {
219 if (actionSymbol.Sequence.HasValue)
220 {
221 // Look for sequence number collisions
222 foreach (var sequenceScheduledActionSymbol in absoluteActionSymbols)
223 {
224 if (sequenceScheduledActionSymbol.Sequence == actionSymbol.Sequence)
225 {
226 this.Messaging.Write(WarningMessages.ActionSequenceCollision(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action, sequenceScheduledActionSymbol.Action, actionSymbol.Sequence ?? 0));
227 if (null != sequenceScheduledActionSymbol.SourceLineNumbers)
228 {
229 this.Messaging.Write(WarningMessages.ActionSequenceCollision2(sequenceScheduledActionSymbol.SourceLineNumbers));
230 }
231 }
232 }
233
234 absoluteActionSymbols.Add(actionSymbol);
235 }
236 }
237
238 absoluteActionSymbols.Sort((x, y) => (x.Sequence ?? 0).CompareTo(y.Sequence ?? 0));
239
240 // Schedule the relatively scheduled actions (by resolving the dependency trees).
241 var previousUsedSequence = 0;
242 var relativeActionSymbols = new List<WixActionSymbol>();
243 for (int j = 0; j < absoluteActionSymbols.Count; j++)
244 {
245 var absoluteActionSymbol = absoluteActionSymbols[j];
246
247 // Get all the relatively scheduled action symbols occuring before and after this absolutely scheduled action symbol.
248 var relativeActions = this.GetAllRelativeActionsForSequenceType(sequenceTable, absoluteActionSymbol);
249
250 // Check for relatively scheduled actions occuring before/after a special action
251 // (those actions with a negative sequence number).
252 if (absoluteActionSymbol.Sequence < 0 && (relativeActions.PreviousActions.Any() || relativeActions.NextActions.Any()))
253 {
254 // Create errors for all the before actions.
255 foreach (var actionSymbol in relativeActions.PreviousActions)
256 {
257 this.Messaging.Write(ErrorMessages.ActionScheduledRelativeToTerminationAction(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action, absoluteActionSymbol.Action));
258 }
259
260 // Create errors for all the after actions.
261 foreach (var actionSymbol in relativeActions.NextActions)
262 {
263 this.Messaging.Write(ErrorMessages.ActionScheduledRelativeToTerminationAction(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action, absoluteActionSymbol.Action));
264 }
265
266 // If there is source line information for the absolutely scheduled action display it
267 if (absoluteActionSymbol.SourceLineNumbers != null)
268 {
269 this.Messaging.Write(ErrorMessages.ActionScheduledRelativeToTerminationAction2(absoluteActionSymbol.SourceLineNumbers));
270 }
271
272 continue;
273 }
274
275 // Schedule the action symbols before this one.
276 var unusedSequence = absoluteActionSymbol.Sequence - 1;
277 for (var i = relativeActions.PreviousActions.Count - 1; i >= 0; i--)
278 {
279 var relativeActionSymbol = relativeActions.PreviousActions[i];
280
281 // look for collisions
282 if (unusedSequence == previousUsedSequence)
283 {
284 this.Messaging.Write(ErrorMessages.NoUniqueActionSequenceNumber(relativeActionSymbol.SourceLineNumbers, relativeActionSymbol.SequenceTable.ToString(), relativeActionSymbol.Action, absoluteActionSymbol.Action));
285 if (absoluteActionSymbol.SourceLineNumbers != null)
286 {
287 this.Messaging.Write(ErrorMessages.NoUniqueActionSequenceNumber2(absoluteActionSymbol.SourceLineNumbers));
288 }
289
290 unusedSequence++;
291 }
292
293 relativeActionSymbol.Sequence = unusedSequence;
294 relativeActionSymbols.Add(relativeActionSymbol);
295
296 unusedSequence--;
297 }
298
299 // Determine the next used action sequence number.
300 var nextUsedSequence = Int16.MaxValue + 1;
301 if (absoluteActionSymbols.Count > j + 1)
302 {
303 nextUsedSequence = absoluteActionSymbols[j + 1].Sequence ?? 0;
304 }
305
306 // Schedule the action symbols after this one.
307 unusedSequence = absoluteActionSymbol.Sequence + 1;
308 for (var i = 0; i < relativeActions.NextActions.Count; i++)
309 {
310 var relativeActionSymbol = relativeActions.NextActions[i];
311
312 if (unusedSequence == nextUsedSequence)
313 {
314 this.Messaging.Write(ErrorMessages.NoUniqueActionSequenceNumber(relativeActionSymbol.SourceLineNumbers, relativeActionSymbol.SequenceTable.ToString(), relativeActionSymbol.Action, absoluteActionSymbol.Action));
315 if (absoluteActionSymbol.SourceLineNumbers != null)
316 {
317 this.Messaging.Write(ErrorMessages.NoUniqueActionSequenceNumber2(absoluteActionSymbol.SourceLineNumbers));
318 }
319
320 unusedSequence--;
321 }
322
323 relativeActionSymbol.Sequence = unusedSequence;
324 relativeActionSymbols.Add(relativeActionSymbol);
325
326 unusedSequence++;
327 }
328
329 // keep track of this sequence number as the previous used sequence number for the next iteration
330 previousUsedSequence = absoluteActionSymbol.Sequence ?? 0;
331 }
332
333 // add the absolutely and relatively scheduled actions to the list of scheduled actions
334 scheduledActionSymbols.AddRange(absoluteActionSymbols);
335 scheduledActionSymbols.AddRange(relativeActionSymbols);
336 }
337
338 return scheduledActionSymbols;
339 }
340
341 private IEnumerable<string> GetRequiredActionIds()
342 {
343 var set = new HashSet<string>();
344
345 // gather the required actions for the output type
346 if (SectionType.Product == this.Section.Type)
347 {
348 // AdminExecuteSequence table
349 set.Add("AdminExecuteSequence/CostFinalize");
350 set.Add("AdminExecuteSequence/CostInitialize");
351 set.Add("AdminExecuteSequence/FileCost");
352 set.Add("AdminExecuteSequence/InstallAdminPackage");
353 set.Add("AdminExecuteSequence/InstallFiles");
354 set.Add("AdminExecuteSequence/InstallFinalize");
355 set.Add("AdminExecuteSequence/InstallInitialize");
356 set.Add("AdminExecuteSequence/InstallValidate");
357
358 // AdminUISequence table
359 set.Add("AdminUISequence/CostFinalize");
360 set.Add("AdminUISequence/CostInitialize");
361 set.Add("AdminUISequence/ExecuteAction");
362 set.Add("AdminUISequence/FileCost");
363
364 // AdvtExecuteSequence table
365 set.Add("AdvertiseExecuteSequence/CostFinalize");
366 set.Add("AdvertiseExecuteSequence/CostInitialize");
367 set.Add("AdvertiseExecuteSequence/InstallInitialize");
368 set.Add("AdvertiseExecuteSequence/InstallFinalize");
369 set.Add("AdvertiseExecuteSequence/InstallValidate");
370 set.Add("AdvertiseExecuteSequence/PublishFeatures");
371 set.Add("AdvertiseExecuteSequence/PublishProduct");
372
373 // InstallExecuteSequence table
374 set.Add("InstallExecuteSequence/CostFinalize");
375 set.Add("InstallExecuteSequence/CostInitialize");
376 set.Add("InstallExecuteSequence/FileCost");
377 set.Add("InstallExecuteSequence/InstallFinalize");
378 set.Add("InstallExecuteSequence/InstallInitialize");
379 set.Add("InstallExecuteSequence/InstallValidate");
380 set.Add("InstallExecuteSequence/ProcessComponents");
381 set.Add("InstallExecuteSequence/PublishFeatures");
382 set.Add("InstallExecuteSequence/PublishProduct");
383 set.Add("InstallExecuteSequence/RegisterProduct");
384 set.Add("InstallExecuteSequence/RegisterUser");
385 set.Add("InstallExecuteSequence/UnpublishFeatures");
386 set.Add("InstallExecuteSequence/ValidateProductID");
387
388 // InstallUISequence table
389 set.Add("InstallUISequence/CostFinalize");
390 set.Add("InstallUISequence/CostInitialize");
391 set.Add("InstallUISequence/ExecuteAction");
392 set.Add("InstallUISequence/FileCost");
393 set.Add("InstallUISequence/ValidateProductID");
394 }
395
396 // Gather the required actions for each symbol type.
397 foreach (var symbolType in this.Section.Symbols.Select(t => t.Definition.Type).Distinct())
398 {
399 switch (symbolType)
400 {
401 case SymbolDefinitionType.AppSearch:
402 set.Add("InstallExecuteSequence/AppSearch");
403 set.Add("InstallUISequence/AppSearch");
404 break;
405 case SymbolDefinitionType.CCPSearch:
406 set.Add("InstallExecuteSequence/AppSearch");
407 set.Add("InstallExecuteSequence/CCPSearch");
408 set.Add("InstallExecuteSequence/RMCCPSearch");
409 set.Add("InstallUISequence/AppSearch");
410 set.Add("InstallUISequence/CCPSearch");
411 set.Add("InstallUISequence/RMCCPSearch");
412 break;
413 case SymbolDefinitionType.Class:
414 set.Add("AdvertiseExecuteSequence/RegisterClassInfo");
415 set.Add("InstallExecuteSequence/RegisterClassInfo");
416 set.Add("InstallExecuteSequence/UnregisterClassInfo");
417 break;
418 case SymbolDefinitionType.Complus:
419 set.Add("InstallExecuteSequence/RegisterComPlus");
420 set.Add("InstallExecuteSequence/UnregisterComPlus");
421 break;
422 case SymbolDefinitionType.Component:
423 case SymbolDefinitionType.CreateFolder:
424 set.Add("InstallExecuteSequence/CreateFolders");
425 set.Add("InstallExecuteSequence/RemoveFolders");
426 break;
427 case SymbolDefinitionType.DuplicateFile:
428 set.Add("InstallExecuteSequence/DuplicateFiles");
429 set.Add("InstallExecuteSequence/RemoveDuplicateFiles");
430 break;
431 case SymbolDefinitionType.Environment:
432 set.Add("InstallExecuteSequence/WriteEnvironmentStrings");
433 set.Add("InstallExecuteSequence/RemoveEnvironmentStrings");
434 break;
435 case SymbolDefinitionType.Extension:
436 set.Add("AdvertiseExecuteSequence/RegisterExtensionInfo");
437 set.Add("InstallExecuteSequence/RegisterExtensionInfo");
438 set.Add("InstallExecuteSequence/UnregisterExtensionInfo");
439 break;
440 case SymbolDefinitionType.File:
441 set.Add("InstallExecuteSequence/InstallFiles");
442 set.Add("InstallExecuteSequence/RemoveFiles");
443
444 var foundFont = false;
445 var foundSelfReg = false;
446 var foundBindPath = false;
447 foreach (var file in this.Section.Symbols.OfType<FileSymbol>())
448 {
449 if (!foundFont && !String.IsNullOrEmpty(file.FontTitle))
450 {
451 set.Add("InstallExecuteSequence/RegisterFonts");
452 set.Add("InstallExecuteSequence/UnregisterFonts");
453 foundFont = true;
454 }
455
456 if (!foundSelfReg && file.SelfRegCost.HasValue)
457 {
458 set.Add("InstallExecuteSequence/SelfRegModules");
459 set.Add("InstallExecuteSequence/SelfUnregModules");
460 foundSelfReg = true;
461 }
462
463 if (!foundBindPath && !String.IsNullOrEmpty(file.BindPath))
464 {
465 set.Add("InstallExecuteSequence/BindImage");
466 foundBindPath = true;
467 }
468 }
469 break;
470 case SymbolDefinitionType.IniFile:
471 set.Add("InstallExecuteSequence/WriteIniValues");
472 set.Add("InstallExecuteSequence/RemoveIniValues");
473 break;
474 case SymbolDefinitionType.IsolatedComponent:
475 set.Add("InstallExecuteSequence/IsolateComponents");
476 break;
477 case SymbolDefinitionType.LaunchCondition:
478 set.Add("InstallExecuteSequence/LaunchConditions");
479 set.Add("InstallUISequence/LaunchConditions");
480 break;
481 case SymbolDefinitionType.MIME:
482 set.Add("AdvertiseExecuteSequence/RegisterMIMEInfo");
483 set.Add("InstallExecuteSequence/RegisterMIMEInfo");
484 set.Add("InstallExecuteSequence/UnregisterMIMEInfo");
485 break;
486 case SymbolDefinitionType.MoveFile:
487 set.Add("InstallExecuteSequence/MoveFiles");
488 break;
489 case SymbolDefinitionType.Assembly:
490 set.Add("AdvertiseExecuteSequence/MsiPublishAssemblies");
491 set.Add("InstallExecuteSequence/MsiPublishAssemblies");
492 set.Add("InstallExecuteSequence/MsiUnpublishAssemblies");
493 break;
494 case SymbolDefinitionType.MsiServiceConfig:
495 case SymbolDefinitionType.MsiServiceConfigFailureActions:
496 set.Add("InstallExecuteSequence/MsiConfigureServices");
497 break;
498 case SymbolDefinitionType.ODBCDataSource:
499 case SymbolDefinitionType.ODBCTranslator:
500 case SymbolDefinitionType.ODBCDriver:
501 set.Add("InstallExecuteSequence/SetODBCFolders");
502 set.Add("InstallExecuteSequence/InstallODBC");
503 set.Add("InstallExecuteSequence/RemoveODBC");
504 break;
505 case SymbolDefinitionType.ProgId:
506 set.Add("AdvertiseExecuteSequence/RegisterProgIdInfo");
507 set.Add("InstallExecuteSequence/RegisterProgIdInfo");
508 set.Add("InstallExecuteSequence/UnregisterProgIdInfo");
509 break;
510 case SymbolDefinitionType.PublishComponent:
511 set.Add("AdvertiseExecuteSequence/PublishComponents");
512 set.Add("InstallExecuteSequence/PublishComponents");
513 set.Add("InstallExecuteSequence/UnpublishComponents");
514 break;
515 case SymbolDefinitionType.Registry:
516 case SymbolDefinitionType.RemoveRegistry:
517 set.Add("InstallExecuteSequence/WriteRegistryValues");
518 set.Add("InstallExecuteSequence/RemoveRegistryValues");
519 break;
520 case SymbolDefinitionType.RemoveFile:
521 set.Add("InstallExecuteSequence/RemoveFiles");
522 break;
523 case SymbolDefinitionType.ServiceControl:
524 set.Add("InstallExecuteSequence/StartServices");
525 set.Add("InstallExecuteSequence/StopServices");
526 set.Add("InstallExecuteSequence/DeleteServices");
527 break;
528 case SymbolDefinitionType.ServiceInstall:
529 set.Add("InstallExecuteSequence/InstallServices");
530 break;
531 case SymbolDefinitionType.Shortcut:
532 set.Add("AdvertiseExecuteSequence/CreateShortcuts");
533 set.Add("InstallExecuteSequence/CreateShortcuts");
534 set.Add("InstallExecuteSequence/RemoveShortcuts");
535 break;
536 case SymbolDefinitionType.TypeLib:
537 set.Add("InstallExecuteSequence/RegisterTypeLibraries");
538 set.Add("InstallExecuteSequence/UnregisterTypeLibraries");
539 break;
540 case SymbolDefinitionType.Upgrade:
541 set.Add("InstallExecuteSequence/FindRelatedProducts");
542 set.Add("InstallUISequence/FindRelatedProducts");
543
544 // Only add the MigrateFeatureStates action if MigrateFeature attribute is set on
545 // at least one UpgradeVersion element.
546 if (this.Section.Symbols.OfType<UpgradeSymbol>().Any(t => t.MigrateFeatures))
547 {
548 set.Add("InstallExecuteSequence/MigrateFeatureStates");
549 set.Add("InstallUISequence/MigrateFeatureStates");
550 }
551 break;
552 }
553 }
554
555 return set;
556 }
557
558 /// <summary>
559 /// Sequence an action before or after a standard action.
560 /// </summary>
561 /// <param name="actionSymbol">The action symbol to be sequenced.</param>
562 /// <param name="requiredActionSymbols">Collection of actions which must be included.</param>
563 /// <param name="firstReference">A dictionary used for detecting cyclic references among action symbols.</param>
564 private void SequenceActionSymbol(WixActionSymbol actionSymbol, Dictionary<string, WixActionSymbol> requiredActionSymbols, Dictionary<WixActionSymbol, WixActionSymbol> firstReference)
565 {
566 var after = false;
567
568 if (actionSymbol.After != null)
569 {
570 after = true;
571 }
572 else if (actionSymbol.Before == null)
573 {
574 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.");
575 }
576
577 var parentActionName = (after ? actionSymbol.After : actionSymbol.Before);
578 var parentActionKey = actionSymbol.SequenceTable.ToString() + "/" + parentActionName;
579
580 if (!requiredActionSymbols.TryGetValue(parentActionKey, out var parentActionSymbol))
581 {
582 // If the missing parent action is a standard action (with a suggested sequence number), add it.
583 if (WindowsInstallerStandard.TryGetStandardAction(parentActionKey, out parentActionSymbol))
584 {
585 // Create a clone to avoid modifying the static copy of the object.
586 // TODO: consider this: parentActionSymbol = parentActionSymbol.Clone();
587
588 requiredActionSymbols.Add(parentActionSymbol.Id.Id, parentActionSymbol);
589 }
590 else
591 {
592 throw new WixException($"Found action {actionSymbol.Id.Id} with a non-existent {(after ? "After" : "Before")} action '{parentActionName}'. The linker should have prevented this.");
593 }
594 }
595
596 this.CheckForCircularActionReference(actionSymbol, requiredActionSymbols, firstReference);
597
598 // Add this action to the appropriate list of dependent action symbols.
599 var relativeActions = this.GetRelativeActions(parentActionSymbol);
600 var relatedSymbols = (after ? relativeActions.NextActions : relativeActions.PreviousActions);
601 relatedSymbols.Add(actionSymbol);
602 }
603
604 /// <summary>
605 /// Check the specified action symbol to see if it leads to a cycle.
606 /// </summary>
607 /// <para> Use the provided dictionary to note the initial action symbol that first led to each action
608 /// symbol. Any action symbol encountered that has already been encountered starting from a different
609 /// initial action symbol inherits the loop characteristics of that initial action symbol, and thus is
610 /// also not part of a cycle. However, any action symbol encountered that has already been encountered
611 /// starting from the same initial action symbol is an indication that the current action symbol is
612 /// part of a cycle.
613 /// </para>
614 /// <param name="actionSymbol">The action symbol to be checked.</param>
615 /// <param name="requiredActionSymbols">Collection of actions which must be included.</param>
616 /// <param name="firstReference">The first encountered action symbol that led to each action symbol.</param>
617 private void CheckForCircularActionReference(WixActionSymbol actionSymbol, Dictionary<string, WixActionSymbol> requiredActionSymbols, Dictionary<WixActionSymbol, WixActionSymbol> firstReference)
618 {
619 WixActionSymbol currentActionSymbol = null;
620 var parentActionSymbol = actionSymbol;
621
622 do
623 {
624 var previousActionSymbol = currentActionSymbol ?? parentActionSymbol;
625 currentActionSymbol = parentActionSymbol;
626
627 if (!firstReference.TryGetValue(currentActionSymbol, out var existingInitialActionSymbol))
628 {
629 firstReference[currentActionSymbol] = actionSymbol;
630 }
631 else if (existingInitialActionSymbol == actionSymbol)
632 {
633 this.Messaging.Write(ErrorMessages.ActionCircularDependency(currentActionSymbol.SourceLineNumbers, currentActionSymbol.SequenceTable.ToString(), currentActionSymbol.Action, previousActionSymbol.Action));
634 }
635
636 parentActionSymbol = this.GetParentActionSymbol(currentActionSymbol, requiredActionSymbols);
637 } while (null != parentActionSymbol && !this.Messaging.EncounteredError);
638 }
639
640 /// <summary>
641 /// Get the action symbol that is the parent of the given action symbol.
642 /// </summary>
643 /// <param name="actionSymbol">The given action symbol.</param>
644 /// <param name="requiredActionSymbols">Collection of actions which must be included.</param>
645 /// <returns>Null if there is no parent. Used for loop termination.</returns>
646 private WixActionSymbol GetParentActionSymbol(WixActionSymbol actionSymbol, Dictionary<string, WixActionSymbol> requiredActionSymbols)
647 {
648 if (null == actionSymbol.Before && null == actionSymbol.After)
649 {
650 return null;
651 }
652
653 var parentActionKey = actionSymbol.SequenceTable.ToString() + "/" + (actionSymbol.After ?? actionSymbol.Before);
654
655 if (!requiredActionSymbols.TryGetValue(parentActionKey, out var parentActionSymbol))
656 {
657 WindowsInstallerStandard.TryGetStandardAction(parentActionKey, out parentActionSymbol);
658 }
659
660 return parentActionSymbol;
661 }
662
663
664 private RelativeActions GetRelativeActions(WixActionSymbol action)
665 {
666 if (!this.RelativeActionsForActions.TryGetValue(action.Id.Id, out var relativeActions))
667 {
668 relativeActions = new RelativeActions();
669 this.RelativeActionsForActions.Add(action.Id.Id, relativeActions);
670 }
671
672 return relativeActions;
673 }
674
675 private RelativeActions GetAllRelativeActionsForSequenceType(SequenceTable sequenceType, WixActionSymbol action)
676 {
677 var relativeActions = new RelativeActions();
678
679 if (this.RelativeActionsForActions.TryGetValue(action.Id.Id, out var actionRelatives))
680 {
681 this.RecurseRelativeActionsForSequenceType(sequenceType, actionRelatives.PreviousActions, relativeActions.PreviousActions);
682
683 this.RecurseRelativeActionsForSequenceType(sequenceType, actionRelatives.NextActions, relativeActions.NextActions);
684 }
685
686 return relativeActions;
687 }
688
689 private void RecurseRelativeActionsForSequenceType(SequenceTable sequenceType, List<WixActionSymbol> actions, List<WixActionSymbol> visitedActions)
690 {
691 foreach (var action in actions.Where(a => a.SequenceTable == sequenceType))
692 {
693 if (this.RelativeActionsForActions.TryGetValue(action.Id.Id, out var actionRelatives))
694 {
695 this.RecurseRelativeActionsForSequenceType(sequenceType, actionRelatives.PreviousActions, visitedActions);
696 }
697
698 visitedActions.Add(action);
699
700 if (actionRelatives != null)
701 {
702 this.RecurseRelativeActionsForSequenceType(sequenceType, actionRelatives.NextActions, visitedActions);
703 }
704 }
705 }
706
707 private class RelativeActions
708 {
709 public List<WixActionSymbol> PreviousActions { get; } = new List<WixActionSymbol>();
710
711 public List<WixActionSymbol> NextActions { get; } = new List<WixActionSymbol>();
712 }
713 }
714}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.ComponentModel;
8 using System.Globalization;
9 using System.IO;
10 using System.Linq;
11 using WixToolset.Core.Native.Msi;
12 using WixToolset.Data;
13 using WixToolset.Data.Symbols;
14 using WixToolset.Extensibility.Data;
15 using WixToolset.Extensibility.Services;
16
17 /// <summary>
18 /// Update file information.
19 /// </summary>
20 internal class UpdateFileFacadesCommand
21 {
22 public UpdateFileFacadesCommand(IMessaging messaging, IntermediateSection section, IEnumerable<IFileFacade> fileFacades, IEnumerable<IFileFacade> updateFileFacades, IDictionary<string, string> variableCache, bool overwriteHash)
23 {
24 this.Messaging = messaging;
25 this.Section = section;
26 this.FileFacades = fileFacades;
27 this.UpdateFileFacades = updateFileFacades;
28 this.VariableCache = variableCache;
29 this.OverwriteHash = overwriteHash;
30 }
31
32 private IMessaging Messaging { get; }
33
34 private IntermediateSection Section { get; }
35
36 private IEnumerable<IFileFacade> FileFacades { get; }
37
38 private IEnumerable<IFileFacade> UpdateFileFacades { get; }
39
40 private bool OverwriteHash { get; }
41
42 private IDictionary<string, string> VariableCache { get; }
43
44 public void Execute()
45 {
46 var assemblyNameSymbols = this.Section.Symbols.OfType<MsiAssemblyNameSymbol>().ToDictionary(t => t.Id.Id);
47
48 foreach (var file in this.UpdateFileFacades.Where(f => f.SourcePath != null))
49 {
50 this.UpdateFileFacade(file, assemblyNameSymbols);
51 }
52 }
53
54 private void UpdateFileFacade(IFileFacade facade, Dictionary<string, MsiAssemblyNameSymbol> assemblyNameSymbols)
55 {
56 FileInfo fileInfo = null;
57 try
58 {
59 fileInfo = new FileInfo(facade.SourcePath);
60 }
61 catch (ArgumentException)
62 {
63 this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath));
64 return;
65 }
66 catch (PathTooLongException)
67 {
68 this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath));
69 return;
70 }
71 catch (NotSupportedException)
72 {
73 this.Messaging.Write(ErrorMessages.InvalidFileName(facade.SourceLineNumber, facade.SourcePath));
74 return;
75 }
76
77 if (!fileInfo.Exists)
78 {
79 this.Messaging.Write(ErrorMessages.CannotFindFile(facade.SourceLineNumber, facade.Id, facade.FileName, facade.SourcePath));
80 return;
81 }
82
83 using (var fileStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read))
84 {
85 if (Int32.MaxValue < fileStream.Length)
86 {
87 throw new WixException(ErrorMessages.FileTooLarge(facade.SourceLineNumber, facade.SourcePath));
88 }
89
90 facade.FileSize = Convert.ToInt32(fileStream.Length, CultureInfo.InvariantCulture);
91 }
92
93 string version = null;
94 string language = null;
95 try
96 {
97 Installer.GetFileVersion(fileInfo.FullName, out version, out language);
98 }
99 catch (Win32Exception e)
100 {
101 if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND
102 {
103 throw new WixException(ErrorMessages.FileNotFound(facade.SourceLineNumber, fileInfo.FullName));
104 }
105 else
106 {
107 throw new WixException(ErrorMessages.Win32Exception(e.NativeErrorCode, e.Message));
108 }
109 }
110
111 // If there is no version, it is assumed there is no language because it won't matter in the versioning of the install.
112 if (String.IsNullOrEmpty(version)) // unversioned files have their hashes added to the MsiFileHash table
113 {
114 if (!this.OverwriteHash)
115 {
116 // not overwriting hash, so don't do the rest of these options.
117 }
118 else if (null != facade.Version)
119 {
120 // Search all of the file rows available to see if the specified version is actually a companion file. Yes, this looks
121 // 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.
122 // That's a reasonable thought but companion file usage is usually pretty rare so we'd be doing something expensive (indexing
123 // all the file rows) for a relatively uncommon situation. Let's not do that.
124 //
125 // Also, if we do not find a matching file identifier then the user provided a default version and is providing a version
126 // for unversioned file. That's allowed but generally a dangerous thing to do so let's point that out to the user.
127 if (!this.FileFacades.Any(r => facade.Version.Equals(r.Id, StringComparison.Ordinal)))
128 {
129 this.Messaging.Write(WarningMessages.DefaultVersionUsedForUnversionedFile(facade.SourceLineNumber, facade.Version, facade.Id));
130 }
131 }
132 else
133 {
134 if (null != facade.Language)
135 {
136 this.Messaging.Write(WarningMessages.DefaultLanguageUsedForUnversionedFile(facade.SourceLineNumber, facade.Language, facade.Id));
137 }
138
139 int[] hash;
140 try
141 {
142 Installer.GetFileHash(fileInfo.FullName, 0, out hash);
143 }
144 catch (Win32Exception e)
145 {
146 if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND
147 {
148 throw new WixException(ErrorMessages.FileNotFound(facade.SourceLineNumber, fileInfo.FullName));
149 }
150 else
151 {
152 throw new WixException(ErrorMessages.Win32Exception(e.NativeErrorCode, fileInfo.FullName, e.Message));
153 }
154 }
155
156 if (null == facade.Hash)
157 {
158 facade.Hash = this.Section.AddSymbol(new MsiFileHashSymbol(facade.SourceLineNumber, facade.Identifier));
159 }
160
161 facade.Hash.Options = 0;
162 facade.Hash.HashPart1 = hash[0];
163 facade.Hash.HashPart2 = hash[1];
164 facade.Hash.HashPart3 = hash[2];
165 facade.Hash.HashPart4 = hash[3];
166 }
167 }
168 else // update the file row with the version and language information.
169 {
170 // If no version was provided by the user, use the version from the file itself.
171 // This is the most common case.
172 if (String.IsNullOrEmpty(facade.Version))
173 {
174 facade.Version = version;
175 }
176 else if (!this.FileFacades.Any(r => facade.Version.Equals(r.Id, StringComparison.Ordinal))) // this looks expensive, but see explanation below.
177 {
178 // The user provided a default version for the file row so we looked for a companion file (a file row with Id matching
179 // the version value). We didn't find it so, we will override the default version they provided with the actual
180 // version from the file itself. Now, I know it looks expensive to search through all the file rows trying to match
181 // on the Id. However, the alternative is to build a big index of all file rows to do look ups. Since this case
182 // where the file version is already present is rare (companion files are pretty uncommon), we'll do the more
183 // CPU intensive search to save on the memory intensive index that wouldn't be used much.
184 //
185 // Also note this case can occur when the file is being updated using the WixBindUpdatedFiles extension mechanism.
186 // That's typically even more rare than companion files so again, no index, just search.
187 facade.Version = version;
188 }
189
190 if (!String.IsNullOrEmpty(facade.Language) && String.IsNullOrEmpty(language))
191 {
192 this.Messaging.Write(WarningMessages.DefaultLanguageUsedForVersionedFile(facade.SourceLineNumber, facade.Language, facade.Id));
193 }
194 else // override the default provided by the user (usually nothing) with the actual language from the file itself.
195 {
196 facade.Language = language;
197 }
198
199 // Populate the binder variables for this file information if requested.
200 if (null != this.VariableCache)
201 {
202 if (!String.IsNullOrEmpty(facade.Version))
203 {
204 var key = String.Format(CultureInfo.InvariantCulture, "fileversion.{0}", facade.Id);
205 this.VariableCache[key] = facade.Version;
206 }
207
208 if (!String.IsNullOrEmpty(facade.Language))
209 {
210 var key = String.Format(CultureInfo.InvariantCulture, "filelanguage.{0}", facade.Id);
211 this.VariableCache[key] = facade.Language;
212 }
213 }
214 }
215
216 // If this is a CLR assembly, load the assembly and get the assembly name information
217 if (AssemblyType.DotNetAssembly == facade.AssemblyType)
218 {
219 try
220 {
221 var assemblyName = AssemblyNameReader.ReadAssembly(facade.SourceLineNumber, fileInfo.FullName, version);
222
223 this.SetMsiAssemblyName(assemblyNameSymbols, facade, "name", assemblyName.Name);
224 this.SetMsiAssemblyName(assemblyNameSymbols, facade, "culture", assemblyName.Culture);
225 this.SetMsiAssemblyName(assemblyNameSymbols, facade, "version", assemblyName.Version);
226
227 if (!String.IsNullOrEmpty(assemblyName.Architecture))
228 {
229 this.SetMsiAssemblyName(assemblyNameSymbols, facade, "processorArchitecture", assemblyName.Architecture);
230 }
231 // TODO: WiX v3 seemed to do this but not clear it should actually be done.
232 //else if (!String.IsNullOrEmpty(file.WixFile.ProcessorArchitecture))
233 //{
234 // this.SetMsiAssemblyName(assemblyNameSymbols, file, "processorArchitecture", file.WixFile.ProcessorArchitecture);
235 //}
236
237 if (assemblyName.StrongNamedSigned)
238 {
239 this.SetMsiAssemblyName(assemblyNameSymbols, facade, "publicKeyToken", assemblyName.PublicKeyToken);
240 }
241 else if (facade.AssemblyApplicationFileRef == null)
242 {
243 throw new WixException(ErrorMessages.GacAssemblyNoStrongName(facade.SourceLineNumber, fileInfo.FullName, facade.ComponentRef));
244 }
245
246 if (!String.IsNullOrEmpty(assemblyName.FileVersion))
247 {
248 this.SetMsiAssemblyName(assemblyNameSymbols, facade, "fileVersion", assemblyName.FileVersion);
249 }
250
251 // add the assembly name to the information cache
252 if (null != this.VariableCache)
253 {
254 this.VariableCache[$"assemblyfullname.{facade.Id}"] = assemblyName.GetFullName();
255 }
256 }
257 catch (WixException e)
258 {
259 this.Messaging.Write(e.Error);
260 }
261 }
262 else if (AssemblyType.Win32Assembly == facade.AssemblyType)
263 {
264 // TODO: Consider passing in the this.FileFacades as an indexed collection instead of searching through
265 // all files like this. Even though this is a rare case it looks like we might be able to index the
266 // file earlier.
267 var fileManifest = this.FileFacades.FirstOrDefault(r => r.Id.Equals(facade.AssemblyManifestFileRef, StringComparison.Ordinal));
268 if (null == fileManifest)
269 {
270 this.Messaging.Write(ErrorMessages.MissingManifestForWin32Assembly(facade.SourceLineNumber, facade.Id, facade.AssemblyManifestFileRef));
271 }
272
273 try
274 {
275 var assemblyName = AssemblyNameReader.ReadAssemblyManifest(facade.SourceLineNumber, fileManifest.SourcePath);
276
277 if (!String.IsNullOrEmpty(assemblyName.Name))
278 {
279 this.SetMsiAssemblyName(assemblyNameSymbols, facade, "name", assemblyName.Name);
280 }
281
282 if (!String.IsNullOrEmpty(assemblyName.Version))
283 {
284 this.SetMsiAssemblyName(assemblyNameSymbols, facade, "version", assemblyName.Version);
285 }
286
287 if (!String.IsNullOrEmpty(assemblyName.Type))
288 {
289 this.SetMsiAssemblyName(assemblyNameSymbols, facade, "type", assemblyName.Type);
290 }
291
292 if (!String.IsNullOrEmpty(assemblyName.Architecture))
293 {
294 this.SetMsiAssemblyName(assemblyNameSymbols, facade, "processorArchitecture", assemblyName.Architecture);
295 }
296
297 if (!String.IsNullOrEmpty(assemblyName.PublicKeyToken))
298 {
299 this.SetMsiAssemblyName(assemblyNameSymbols, facade, "publicKeyToken", assemblyName.PublicKeyToken);
300 }
301 }
302 catch (WixException e)
303 {
304 this.Messaging.Write(e.Error);
305 }
306 }
307 }
308
309 /// <summary>
310 /// Set an MsiAssemblyName row. If it was directly authored, override the value, otherwise
311 /// create a new row.
312 /// </summary>
313 /// <param name="assemblyNameSymbols">MsiAssemblyName table.</param>
314 /// <param name="facade">FileFacade containing the assembly read for the MsiAssemblyName row.</param>
315 /// <param name="name">MsiAssemblyName name.</param>
316 /// <param name="value">MsiAssemblyName value.</param>
317 private void SetMsiAssemblyName(Dictionary<string, MsiAssemblyNameSymbol> assemblyNameSymbols, IFileFacade facade, string name, string value)
318 {
319 // check for null value (this can occur when grabbing the file version from an assembly without one)
320 if (String.IsNullOrEmpty(value))
321 {
322 this.Messaging.Write(WarningMessages.NullMsiAssemblyNameValue(facade.SourceLineNumber, facade.ComponentRef, name));
323 }
324 else
325 {
326 // 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.
327 if ("name" == name && AssemblyType.DotNetAssembly == facade.AssemblyType &&
328 String.IsNullOrEmpty(facade.AssemblyApplicationFileRef) &&
329 !String.Equals(Path.GetFileNameWithoutExtension(facade.FileName), value, StringComparison.OrdinalIgnoreCase))
330 {
331 this.Messaging.Write(ErrorMessages.GACAssemblyIdentityWarning(facade.SourceLineNumber, Path.GetFileNameWithoutExtension(facade.FileName), value));
332 }
333
334 // override directly authored value
335 var lookup = String.Concat(facade.ComponentRef, "/", name);
336 if (!assemblyNameSymbols.TryGetValue(lookup, out var assemblyNameSymbol))
337 {
338 assemblyNameSymbol = this.Section.AddSymbol(new MsiAssemblyNameSymbol(facade.SourceLineNumber, new Identifier(AccessModifier.Section, facade.ComponentRef, name))
339 {
340 ComponentRef = facade.ComponentRef,
341 Name = name,
342 Value = value,
343 });
344
345 if (null == facade.AssemblyNames)
346 {
347 facade.AssemblyNames = new List<MsiAssemblyNameSymbol>();
348 }
349
350 facade.AssemblyNames.Add(assemblyNameSymbol);
351
352 assemblyNameSymbols.Add(assemblyNameSymbol.Id.Id, assemblyNameSymbol);
353 }
354
355 assemblyNameSymbol.Value = value;
356
357 if (this.VariableCache != null)
358 {
359 var key = String.Format(CultureInfo.InvariantCulture, "assembly{0}.{1}", name, facade.Id).ToLowerInvariant();
360 this.VariableCache[key] = value;
361 }
362 }
363 }
364 }
365}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.IO;
7 using System.Linq;
8 using WixToolset.Data;
9 using WixToolset.Data.Symbols;
10 using WixToolset.Extensibility.Services;
11
12 internal class UpdateFromTextFilesCommand
13 {
14 public UpdateFromTextFilesCommand(IMessaging messaging, IntermediateSection section)
15 {
16 this.Messaging = messaging;
17 this.Section = section;
18 }
19
20 private IMessaging Messaging { get; }
21
22 private IntermediateSection Section { get; }
23
24 public void Execute()
25 {
26 foreach (var bbControl in this.Section.Symbols.OfType<BBControlSymbol>().Where(t => t.SourceFile != null))
27 {
28 bbControl.Text = this.ReadTextFile(bbControl.SourceLineNumbers, bbControl.SourceFile.Path);
29 }
30
31 foreach (var control in this.Section.Symbols.OfType<ControlSymbol>().Where(t => t.SourceFile != null))
32 {
33 control.Text = this.ReadTextFile(control.SourceLineNumbers, control.SourceFile.Path);
34 }
35
36 foreach (var customAction in this.Section.Symbols.OfType<CustomActionSymbol>().Where(c => c.ScriptFile != null))
37 {
38 customAction.Target = this.ReadTextFile(customAction.SourceLineNumbers, customAction.ScriptFile.Path);
39 }
40 }
41
42 /// <summary>
43 /// Reads a text file and returns the contents.
44 /// </summary>
45 /// <param name="sourceLineNumbers">Source line numbers for row from source.</param>
46 /// <param name="source">Source path to file to read.</param>
47 /// <returns>Text string read from file.</returns>
48 private string ReadTextFile(SourceLineNumber sourceLineNumbers, string source)
49 {
50 try
51 {
52 using (var reader = new StreamReader(source))
53 {
54 return reader.ReadToEnd();
55 }
56 }
57 catch (DirectoryNotFoundException e)
58 {
59 this.Messaging.Write(ErrorMessages.BinderFileManagerMissingFile(sourceLineNumbers, e.Message));
60 }
61 catch (FileNotFoundException e)
62 {
63 this.Messaging.Write(ErrorMessages.BinderFileManagerMissingFile(sourceLineNumbers, e.Message));
64 }
65 catch (IOException e)
66 {
67 this.Messaging.Write(ErrorMessages.BinderFileManagerMissingFile(sourceLineNumbers, e.Message));
68 }
69 catch (NotSupportedException)
70 {
71 this.Messaging.Write(ErrorMessages.FileNotFound(sourceLineNumbers, source));
72 }
73
74 return null;
75 }
76 }
77}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System.Collections.Generic;
6 using System.Linq;
7 using WixToolset.Data;
8 using WixToolset.Data.Symbols;
9 using WixToolset.Extensibility.Data;
10
11 internal class UpdateMediaSequencesCommand
12 {
13 public UpdateMediaSequencesCommand(IntermediateSection section, IEnumerable<IFileFacade> fileFacades)
14 {
15 this.Section = section;
16 this.FileFacades = fileFacades;
17 }
18
19 private IntermediateSection Section { get; }
20
21 private IEnumerable<IFileFacade> FileFacades { get; }
22
23 public void Execute()
24 {
25 var mediaRows = this.Section.Symbols.OfType<MediaSymbol>().ToDictionary(t => t.DiskId);
26
27 // Calculate sequence numbers and media disk id layout for all file media information objects.
28 if (SectionType.Module == this.Section.Type)
29 {
30 var lastSequence = 0;
31
32 foreach (var facade in this.FileFacades)
33 {
34 facade.Sequence = ++lastSequence;
35 }
36 }
37 else
38 {
39 var lastSequence = 0;
40 MediaSymbol mediaSymbol = null;
41 var patchGroups = new Dictionary<int, List<IFileFacade>>();
42
43 // Sequence the non-patch-added files.
44 foreach (var facade in this.FileFacades)
45 {
46 if (null == mediaSymbol)
47 {
48 mediaSymbol = mediaRows[facade.DiskId];
49 if (SectionType.Patch == this.Section.Type)
50 {
51 // patch Media cannot start at zero
52 lastSequence = mediaSymbol.LastSequence ?? 1;
53 }
54 }
55 else if (mediaSymbol.DiskId != facade.DiskId)
56 {
57 mediaSymbol.LastSequence = lastSequence;
58 mediaSymbol = mediaRows[facade.DiskId];
59 }
60
61 if (facade.PatchGroup.HasValue)
62 {
63 if (patchGroups.TryGetValue(facade.PatchGroup.Value, out var patchGroup))
64 {
65 patchGroup = new List<IFileFacade>();
66 patchGroups.Add(facade.PatchGroup.Value, patchGroup);
67 }
68
69 patchGroup.Add(facade);
70 }
71 else if (!facade.FromModule)
72 {
73 facade.Sequence = ++lastSequence;
74 }
75 }
76
77 if (null != mediaSymbol)
78 {
79 mediaSymbol.LastSequence = lastSequence;
80 mediaSymbol = null;
81 }
82
83 // Sequence the patch-added files.
84 foreach (var patchGroup in patchGroups.Values)
85 {
86 foreach (var facade in patchGroup)
87 {
88 if (null == mediaSymbol)
89 {
90 mediaSymbol = mediaRows[facade.DiskId];
91 }
92 else if (mediaSymbol.DiskId != facade.DiskId)
93 {
94 mediaSymbol.LastSequence = lastSequence;
95 mediaSymbol = mediaRows[facade.DiskId];
96 }
97
98 facade.Sequence = ++lastSequence;
99 }
100 }
101
102 if (null != mediaSymbol)
103 {
104 mediaSymbol.LastSequence = lastSequence;
105 }
106 }
107 }
108 }
109}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using WixToolset.Data;
9 using WixToolset.Data.Symbols;
10 using WixToolset.Data.WindowsInstaller;
11 using WixToolset.Data.WindowsInstaller.Rows;
12 using WixToolset.Extensibility.Data;
13 using WixToolset.Extensibility.Services;
14
15 internal class UpdateTransformsWithFileFacades
16 {
17 public UpdateTransformsWithFileFacades(IMessaging messaging, WindowsInstallerData output, IEnumerable<SubStorage> subStorages, TableDefinitionCollection tableDefinitions, IEnumerable<IFileFacade> fileFacades)
18 {
19 this.Messaging = messaging;
20 this.Output = output;
21 this.SubStorages = subStorages;
22 this.TableDefinitions = tableDefinitions;
23 this.FileFacades = fileFacades;
24 }
25
26 private IMessaging Messaging { get; }
27
28 private WindowsInstallerData Output { get; }
29
30 private IEnumerable<SubStorage> SubStorages { get; }
31
32 private TableDefinitionCollection TableDefinitions { get; }
33
34 private IEnumerable<IFileFacade> FileFacades { get; }
35
36 public void Execute()
37 {
38 var fileFacadesByDiskId = new Dictionary<int, Dictionary<string, IFileFacade>>();
39
40 // Index patch file facades by diskId+fileId.
41 foreach (var facade in this.FileFacades)
42 {
43 if (!fileFacadesByDiskId.TryGetValue(facade.DiskId, out var mediaFacades))
44 {
45 mediaFacades = new Dictionary<string, IFileFacade>();
46 fileFacadesByDiskId.Add(facade.DiskId, mediaFacades);
47 }
48
49 mediaFacades.Add(facade.Id, facade);
50 }
51
52 var patchMediaRows = new RowDictionary<MediaRow>(this.Output.Tables["Media"]);
53
54 // Index paired transforms by name without the "#" prefix.
55 var pairedTransforms = this.SubStorages.Where(s => s.Name.StartsWith("#")).ToDictionary(s => s.Name, s => s.Data);
56
57 // Copy File bind data into substorages
58 foreach (var substorage in this.SubStorages.Where(s => !s.Name.StartsWith("#")))
59 {
60 var mainTransform = substorage.Data;
61
62 var mainMsiFileHashIndex = new RowDictionary<Row>(mainTransform.Tables["MsiFileHash"]);
63
64 var pairedTransform = pairedTransforms["#" + substorage.Name];
65
66 // Copy Media.LastSequence.
67 var pairedMediaTable = pairedTransform.Tables["Media"];
68 foreach (MediaRow pairedMediaRow in pairedMediaTable.Rows)
69 {
70 var patchMediaRow = patchMediaRows.Get(pairedMediaRow.DiskId);
71 pairedMediaRow.LastSequence = patchMediaRow.LastSequence;
72 }
73
74 // Validate file row changes for keypath-related issues
75 this.ValidateFileRowChanges(mainTransform);
76
77 // Index File table of pairedTransform
78 var pairedFileRows = new RowDictionary<FileRow>(pairedTransform.Tables["File"]);
79
80 var mainFileTable = mainTransform.Tables["File"];
81 if (null != mainFileTable)
82 {
83 // Remove the MsiFileHash table because it will be updated later with the final file hash for each file
84 mainTransform.Tables.Remove("MsiFileHash");
85
86 foreach (FileRow mainFileRow in mainFileTable.Rows)
87 {
88 if (RowOperation.Delete == mainFileRow.Operation)
89 {
90 continue;
91 }
92 else if (RowOperation.None == mainFileRow.Operation)
93 {
94 continue;
95 }
96
97 // Index patch files by diskId+fileId
98 if (!fileFacadesByDiskId.TryGetValue(mainFileRow.DiskId, out var mediaFacades))
99 {
100 mediaFacades = new Dictionary<string, IFileFacade>();
101 fileFacadesByDiskId.Add(mainFileRow.DiskId, mediaFacades);
102 }
103
104 // copy data from the patch back to the transform
105 if (mediaFacades.TryGetValue(mainFileRow.File, out var facade))
106 {
107 var patchFileRow = facade.GetFileRow();
108 var pairedFileRow = pairedFileRows.Get(mainFileRow.File);
109
110 for (var i = 0; i < patchFileRow.Fields.Length; i++)
111 {
112 var patchValue = patchFileRow.FieldAsString(i) ?? String.Empty;
113 var mainValue = mainFileRow.FieldAsString(i) ?? String.Empty;
114
115 if (1 == i)
116 {
117 // File.Component_ changes should not come from the shared file rows
118 // that contain the file information as each individual transform might
119 // have different changes (or no changes at all).
120 }
121 else if (6 == i) // File.Attributes should not changed for binary deltas
122 {
123#if TODO_PATCHING_DELTA
124 if (null != patchFileRow.Patch)
125 {
126 // File.Attribute should not change for binary deltas
127 pairedFileRow.Attributes = mainFileRow.Attributes;
128 mainFileRow.Fields[i].Modified = false;
129 }
130#endif
131 }
132 else if (7 == i) // File.Sequence is updated in pairedTransform, not mainTransform
133 {
134 // file sequence is updated in Patch table instead of File table for delta patches
135#if TODO_PATCHING_DELTA
136 if (null != patchFileRow.Patch)
137 {
138 pairedFileRow.Fields[i].Modified = false;
139 }
140 else
141#endif
142 {
143 pairedFileRow[i] = patchFileRow[i];
144 pairedFileRow.Fields[i].Modified = true;
145 }
146 mainFileRow.Fields[i].Modified = false;
147 }
148 else if (patchValue != mainValue)
149 {
150 mainFileRow[i] = patchFileRow[i];
151 mainFileRow.Fields[i].Modified = true;
152 if (mainFileRow.Operation == RowOperation.None)
153 {
154 mainFileRow.Operation = RowOperation.Modify;
155 }
156 }
157 }
158
159 // Copy MsiFileHash row for this File.
160 if (!mainMsiFileHashIndex.TryGetValue(patchFileRow.File, out var patchHashRow))
161 {
162 //patchHashRow = patchFileRow.Hash;
163 throw new NotImplementedException();
164 }
165
166 if (null != patchHashRow)
167 {
168 var mainHashTable = mainTransform.EnsureTable(this.TableDefinitions["MsiFileHash"]);
169 var mainHashRow = mainHashTable.CreateRow(mainFileRow.SourceLineNumbers);
170 for (var i = 0; i < patchHashRow.Fields.Length; i++)
171 {
172 mainHashRow[i] = patchHashRow[i];
173 if (i > 1)
174 {
175 // assume all hash fields have been modified
176 mainHashRow.Fields[i].Modified = true;
177 }
178 }
179
180 // assume the MsiFileHash operation follows the File one
181 mainHashRow.Operation = mainFileRow.Operation;
182 }
183
184 // copy MsiAssemblyName rows for this File
185#if TODO_PATCHING
186 List<Row> patchAssemblyNameRows = patchFileRow.AssemblyNames;
187 if (null != patchAssemblyNameRows)
188 {
189 var mainAssemblyNameTable = mainTransform.EnsureTable(this.TableDefinitions["MsiAssemblyName"]);
190 foreach (var patchAssemblyNameRow in patchAssemblyNameRows)
191 {
192 // Copy if there isn't an identical modified/added row already in the transform.
193 var foundMatchingModifiedRow = false;
194 foreach (var mainAssemblyNameRow in mainAssemblyNameTable.Rows)
195 {
196 if (RowOperation.None != mainAssemblyNameRow.Operation && mainAssemblyNameRow.GetPrimaryKey('/').Equals(patchAssemblyNameRow.GetPrimaryKey('/')))
197 {
198 foundMatchingModifiedRow = true;
199 break;
200 }
201 }
202
203 if (!foundMatchingModifiedRow)
204 {
205 var mainAssemblyNameRow = mainAssemblyNameTable.CreateRow(mainFileRow.SourceLineNumbers);
206 for (var i = 0; i < patchAssemblyNameRow.Fields.Length; i++)
207 {
208 mainAssemblyNameRow[i] = patchAssemblyNameRow[i];
209 }
210
211 // assume value field has been modified
212 mainAssemblyNameRow.Fields[2].Modified = true;
213 mainAssemblyNameRow.Operation = mainFileRow.Operation;
214 }
215 }
216 }
217#endif
218
219 // Add patch header for this file
220#if TODO_PATCHING_DELTA
221 if (null != patchFileRow.Patch)
222 {
223 // Add the PatchFiles action automatically to the AdminExecuteSequence and InstallExecuteSequence tables.
224 this.AddPatchFilesActionToSequenceTable(SequenceTable.AdminExecuteSequence, mainTransform, pairedTransform, mainFileRow);
225 this.AddPatchFilesActionToSequenceTable(SequenceTable.InstallExecuteSequence, mainTransform, pairedTransform, mainFileRow);
226
227 // Add to Patch table
228 var patchTable = pairedTransform.EnsureTable(this.TableDefinitions["Patch"]);
229 if (0 == patchTable.Rows.Count)
230 {
231 patchTable.Operation = TableOperation.Add;
232 }
233
234 var patchRow = patchTable.CreateRow(mainFileRow.SourceLineNumbers);
235 patchRow[0] = patchFileRow.File;
236 patchRow[1] = patchFileRow.Sequence;
237
238 var patchFile = new FileInfo(patchFileRow.Source);
239 patchRow[2] = (int)patchFile.Length;
240 patchRow[3] = 0 == (PatchAttributeType.AllowIgnoreOnError & patchFileRow.PatchAttributes) ? 0 : 1;
241
242 var streamName = patchTable.Name + "." + patchRow[0] + "." + patchRow[1];
243 if (Msi.MsiInterop.MsiMaxStreamNameLength < streamName.Length)
244 {
245 streamName = "_" + Guid.NewGuid().ToString("D").ToUpperInvariant().Replace('-', '_');
246
247 var patchHeadersTable = pairedTransform.EnsureTable(this.TableDefinitions["MsiPatchHeaders"]);
248 if (0 == patchHeadersTable.Rows.Count)
249 {
250 patchHeadersTable.Operation = TableOperation.Add;
251 }
252
253 var patchHeadersRow = patchHeadersTable.CreateRow(mainFileRow.SourceLineNumbers);
254 patchHeadersRow[0] = streamName;
255 patchHeadersRow[1] = patchFileRow.Patch;
256 patchRow[5] = streamName;
257 patchHeadersRow.Operation = RowOperation.Add;
258 }
259 else
260 {
261 patchRow[4] = patchFileRow.Patch;
262 }
263 patchRow.Operation = RowOperation.Add;
264 }
265#endif
266 }
267 else
268 {
269 // TODO: throw because all transform rows should have made it into the patch
270 }
271 }
272 }
273
274 this.Output.Tables.Remove("Media");
275 this.Output.Tables.Remove("File");
276 this.Output.Tables.Remove("MsiFileHash");
277 this.Output.Tables.Remove("MsiAssemblyName");
278 }
279 }
280
281 /// <summary>
282 /// Adds the PatchFiles action to the sequence table if it does not already exist.
283 /// </summary>
284 /// <param name="table">The sequence table to check or modify.</param>
285 /// <param name="mainTransform">The primary authoring transform.</param>
286 /// <param name="pairedTransform">The secondary patch transform.</param>
287 /// <param name="mainFileRow">The file row that contains information about the patched file.</param>
288 private void AddPatchFilesActionToSequenceTable(SequenceTable table, WindowsInstallerData mainTransform, WindowsInstallerData pairedTransform, Row mainFileRow)
289 {
290 var tableName = table.ToString();
291
292 // Find/add PatchFiles action (also determine sequence for it).
293 // Search mainTransform first, then pairedTransform (pairedTransform overrides).
294 var hasPatchFilesAction = false;
295 var installFilesSequence = 0;
296 var duplicateFilesSequence = 0;
297
298 TestSequenceTableForPatchFilesAction(
299 mainTransform.Tables[tableName],
300 ref hasPatchFilesAction,
301 ref installFilesSequence,
302 ref duplicateFilesSequence);
303 TestSequenceTableForPatchFilesAction(
304 pairedTransform.Tables[tableName],
305 ref hasPatchFilesAction,
306 ref installFilesSequence,
307 ref duplicateFilesSequence);
308 if (!hasPatchFilesAction)
309 {
310 WindowsInstallerStandard.TryGetStandardAction(tableName, "PatchFiles", out var patchFilesActionSymbol);
311
312 var sequence = patchFilesActionSymbol.Sequence;
313
314 // Test for default sequence value's appropriateness
315 if (installFilesSequence >= sequence || (0 != duplicateFilesSequence && duplicateFilesSequence <= sequence))
316 {
317 if (0 != duplicateFilesSequence)
318 {
319 if (duplicateFilesSequence < installFilesSequence)
320 {
321 throw new WixException(ErrorMessages.InsertInvalidSequenceActionOrder(mainFileRow.SourceLineNumbers, tableName, "InstallFiles", "DuplicateFiles", patchFilesActionSymbol.Action));
322 }
323 else
324 {
325 sequence = (duplicateFilesSequence + installFilesSequence) / 2;
326 if (installFilesSequence == sequence || duplicateFilesSequence == sequence)
327 {
328 throw new WixException(ErrorMessages.InsertSequenceNoSpace(mainFileRow.SourceLineNumbers, tableName, "InstallFiles", "DuplicateFiles", patchFilesActionSymbol.Action));
329 }
330 }
331 }
332 else
333 {
334 sequence = installFilesSequence + 1;
335 }
336 }
337
338 var sequenceTable = pairedTransform.EnsureTable(this.TableDefinitions[tableName]);
339 if (0 == sequenceTable.Rows.Count)
340 {
341 sequenceTable.Operation = TableOperation.Add;
342 }
343
344 var patchAction = sequenceTable.CreateRow(null);
345 patchAction[0] = patchFilesActionSymbol.Action;
346 patchAction[1] = patchFilesActionSymbol.Condition;
347 patchAction[2] = sequence;
348 patchAction.Operation = RowOperation.Add;
349 }
350 }
351
352 /// <summary>
353 /// Tests sequence table for PatchFiles and associated actions
354 /// </summary>
355 /// <param name="sequenceTable">The table to test.</param>
356 /// <param name="hasPatchFilesAction">Set to true if PatchFiles action is found. Left unchanged otherwise.</param>
357 /// <param name="installFilesSequence">Set to sequence value of InstallFiles action if found. Left unchanged otherwise.</param>
358 /// <param name="duplicateFilesSequence">Set to sequence value of DuplicateFiles action if found. Left unchanged otherwise.</param>
359 private static void TestSequenceTableForPatchFilesAction(Table sequenceTable, ref bool hasPatchFilesAction, ref int installFilesSequence, ref int duplicateFilesSequence)
360 {
361 if (null != sequenceTable)
362 {
363 foreach (var row in sequenceTable.Rows)
364 {
365 var actionName = row.FieldAsString(0);
366 switch (actionName)
367 {
368 case "PatchFiles":
369 hasPatchFilesAction = true;
370 break;
371
372 case "InstallFiles":
373 installFilesSequence = row.FieldAsInteger(2);
374 break;
375
376 case "DuplicateFiles":
377 duplicateFilesSequence = row.FieldAsInteger(2);
378 break;
379 }
380 }
381 }
382 }
383
384 /// <summary>
385 /// Signal a warning if a non-keypath file was changed in a patch without also changing the keypath file of the component.
386 /// </summary>
387 /// <param name="transform">The output to validate.</param>
388 private void ValidateFileRowChanges(WindowsInstallerData transform)
389 {
390 var componentTable = transform.Tables["Component"];
391 var fileTable = transform.Tables["File"];
392
393 // There's no sense validating keypaths if the transform has no component or file table
394 if (componentTable == null || fileTable == null)
395 {
396 return;
397 }
398
399 var componentKeyPath = new Dictionary<string, string>(componentTable.Rows.Count);
400
401 // Index the Component table for non-directory & non-registry key paths.
402 foreach (var row in componentTable.Rows)
403 {
404 var keyPath = row.FieldAsString(5);
405 if (keyPath != null && 0 != (row.FieldAsInteger(3) & WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath))
406 {
407 componentKeyPath.Add(row.FieldAsString(0), keyPath);
408 }
409 }
410
411 var componentWithChangedKeyPath = new Dictionary<string, string>();
412 var componentWithNonKeyPathChanged = new Dictionary<string, string>();
413 // Verify changes in the file table, now that file diffing has occurred
414 foreach (FileRow row in fileTable.Rows)
415 {
416 if (RowOperation.Modify != row.Operation)
417 {
418 continue;
419 }
420
421 var fileId = row.FieldAsString(0);
422 var componentId = row.FieldAsString(1);
423
424 // If this file is the keypath of a component
425 if (componentKeyPath.ContainsValue(fileId))
426 {
427 if (!componentWithChangedKeyPath.ContainsKey(componentId))
428 {
429 componentWithChangedKeyPath.Add(componentId, fileId);
430 }
431 }
432 else
433 {
434 if (!componentWithNonKeyPathChanged.ContainsKey(componentId))
435 {
436 componentWithNonKeyPathChanged.Add(componentId, fileId);
437 }
438 }
439 }
440
441 foreach (var componentFile in componentWithNonKeyPathChanged)
442 {
443 // Make sure all changes to non keypath files also had a change in the keypath.
444 if (!componentWithChangedKeyPath.ContainsKey(componentFile.Key) && componentKeyPath.TryGetValue(componentFile.Key, out var keyPath))
445 {
446 this.Messaging.Write(WarningMessages.UpdateOfNonKeyPathFile(componentFile.Value, componentFile.Key, keyPath));
447 }
448 }
449 }
450 }
451}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.IO;
9 using System.Linq;
10 using WixToolset.Core.Native;
11 using WixToolset.Data;
12 using WixToolset.Data.WindowsInstaller;
13 using WixToolset.Extensibility.Data;
14 using WixToolset.Extensibility.Services;
15
16 internal class ValidateDatabaseCommand : IWindowsInstallerValidatorCallback
17 {
18 // Set of ICEs that have equivalent-or-better checks in WiX.
19 private static readonly string[] WellKnownSuppressedIces = new[] { "ICE08", "ICE33", "ICE47", "ICE66" };
20
21 public ValidateDatabaseCommand(IMessaging messaging, IBackendHelper backendHelper, string intermediateFolder, WindowsInstallerData data, string outputPath, IEnumerable<string> cubeFiles, IEnumerable<string> ices, IEnumerable<string> suppressedIces)
22 {
23 this.Messaging = messaging;
24 this.BackendHelper = backendHelper;
25 this.Data = data;
26 this.OutputPath = outputPath;
27 this.CubeFiles = cubeFiles;
28 this.Ices = ices;
29 this.SuppressedIces = suppressedIces == null ? WellKnownSuppressedIces : suppressedIces.Union(WellKnownSuppressedIces);
30
31 this.IntermediateFolder = intermediateFolder;
32 this.OutputSourceLineNumber = new SourceLineNumber(outputPath);
33 }
34
35 public IEnumerable<ITrackedFile> TrackedFiles { get; private set; }
36
37 /// <summary>
38 /// Encountered error implementation for <see cref="IWindowsInstallerValidatorCallback"/>.
39 /// </summary>
40 public bool EncounteredError => this.Messaging.EncounteredError;
41
42 private IMessaging Messaging { get; }
43
44 private IBackendHelper BackendHelper { get; }
45
46 private WindowsInstallerData Data { get; }
47
48 private string OutputPath { get; }
49
50 private IEnumerable<string> CubeFiles { get; }
51
52 private IEnumerable<string> Ices { get; }
53
54 private IEnumerable<string> SuppressedIces { get; }
55
56 private string IntermediateFolder { get; }
57
58 /// <summary>
59 /// Fallback when an exact source line number cannot be calculated for a validation error.
60 /// </summary>
61 private SourceLineNumber OutputSourceLineNumber { get; set; }
62
63 private Dictionary<string, SourceLineNumber> SourceLineNumbersByTablePrimaryKey { get; set; }
64
65 public void Execute()
66 {
67 var trackedFiles = new List<ITrackedFile>();
68 var stopwatch = Stopwatch.StartNew();
69
70 this.Messaging.Write(VerboseMessages.ValidatingDatabase());
71
72 // Ensure the temporary files can be created the working folder.
73 var workingFolder = Path.Combine(this.IntermediateFolder, "_validate");
74 Directory.CreateDirectory(workingFolder);
75
76 // Copy the database to a temporary location so it can be manipulated.
77 // Ensure it is not read-only.
78 var workingDatabasePath = Path.Combine(workingFolder, Path.GetFileName(this.OutputPath));
79 FileSystem.CopyFile(this.OutputPath, workingDatabasePath, allowHardlink: false);
80
81 var trackWorkingDatabase = this.BackendHelper.TrackFile(workingDatabasePath, TrackedFileType.Temporary);
82 trackedFiles.Add(trackWorkingDatabase);
83
84 var attributes = File.GetAttributes(workingDatabasePath);
85 File.SetAttributes(workingDatabasePath, attributes & ~FileAttributes.ReadOnly);
86
87 var validator = new WindowsInstallerValidator(this, workingDatabasePath, this.CubeFiles, this.Ices, this.SuppressedIces);
88 validator.Execute();
89
90 stopwatch.Stop();
91 this.Messaging.Write(VerboseMessages.ValidatedDatabase(stopwatch.ElapsedMilliseconds));
92
93
94 this.TrackedFiles = trackedFiles;
95 }
96
97 private void LogValidationMessage(ValidationMessage message)
98 {
99 var messageSourceLineNumbers = this.OutputSourceLineNumber;
100 if (!String.IsNullOrEmpty(message.Table) && !String.IsNullOrEmpty(message.Column) && message.PrimaryKeys != null)
101 {
102 messageSourceLineNumbers = this.GetSourceLineNumbers(message.Table, message.PrimaryKeys);
103 }
104
105 switch (message.Type)
106 {
107 case ValidationMessageType.InternalFailure:
108 case ValidationMessageType.Error:
109 this.Messaging.Write(ErrorMessages.ValidationError(messageSourceLineNumbers, message.IceName, message.Description));
110 break;
111 case ValidationMessageType.Warning:
112 this.Messaging.Write(WarningMessages.ValidationWarning(messageSourceLineNumbers, message.IceName, message.Description));
113 break;
114 case ValidationMessageType.Info:
115 this.Messaging.Write(VerboseMessages.ValidationInfo(message.IceName, message.Description));
116 break;
117 default:
118 throw new WixException(ErrorMessages.InvalidValidatorMessageType(message.Type.ToString()));
119 }
120 }
121
122 /// <summary>
123 /// Validation blocked by other installation operation for <see cref="IWindowsInstallerValidatorCallback"/>.
124 /// </summary>
125 public void ValidationBlocked()
126 {
127 this.Messaging.Write(VerboseMessages.ValidationSerialized());
128 }
129
130 /// <summary>
131 /// Validation message implementation for <see cref="IWindowsInstallerValidatorCallback"/>.
132 /// </summary>
133 public bool ValidationMessage(ValidationMessage message)
134 {
135 this.LogValidationMessage(message);
136 return true;
137 }
138
139 /// <summary>
140 /// Gets the source line information (if available) for a row by its table name and primary key.
141 /// </summary>
142 /// <param name="tableName">The table name of the row.</param>
143 /// <param name="primaryKeys">The primary keys of the row.</param>
144 /// <returns>The source line number information if found; null otherwise.</returns>
145 private SourceLineNumber GetSourceLineNumbers(string tableName, IEnumerable<string> primaryKeys)
146 {
147 // Source line information only exists if an output file was supplied
148 if (this.Data == null)
149 {
150 // Use the file name as the source line information.
151 return this.OutputSourceLineNumber;
152 }
153
154 // Index the source line information if it hasn't been indexed already.
155 if (this.SourceLineNumbersByTablePrimaryKey == null)
156 {
157 this.SourceLineNumbersByTablePrimaryKey = new Dictionary<string, SourceLineNumber>();
158
159 // Index each real table
160 foreach (var table in this.Data.Tables.Where(t => !t.Definition.Unreal))
161 {
162 // Index each row that contain source line information
163 foreach (var row in table.Rows.Where(r => r.SourceLineNumbers != null))
164 {
165 // Index the row using its table name and primary key
166 var primaryKey = row.GetPrimaryKey(';');
167
168 if (!String.IsNullOrEmpty(primaryKey))
169 {
170 try
171 {
172 var key = String.Concat(table.Name, ":", primaryKey);
173 this.SourceLineNumbersByTablePrimaryKey.Add(key, row.SourceLineNumbers);
174 }
175 catch (ArgumentException)
176 {
177 this.Messaging.Write(WarningMessages.DuplicatePrimaryKey(row.SourceLineNumbers, primaryKey, table.Name));
178 }
179 }
180 }
181 }
182 }
183
184 return this.SourceLineNumbersByTablePrimaryKey.TryGetValue(String.Concat(tableName, ":", String.Join(";", primaryKeys)), out var sourceLineNumbers) ? sourceLineNumbers : null;
185 }
186 }
187}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Decompile
4{
5 using System;
6 using System.Collections.Generic;
7 using System.ComponentModel;
8 using System.IO;
9 using System.Linq;
10 using WixToolset.Core.Native.Msi;
11 using WixToolset.Core.WindowsInstaller.Unbind;
12 using WixToolset.Data;
13 using WixToolset.Data.WindowsInstaller;
14 using WixToolset.Extensibility;
15 using WixToolset.Extensibility.Data;
16 using WixToolset.Extensibility.Services;
17
18 internal class DecompileMsiOrMsmCommand
19 {
20 public DecompileMsiOrMsmCommand(IDecompileContext context, IEnumerable<IWindowsInstallerBackendDecompilerExtension> backendExtensions)
21 {
22 this.Context = context;
23 this.Extensions = backendExtensions;
24 this.Messaging = context.ServiceProvider.GetService<IMessaging>();
25 }
26
27 private IDecompileContext Context { get; }
28
29 private IEnumerable<IWindowsInstallerBackendDecompilerExtension> Extensions { get; }
30
31 private IMessaging Messaging { get; }
32
33 public IDecompileResult Execute()
34 {
35 var result = this.Context.ServiceProvider.GetService<IDecompileResult>();
36
37 try
38 {
39 using (var database = new Database(this.Context.DecompilePath, OpenDatabase.ReadOnly))
40 {
41 // Delete the directory and its files to prevent cab extraction failure due to an existing file.
42 if (Directory.Exists(this.Context.ExtractFolder))
43 {
44 Directory.Delete(this.Context.ExtractFolder, true);
45 }
46
47 var backendHelper = this.Context.ServiceProvider.GetService<IBackendHelper>();
48
49 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);
50 var output = unbindCommand.Execute();
51 var extractedFilePaths = new List<string>(unbindCommand.ExportedFiles);
52
53 var decompiler = new Decompiler(this.Messaging, backendHelper, this.Extensions, this.Context.BaseSourcePath, this.Context.SuppressCustomTables, this.Context.SuppressDroppingEmptyTables, this.Context.SuppressUI, this.Context.TreatProductAsModule);
54 result.Document = decompiler.Decompile(output);
55
56 result.Platform = GetPlatformFromOutput(output);
57
58 // extract the files from the cabinets
59 if (!String.IsNullOrEmpty(this.Context.ExtractFolder) && !this.Context.SuppressExtractCabinets)
60 {
61 var fileDirectory = String.IsNullOrEmpty(this.Context.CabinetExtractFolder) ? Path.Combine(this.Context.ExtractFolder, "File") : this.Context.CabinetExtractFolder;
62
63 var extractCommand = new ExtractCabinetsCommand(output, database, this.Context.DecompilePath, fileDirectory, this.Context.IntermediateFolder, this.Context.TreatProductAsModule);
64 extractCommand.Execute();
65
66 extractedFilePaths.AddRange(extractCommand.ExtractedFiles);
67 result.ExtractedFilePaths = extractedFilePaths;
68 }
69 else
70 {
71 result.ExtractedFilePaths = new string[0];
72 }
73 }
74 }
75 catch (Win32Exception e)
76 {
77 if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED
78 {
79 throw new WixException(ErrorMessages.OpenDatabaseFailed(this.Context.DecompilePath));
80 }
81
82 throw;
83 }
84
85 return result;
86 }
87
88 private static Platform? GetPlatformFromOutput(WindowsInstallerData output)
89 {
90 var template = output.Tables["_SummaryInformation"]?.Rows.SingleOrDefault(row => row.FieldAsInteger(0) == 7)?.FieldAsString(1);
91
92 return Decompiler.GetPlatformFromTemplateSummaryInformation(template?.Split(';'));
93
94 }
95 }
96}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Decompile
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.IO;
9 using System.Linq;
10 using System.Text;
11 using System.Text.RegularExpressions;
12 using System.Xml.Linq;
13 using WixToolset.Data;
14 using WixToolset.Data.Symbols;
15 using WixToolset.Data.WindowsInstaller;
16 using WixToolset.Data.WindowsInstaller.Rows;
17 using WixToolset.Extensibility;
18 using WixToolset.Extensibility.Services;
19
20 /// <summary>
21 /// Decompiles an msi database into WiX source.
22 /// </summary>
23 internal class Decompiler
24 {
25 private static readonly Regex NullSplitter = new Regex(@"\[~]");
26
27 // NameToBit arrays
28 private static readonly string[] TextControlAttributes = { "Transparent", "NoPrefix", "NoWrap", "FormatSize", "UserLanguage" };
29 private static readonly string[] HyperlinkControlAttributes = { "Transparent" };
30 private static readonly string[] EditControlAttributes = { "Multiline", null, null, null, null, "Password" };
31 private static readonly string[] ProgressControlAttributes = { "ProgressBlocks" };
32 private static readonly string[] VolumeControlAttributes = { "Removable", "Fixed", "Remote", "CDROM", "RAMDisk", "Floppy", "ShowRollbackCost" };
33 private static readonly string[] ListboxControlAttributes = { "Sorted", null, null, null, "UserLanguage" };
34 private static readonly string[] ListviewControlAttributes = { "Sorted", null, null, null, "FixedSize", "Icon16", "Icon32" };
35 private static readonly string[] ComboboxControlAttributes = { "Sorted", "ComboList", null, null, "UserLanguage" };
36 private static readonly string[] RadioControlAttributes = { "Image", "PushLike", "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32", null, "HasBorder" };
37 private static readonly string[] ButtonControlAttributes = { "Image", null, "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32", "ElevationShield" };
38 private static readonly string[] IconControlAttributes = { "Image", null, null, null, "FixedSize", "Icon16", "Icon32" };
39 private static readonly string[] BitmapControlAttributes = { "Image", null, null, null, "FixedSize" };
40 private static readonly string[] CheckboxControlAttributes = { null, "PushLike", "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32" };
41 private XElement uiElement;
42
43 /// <summary>
44 /// Creates a new decompiler object with a default set of table definitions.
45 /// </summary>
46 public Decompiler(IMessaging messaging, IBackendHelper backendHelper, IEnumerable<IWindowsInstallerBackendDecompilerExtension> extensions, string baseSourcePath, bool suppressCustomTables, bool suppressDroppingEmptyTables, bool suppressUI, bool treatProductAsModule)
47 {
48 this.Messaging = messaging;
49 this.BackendHelper = backendHelper;
50 this.Extensions = extensions;
51 this.BaseSourcePath = baseSourcePath ?? "SourceDir";
52 this.SuppressCustomTables = suppressCustomTables;
53 this.SuppressDroppingEmptyTables = suppressDroppingEmptyTables;
54 this.SuppressUI = suppressUI;
55 this.TreatProductAsModule = treatProductAsModule;
56
57 this.ExtensionsByTableName = new Dictionary<string, IWindowsInstallerBackendDecompilerExtension>();
58 this.StandardActions = WindowsInstallerStandard.StandardActions().ToDictionary(a => a.Id.Id);
59
60 this.TableDefinitions = new TableDefinitionCollection();
61 }
62
63 private IMessaging Messaging { get; }
64
65 private IBackendHelper BackendHelper { get; }
66
67 private IEnumerable<IWindowsInstallerBackendDecompilerExtension> Extensions { get; }
68
69 private Dictionary<string, IWindowsInstallerBackendDecompilerExtension> ExtensionsByTableName { get; }
70
71 private string BaseSourcePath { get; }
72
73 private bool SuppressCustomTables { get; }
74
75 private bool SuppressDroppingEmptyTables { get; }
76
77 private bool SuppressRelativeActionSequencing { get; }
78
79 private bool SuppressUI { get; }
80
81 private bool TreatProductAsModule { get; }
82
83 private OutputType OutputType { get; set; }
84
85 private Dictionary<string, WixActionSymbol> StandardActions { get; }
86
87 private bool Compressed { get; set; }
88
89 private XElement RootElement { get; set; }
90
91 private TableDefinitionCollection TableDefinitions { get; }
92
93 private bool ShortNames { get; set; }
94
95 private string ModularizationGuid { get; set; }
96
97 private XElement UIElement
98 {
99 get
100 {
101 if (null == this.uiElement)
102 {
103 this.uiElement = new XElement(Names.UIElement);
104 this.RootElement.Add(this.uiElement);
105 }
106
107 return this.uiElement;
108 }
109 }
110
111 private Dictionary<string, XElement> Singletons { get; } = new Dictionary<string, XElement>();
112
113 private Dictionary<string, XElement> IndexedElements { get; } = new Dictionary<string, XElement>();
114
115 private Dictionary<string, XElement> PatchTargetFiles { get; } = new Dictionary<string, XElement>();
116
117 /// <summary>
118 /// Decompile the database file.
119 /// </summary>
120 /// <param name="output">The output to decompile.</param>
121 /// <returns>The serialized WiX source code.</returns>
122 public XDocument Decompile(WindowsInstallerData output)
123 {
124 if (null == output)
125 {
126 throw new ArgumentNullException(nameof(output));
127 }
128
129 this.OutputType = output.Type;
130
131 // collect the table definitions from the output
132 this.TableDefinitions.Clear();
133 foreach (var table in output.Tables)
134 {
135 this.TableDefinitions.Add(table.Definition);
136 }
137
138 // add any missing standard and wix-specific table definitions
139 foreach (var tableDefinition in WindowsInstallerTableDefinitions.All)
140 {
141 if (!this.TableDefinitions.Contains(tableDefinition.Name))
142 {
143 this.TableDefinitions.Add(tableDefinition);
144 }
145 }
146
147 // add any missing extension table definitions
148#if TODO_DECOMPILER_EXTENSIONS
149 foreach (var extension in this.Extensions)
150 {
151 this.AddExtension(extension);
152 }
153#endif
154
155 switch (this.OutputType)
156 {
157 case OutputType.Module:
158 this.RootElement = new XElement(Names.ModuleElement);
159 break;
160 case OutputType.PatchCreation:
161 this.RootElement = new XElement(Names.PatchCreationElement);
162 break;
163 case OutputType.Product:
164 this.RootElement = new XElement(Names.PackageElement);
165 break;
166 default:
167 throw new InvalidOperationException("Unknown output type.");
168 }
169
170 var xWix = new XElement(Names.WixElement, this.RootElement);
171
172 // try to decompile the database file
173 // stop processing if an error previously occurred
174 if (this.Messaging.EncounteredError)
175 {
176 return null;
177 }
178
179 this.InitializeDecompile(output.Tables, output.Codepage);
180
181 // stop processing if an error previously occurred
182 if (this.Messaging.EncounteredError)
183 {
184 return null;
185 }
186
187 // decompile the tables
188 this.DecompileTables(output);
189
190 // finalize the decompiler and its extensions
191 this.FinalizeDecompile(output.Tables);
192
193 // return the XML document only if decompilation completed successfully
194 var document = new XDocument(xWix);
195 return this.Messaging.EncounteredError ? null : document;
196 }
197
198#if TODO_DECOMPILER_EXTENSIONS
199 private void AddExtension(IWindowsInstallerBackendDecompilerExtension extension)
200 {
201 if (null != extension.TableDefinitions)
202 {
203 foreach (TableDefinition tableDefinition in extension.TableDefinitions)
204 {
205 if (!this.ExtensionsByTableName.ContainsKey(tableDefinition.Name))
206 {
207 this.ExtensionsByTableName.Add(tableDefinition.Name, extension);
208 }
209 else
210 {
211 this.Messaging.Write(ErrorMessages.DuplicateExtensionTable(extension.GetType().ToString(), tableDefinition.Name));
212 }
213 }
214 }
215 }
216#endif
217
218 internal static Platform? GetPlatformFromTemplateSummaryInformation(string[] template)
219 {
220 if (null != template && 1 < template.Length && null != template[0] && 0 < template[0].Length)
221 {
222 switch (template[0])
223 {
224 case "Intel":
225 return Platform.X86;
226 case "x64":
227 return Platform.X64;
228 case "Arm64":
229 return Platform.ARM64;
230 }
231 }
232
233 return null;
234 }
235
236 /// <summary>
237 /// Gets the element corresponding to the row it came from.
238 /// </summary>
239 /// <param name="row">The row corresponding to the element.</param>
240 /// <returns>The indexed element.</returns>
241 private XElement GetIndexedElement(WixToolset.Data.WindowsInstaller.Row row) => this.GetIndexedElement(row.TableDefinition.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter));
242
243 /// <summary>
244 /// Gets the element corresponding to the primary key of the given table.
245 /// </summary>
246 /// <param name="table">The table corresponding to the element.</param>
247 /// <param name="primaryKey">The primary key corresponding to the element.</param>
248 /// <returns>The indexed element.</returns>
249 private XElement GetIndexedElement(string table, params string[] primaryKey) => this.IndexedElements[String.Concat(table, ':', String.Join(DecompilerConstants.PrimaryKeyDelimiterString, primaryKey))];
250
251 /// <summary>
252 /// Tries to get the element corresponding to the primary key of the given table.
253 /// </summary>
254 /// <param name="row">The table corresponding to the element.</param>
255 /// <param name="xElement">The indexed element.</param>
256 /// <returns>Whether the element was found.</returns>
257 private bool TryGetIndexedElement(WixToolset.Data.WindowsInstaller.Row row, out XElement xElement) => this.TryGetIndexedElement(row.TableDefinition.Name, out xElement, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter));
258
259 /// <summary>
260 /// Tries to get the element corresponding to the primary key of the given table.
261 /// </summary>
262 /// <param name="table">The table corresponding to the element.</param>
263 /// <param name="xElement">The indexed element.</param>
264 /// <param name="primaryKey">The primary key corresponding to the element.</param>
265 /// <returns>Whether the element was found.</returns>
266 private bool TryGetIndexedElement(string table, out XElement xElement, params string[] primaryKey) => this.IndexedElements.TryGetValue(String.Concat(table, ':', String.Join(DecompilerConstants.PrimaryKeyDelimiterString, primaryKey)), out xElement);
267
268 /// <summary>
269 /// Index an element by its corresponding row.
270 /// </summary>
271 /// <param name="row">The row corresponding to the element.</param>
272 /// <param name="element">The element to index.</param>
273 private void IndexElement(WixToolset.Data.WindowsInstaller.Row row, XElement element)
274 {
275 this.IndexedElements.Add(String.Concat(row.TableDefinition.Name, ':', row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)), element);
276 }
277
278 /// <summary>
279 /// Index an element by its corresponding row.
280 /// </summary>
281 /// <param name="element">The element to index.</param>
282 /// <param name="table"></param>
283 /// <param name="primaryKey"></param>
284 private void IndexElement(XElement element, string table, params string[] primaryKey)
285 {
286 this.IndexedElements.Add(String.Concat(table, ':', String.Join(DecompilerConstants.PrimaryKeyDelimiterString, primaryKey)), element);
287 }
288
289 private Dictionary<string, List<XElement>> IndexTableOneToMany(IEnumerable<Row> rows, int column = 0)
290 {
291 return rows
292 .ToLookup(row => row.FieldAsString(column), row => this.GetIndexedElement(row))
293 .ToDictionary(lookup => lookup.Key, lookup => lookup.ToList());
294 }
295
296 private Dictionary<string, List<XElement>> IndexTableOneToMany(TableIndexedCollection tables, string tableName, int column = 0) => this.IndexTableOneToMany(tables[tableName]?.Rows ?? Enumerable.Empty<Row>(), column);
297
298 private Dictionary<string, List<XElement>> IndexTableOneToMany(Table table, int column = 0) => this.IndexTableOneToMany(table?.Rows ?? Enumerable.Empty<Row>(), column);
299
300 private void AddChildToParent(string parentName, XElement xChild, Row row, int column)
301 {
302 var key = row.FieldAsString(column);
303 if (this.TryGetIndexedElement(parentName, out var xParent, key))
304 {
305 xParent.Add(xChild);
306 }
307 else
308 {
309 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, row.Table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), row.Fields[column].Column.Name, key, parentName));
310 }
311 }
312
313 private static XAttribute XAttributeIfNotNull(string attributeName, Row row, int column) => row.IsColumnNull(column) ? null : new XAttribute(attributeName, row.FieldAsString(column));
314
315 private static void SetAttributeIfNotNull(XElement xElement, string attributeName, string value)
316 {
317 if (!String.IsNullOrEmpty(value))
318 {
319 xElement.SetAttributeValue(attributeName, value);
320 }
321 }
322
323 private static void SetAttributeIfNotNull(XElement xElement, string attributeName, int? value)
324 {
325 if (value.HasValue)
326 {
327 xElement.SetAttributeValue(attributeName, value);
328 }
329 }
330
331 /// <summary>
332 /// Convert an Int32 into a DateTime.
333 /// </summary>
334 /// <param name="value">The Int32 value.</param>
335 /// <returns>The DateTime.</returns>
336 private static DateTime ConvertIntegerToDateTime(int value)
337 {
338 var date = value / 65536;
339 var time = value % 65536;
340
341 return new DateTime(1980 + (date / 512), (date % 512) / 32, date % 32, time / 2048, (time % 2048) / 32, (time % 32) * 2);
342 }
343
344 /// <summary>
345 /// Set the common control attributes in a control element.
346 /// </summary>
347 /// <param name="attributes">The control attributes.</param>
348 /// <param name="xControl">The control element.</param>
349 private static void SetControlAttributes(int attributes, XElement xControl)
350 {
351 if (0 == (attributes & WindowsInstallerConstants.MsidbControlAttributesEnabled))
352 {
353 xControl.SetAttributeValue("Disabled", "yes");
354 }
355
356 if (WindowsInstallerConstants.MsidbControlAttributesIndirect == (attributes & WindowsInstallerConstants.MsidbControlAttributesIndirect))
357 {
358 xControl.SetAttributeValue("Indirect", "yes");
359 }
360
361 if (WindowsInstallerConstants.MsidbControlAttributesInteger == (attributes & WindowsInstallerConstants.MsidbControlAttributesInteger))
362 {
363 xControl.SetAttributeValue("Integer", "yes");
364 }
365
366 if (WindowsInstallerConstants.MsidbControlAttributesLeftScroll == (attributes & WindowsInstallerConstants.MsidbControlAttributesLeftScroll))
367 {
368 xControl.SetAttributeValue("LeftScroll", "yes");
369 }
370
371 if (WindowsInstallerConstants.MsidbControlAttributesRightAligned == (attributes & WindowsInstallerConstants.MsidbControlAttributesRightAligned))
372 {
373 xControl.SetAttributeValue("RightAligned", "yes");
374 }
375
376 if (WindowsInstallerConstants.MsidbControlAttributesRTLRO == (attributes & WindowsInstallerConstants.MsidbControlAttributesRTLRO))
377 {
378 xControl.SetAttributeValue("RightToLeft", "yes");
379 }
380
381 if (WindowsInstallerConstants.MsidbControlAttributesSunken == (attributes & WindowsInstallerConstants.MsidbControlAttributesSunken))
382 {
383 xControl.SetAttributeValue("Sunken", "yes");
384 }
385
386 if (0 == (attributes & WindowsInstallerConstants.MsidbControlAttributesVisible))
387 {
388 xControl.SetAttributeValue("Hidden", "yes");
389 }
390 }
391
392 /// <summary>
393 /// Creates an action element.
394 /// </summary>
395 /// <param name="actionSymbol">The action from which the element should be created.</param>
396 private void CreateActionElement(WixActionSymbol actionSymbol)
397 {
398 XElement xAction;
399
400 if (this.TryGetIndexedElement("CustomAction", out var _, actionSymbol.Action)) // custom action
401 {
402 xAction = new XElement(Names.CustomElement,
403 new XAttribute("Action", actionSymbol.Action),
404 String.IsNullOrEmpty(actionSymbol.Condition) ? null : new XAttribute("Condition", actionSymbol.Condition));
405
406 switch (actionSymbol.Sequence)
407 {
408 case (-4):
409 xAction.SetAttributeValue("OnExit", "suspend");
410 break;
411 case (-3):
412 xAction.SetAttributeValue("OnExit", "error");
413 break;
414 case (-2):
415 xAction.SetAttributeValue("OnExit", "cancel");
416 break;
417 case (-1):
418 xAction.SetAttributeValue("OnExit", "success");
419 break;
420 default:
421 if (null != actionSymbol.Before)
422 {
423 xAction.SetAttributeValue("Before", actionSymbol.Before);
424 }
425 else if (null != actionSymbol.After)
426 {
427 xAction.SetAttributeValue("After", actionSymbol.After);
428 }
429 else if (actionSymbol.Sequence.HasValue)
430 {
431 xAction.SetAttributeValue("Sequence", actionSymbol.Sequence.Value);
432 }
433 break;
434 }
435 }
436 else if (this.TryGetIndexedElement("Dialog", out var _, actionSymbol.Action)) // dialog
437 {
438 xAction = new XElement(Names.CustomElement,
439 new XAttribute("Dialog", actionSymbol.Action),
440 new XAttribute("Condition", actionSymbol.Condition));
441
442 switch (actionSymbol.Sequence)
443 {
444 case (-4):
445 xAction.SetAttributeValue("OnExit", "suspend");
446 break;
447 case (-3):
448 xAction.SetAttributeValue("OnExit", "error");
449 break;
450 case (-2):
451 xAction.SetAttributeValue("OnExit", "cancel");
452 break;
453 case (-1):
454 xAction.SetAttributeValue("OnExit", "success");
455 break;
456 default:
457 SetAttributeIfNotNull(xAction, "Before", actionSymbol.Before);
458 SetAttributeIfNotNull(xAction, "After", actionSymbol.After);
459 SetAttributeIfNotNull(xAction, "Sequence", actionSymbol.Sequence);
460 break;
461 }
462 }
463 else // possibly a standard action without suggested sequence information
464 {
465 xAction = this.CreateStandardActionElement(actionSymbol);
466 }
467
468 // add the action element to the appropriate sequence element
469 if (null != xAction)
470 {
471 var sequenceTable = actionSymbol.SequenceTable.ToString();
472 if (!this.Singletons.TryGetValue(sequenceTable, out var xSequence))
473 {
474 xSequence = new XElement(Names.WxsNamespace + sequenceTable);
475
476 this.RootElement.Add(xSequence);
477 this.Singletons.Add(sequenceTable, xSequence);
478 }
479
480 try
481 {
482 xSequence.Add(xAction);
483 }
484 catch (ArgumentException) // action/dialog is not valid for this sequence
485 {
486 this.Messaging.Write(WarningMessages.IllegalActionInSequence(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action));
487 }
488 }
489 }
490
491 /// <summary>
492 /// Creates a standard action element.
493 /// </summary>
494 /// <param name="actionSymbol">The action row from which the element should be created.</param>
495 /// <returns>The created element.</returns>
496 private XElement CreateStandardActionElement(WixActionSymbol actionSymbol)
497 {
498 XElement xStandardAction = null;
499
500 switch (actionSymbol.Action)
501 {
502 case "AllocateRegistrySpace":
503 case "BindImage":
504 case "CostFinalize":
505 case "CostInitialize":
506 case "CreateFolders":
507 case "CreateShortcuts":
508 case "DeleteServices":
509 case "DuplicateFiles":
510 case "ExecuteAction":
511 case "FileCost":
512 case "InstallAdminPackage":
513 case "InstallFiles":
514 case "InstallFinalize":
515 case "InstallInitialize":
516 case "InstallODBC":
517 case "InstallServices":
518 case "InstallValidate":
519 case "IsolateComponents":
520 case "MigrateFeatureStates":
521 case "MoveFiles":
522 case "MsiPublishAssemblies":
523 case "MsiUnpublishAssemblies":
524 case "PatchFiles":
525 case "ProcessComponents":
526 case "PublishComponents":
527 case "PublishFeatures":
528 case "PublishProduct":
529 case "RegisterClassInfo":
530 case "RegisterComPlus":
531 case "RegisterExtensionInfo":
532 case "RegisterFonts":
533 case "RegisterMIMEInfo":
534 case "RegisterProduct":
535 case "RegisterProgIdInfo":
536 case "RegisterTypeLibraries":
537 case "RegisterUser":
538 case "RemoveDuplicateFiles":
539 case "RemoveEnvironmentStrings":
540 case "RemoveFiles":
541 case "RemoveFolders":
542 case "RemoveIniValues":
543 case "RemoveODBC":
544 case "RemoveRegistryValues":
545 case "RemoveShortcuts":
546 case "SelfRegModules":
547 case "SelfUnregModules":
548 case "SetODBCFolders":
549 case "StartServices":
550 case "StopServices":
551 case "UnpublishComponents":
552 case "UnpublishFeatures":
553 case "UnregisterClassInfo":
554 case "UnregisterComPlus":
555 case "UnregisterExtensionInfo":
556 case "UnregisterFonts":
557 case "UnregisterMIMEInfo":
558 case "UnregisterProgIdInfo":
559 case "UnregisterTypeLibraries":
560 case "ValidateProductID":
561 case "WriteEnvironmentStrings":
562 case "WriteIniValues":
563 case "WriteRegistryValues":
564 xStandardAction = new XElement(Names.WxsNamespace + actionSymbol.Action);
565 break;
566
567 case "AppSearch":
568 this.StandardActions.TryGetValue(actionSymbol.Id.Id, out var appSearchActionRow);
569
570 if (null != actionSymbol.Before || null != actionSymbol.After || (null != appSearchActionRow && actionSymbol.Sequence != appSearchActionRow.Sequence))
571 {
572 xStandardAction = new XElement(Names.AppSearchElement);
573
574 SetAttributeIfNotNull(xStandardAction, "Condition", actionSymbol.Condition);
575 SetAttributeIfNotNull(xStandardAction, "Before", actionSymbol.Before);
576 SetAttributeIfNotNull(xStandardAction, "After", actionSymbol.After);
577 SetAttributeIfNotNull(xStandardAction, "Sequence", actionSymbol.Sequence);
578
579 return xStandardAction;
580 }
581 break;
582
583 case "CCPSearch":
584 case "DisableRollback":
585 case "FindRelatedProducts":
586 case "ForceReboot":
587 case "InstallExecute":
588 case "InstallExecuteAgain":
589 case "LaunchConditions":
590 case "RemoveExistingProducts":
591 case "ResolveSource":
592 case "RMCCPSearch":
593 case "ScheduleReboot":
594 xStandardAction = new XElement(Names.WxsNamespace + actionSymbol.Action);
595 Decompiler.SequenceRelativeAction(actionSymbol, xStandardAction);
596 return xStandardAction;
597
598 default:
599 this.Messaging.Write(WarningMessages.UnknownAction(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action));
600 return null;
601 }
602
603 if (xStandardAction != null)
604 {
605 this.SequenceStandardAction(actionSymbol, xStandardAction);
606 }
607
608 return xStandardAction;
609 }
610
611 /// <summary>
612 /// Applies the condition and sequence to a standard action element based on the action symbol data.
613 /// </summary>
614 /// <param name="actionSymbol">Action data from the database.</param>
615 /// <param name="xAction">Element to be sequenced.</param>
616 private void SequenceStandardAction(WixActionSymbol actionSymbol, XElement xAction)
617 {
618 xAction.SetAttributeValue("Condition", actionSymbol.Condition);
619
620 if ((null != actionSymbol.Before || null != actionSymbol.After) && 0 == actionSymbol.Sequence)
621 {
622 this.Messaging.Write(WarningMessages.DecompiledStandardActionRelativelyScheduledInModule(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action));
623 }
624 else if (actionSymbol.Sequence.HasValue)
625 {
626 xAction.SetAttributeValue("Sequence", actionSymbol.Sequence.Value);
627 }
628 }
629
630 /// <summary>
631 /// Applies the condition and relative sequence to an action element based on the action row data.
632 /// </summary>
633 /// <param name="actionSymbol">Action data from the database.</param>
634 /// <param name="xAction">Element to be sequenced.</param>
635 private static void SequenceRelativeAction(WixActionSymbol actionSymbol, XElement xAction)
636 {
637 SetAttributeIfNotNull(xAction, "Condition", actionSymbol.Condition);
638 SetAttributeIfNotNull(xAction, "Before", actionSymbol.Before);
639 SetAttributeIfNotNull(xAction, "After", actionSymbol.After);
640 SetAttributeIfNotNull(xAction, "Sequence", actionSymbol.Sequence);
641 }
642
643 /// <summary>
644 /// Ensure that a particular property exists in the decompiled output.
645 /// </summary>
646 /// <param name="id">The identifier of the property.</param>
647 /// <returns>The property element.</returns>
648 private XElement EnsureProperty(string id)
649 {
650 XElement xProperty;
651
652 if (!this.TryGetIndexedElement("Property", out xProperty, id))
653 {
654 xProperty = new XElement(Names.PropertyElement, new XAttribute("Id", id));
655
656 this.RootElement.Add(xProperty);
657 this.IndexElement(xProperty, "Property", id);
658 }
659
660 return xProperty;
661 }
662
663 /// <summary>
664 /// Finalize decompilation.
665 /// </summary>
666 /// <param name="tables">The collection of all tables.</param>
667 private void FinalizeDecompile(TableIndexedCollection tables)
668 {
669 if (OutputType.PatchCreation == this.OutputType)
670 {
671 this.FinalizeFamilyFileRangesTable(tables);
672 }
673 else
674 {
675 this.FinalizeSummaryInformationStream(tables);
676 this.FinalizeCheckBoxTable(tables);
677 this.FinalizeComponentTable(tables);
678 this.FinalizeDialogTable(tables);
679 this.FinalizeDuplicateMoveFileTables(tables);
680 this.FinalizeFeatureComponentsTable(tables);
681 this.FinalizeFileTable(tables);
682 this.FinalizeMIMETable(tables);
683 this.FinalizeMsiLockPermissionsExTable(tables);
684 this.FinalizeLockPermissionsTable(tables);
685 this.FinalizeProgIdTable(tables);
686 this.FinalizePropertyTable(tables);
687 this.FinalizeRemoveFileTable(tables);
688 this.FinalizeSearchTables(tables);
689 this.FinalizeShortcutTable(tables);
690 this.FinalizeUpgradeTable(tables);
691 this.FinalizeSequenceTables(tables);
692 this.FinalizeVerbTable(tables);
693 }
694 }
695
696 /// <summary>
697 /// Finalize the CheckBox table.
698 /// </summary>
699 /// <param name="tables">The collection of all tables.</param>
700 /// <remarks>
701 /// Enumerates through all the Control rows, looking for controls of type "CheckBox" with
702 /// a value in the Property column. This is then possibly matched up with a CheckBox row
703 /// to retrieve a CheckBoxValue. There is no foreign key from the Control to CheckBox table.
704 /// </remarks>
705 private void FinalizeCheckBoxTable(TableIndexedCollection tables)
706 {
707 // if the user has requested to suppress the UI elements, we have nothing to do
708 if (this.SuppressUI)
709 {
710 return;
711 }
712
713 var checkBoxTable = tables["CheckBox"];
714 var controlTable = tables["Control"];
715
716 var checkBoxes = checkBoxTable?.Rows.ToDictionary(row => row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter));
717 var checkBoxProperties = checkBoxTable?.Rows.ToDictionary(row => row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), row => false);
718
719 // enumerate through the Control table, adding CheckBox values where appropriate
720 if (null != controlTable)
721 {
722 foreach (var row in controlTable.Rows)
723 {
724 var xControl = this.GetIndexedElement(row);
725
726 if ("CheckBox" == row.FieldAsString(2))
727 {
728 var property = row.FieldAsString(8);
729 if (!String.IsNullOrEmpty(property) && checkBoxes.TryGetValue(property, out var checkBoxRow))
730 {
731 // if we've seen this property already, create a reference to it
732 if (checkBoxProperties.TryGetValue(property, out var seen) && seen)
733 {
734 xControl.SetAttributeValue("CheckBoxPropertyRef", property);
735 }
736 else
737 {
738 xControl.SetAttributeValue("Property", property);
739 checkBoxProperties[property] = true;
740 }
741
742 xControl.SetAttributeValue("CheckBoxValue", checkBoxRow.FieldAsString(1));
743 }
744 else
745 {
746 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Control", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Property", row.FieldAsString(8), "CheckBox"));
747 }
748 }
749 }
750 }
751 }
752
753 /// <summary>
754 /// Finalize the Component table.
755 /// </summary>
756 /// <param name="tables">The collection of all tables.</param>
757 /// <remarks>
758 /// Set the keypaths for each component.
759 /// </remarks>
760 private void FinalizeComponentTable(TableIndexedCollection tables)
761 {
762 var componentTable = tables["Component"];
763 var fileTable = tables["File"];
764 var odbcDataSourceTable = tables["ODBCDataSource"];
765 var registryTable = tables["Registry"];
766
767 // set the component keypaths
768 if (null != componentTable)
769 {
770 foreach (var row in componentTable.Rows)
771 {
772 var attributes = row.FieldAsInteger(3);
773 var keyPath = row.FieldAsString(5);
774
775 if (String.IsNullOrEmpty(keyPath))
776 {
777 var xComponent = this.GetIndexedElement("Component", row.FieldAsString(0));
778 xComponent.SetAttributeValue("KeyPath", "yes");
779 }
780 else if (WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath == (attributes & WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath))
781 {
782 if (this.TryGetIndexedElement("Registry", out var xRegistry, keyPath))
783 {
784 if (xRegistry.Name.LocalName == "RegistryValue")
785 {
786 xRegistry.SetAttributeValue("KeyPath", "yes");
787 }
788 else
789 {
790 this.Messaging.Write(WarningMessages.IllegalRegistryKeyPath(row.SourceLineNumbers, "Component", keyPath));
791 }
792 }
793 else
794 {
795 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Component", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "KeyPath", keyPath, "Registry"));
796 }
797 }
798 else if (WindowsInstallerConstants.MsidbComponentAttributesODBCDataSource == (attributes & WindowsInstallerConstants.MsidbComponentAttributesODBCDataSource))
799 {
800 if (this.TryGetIndexedElement("ODBCDataSource", out var xOdbcDataSource, keyPath))
801 {
802 xOdbcDataSource.SetAttributeValue("KeyPath", "yes");
803 }
804 else
805 {
806 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Component", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "KeyPath", keyPath, "ODBCDataSource"));
807 }
808 }
809 else
810 {
811 if (this.TryGetIndexedElement("File", out var xFile, keyPath))
812 {
813 xFile.SetAttributeValue("KeyPath", "yes");
814 }
815 else
816 {
817 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Component", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "KeyPath", keyPath, "File"));
818 }
819 }
820 }
821 }
822
823 // add the File children elements
824 if (null != fileTable)
825 {
826 foreach (FileRow fileRow in fileTable.Rows)
827 {
828 if (this.TryGetIndexedElement("Component", out var xComponent, fileRow.Component)
829 && this.TryGetIndexedElement(fileRow, out var xFile))
830 {
831 xComponent.Add(xFile);
832 }
833 else
834 {
835 this.Messaging.Write(WarningMessages.ExpectedForeignRow(fileRow.SourceLineNumbers, "File", fileRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", fileRow.Component, "Component"));
836 }
837 }
838 }
839
840 // add the ODBCDataSource children elements
841 if (null != odbcDataSourceTable)
842 {
843 foreach (var row in odbcDataSourceTable.Rows)
844 {
845 if (this.TryGetIndexedElement("Component", out var xComponent, row.FieldAsString(1))
846 && this.TryGetIndexedElement(row, out var xOdbcDataSource))
847 {
848 xComponent.Add(xOdbcDataSource);
849 }
850 else
851 {
852 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "ODBCDataSource", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(1), "Component"));
853 }
854 }
855 }
856
857 // add the Registry children elements
858 if (null != registryTable)
859 {
860 foreach (var row in registryTable.Rows)
861 {
862 if (this.TryGetIndexedElement("Component", out var xComponent, row.FieldAsString(5))
863 && this.TryGetIndexedElement(row, out var xRegistry))
864 {
865 xComponent.Add(xRegistry);
866 }
867 else
868 {
869 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Registry", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(5), "Component"));
870 }
871 }
872 }
873 }
874
875 /// <summary>
876 /// Finalize the Dialog table.
877 /// </summary>
878 /// <param name="tables">The collection of all tables.</param>
879 /// <remarks>
880 /// Sets the first, default, and cancel control for each dialog and adds all child control
881 /// elements to the dialog.
882 /// </remarks>
883 private void FinalizeDialogTable(TableIndexedCollection tables)
884 {
885 // if the user has requested to suppress the UI elements, we have nothing to do
886 if (this.SuppressUI)
887 {
888 return;
889 }
890
891 var addedControls = new HashSet<XElement>();
892
893 var controlTable = tables["Control"];
894 var controlRows = controlTable?.Rows.ToDictionary(row => row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter));
895
896 var dialogTable = tables["Dialog"];
897 if (null != dialogTable)
898 {
899 foreach (var dialogRow in dialogTable.Rows)
900 {
901 var xDialog = this.GetIndexedElement(dialogRow);
902 var dialogId = dialogRow.FieldAsString(0);
903
904 if (!this.TryGetIndexedElement("Control", out var xControl, dialogId, dialogRow.FieldAsString(7)))
905 {
906 this.Messaging.Write(WarningMessages.ExpectedForeignRow(dialogRow.SourceLineNumbers, "Dialog", dialogRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog", dialogId, "Control_First", dialogRow.FieldAsString(7), "Control"));
907 }
908
909 // add tabbable controls
910 while (null != xControl)
911 {
912 var controlId = xControl.Attribute("Id");
913 var controlRow = controlRows[String.Concat(dialogId, DecompilerConstants.PrimaryKeyDelimiter, controlId)];
914
915 xControl.SetAttributeValue("TabSkip", "no");
916
917 xDialog.Add(xControl);
918 addedControls.Add(xControl);
919
920 var controlNext = controlRow.FieldAsString(10);
921 if (!String.IsNullOrEmpty(controlNext))
922 {
923 if (this.TryGetIndexedElement("Control", out xControl, dialogId, controlNext))
924 {
925 // looped back to the first control in the dialog
926 if (addedControls.Contains(xControl))
927 {
928 xControl = null;
929 }
930 }
931 else
932 {
933 this.Messaging.Write(WarningMessages.ExpectedForeignRow(controlRow.SourceLineNumbers, "Control", controlRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", dialogId, "Control_Next", controlNext, "Control"));
934 }
935 }
936 else
937 {
938 xControl = null;
939 }
940 }
941
942 // set default control
943 var controlDefault = dialogRow.FieldAsString(8);
944 if (!String.IsNullOrEmpty(controlDefault))
945 {
946 if (this.TryGetIndexedElement("Control", out var xDefaultControl, dialogId, controlDefault))
947 {
948 xDefaultControl.SetAttributeValue("Default", "yes");
949 }
950 else
951 {
952 this.Messaging.Write(WarningMessages.ExpectedForeignRow(dialogRow.SourceLineNumbers, "Dialog", dialogRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog", dialogId, "Control_Default", Convert.ToString(dialogRow[8]), "Control"));
953 }
954 }
955
956 // set cancel control
957 var controlCancel = dialogRow.FieldAsString(8);
958 if (!String.IsNullOrEmpty(controlCancel))
959 {
960 if (this.TryGetIndexedElement("Control", out var xCancelControl, dialogId, controlCancel))
961 {
962 xCancelControl.SetAttributeValue("Cancel", "yes");
963 }
964 else
965 {
966 this.Messaging.Write(WarningMessages.ExpectedForeignRow(dialogRow.SourceLineNumbers, "Dialog", dialogRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog", dialogId, "Control_Cancel", Convert.ToString(dialogRow[9]), "Control"));
967 }
968 }
969 }
970 }
971
972 // add the non-tabbable controls to the dialog
973 if (null != controlTable)
974 {
975 foreach (var controlRow in controlTable.Rows)
976 {
977 var dialogId = controlRow.FieldAsString(0);
978 if (!this.TryGetIndexedElement("Dialog", out var xDialog, dialogId))
979 {
980 this.Messaging.Write(WarningMessages.ExpectedForeignRow(controlRow.SourceLineNumbers, "Control", controlRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", dialogId, "Dialog"));
981 continue;
982 }
983
984 var xControl = this.GetIndexedElement(controlRow);
985 if (!addedControls.Contains(xControl))
986 {
987 xControl.SetAttributeValue("TabSkip", "yes");
988 xDialog.Add(xControl);
989 }
990 }
991 }
992 }
993
994 /// <summary>
995 /// Finalize the DuplicateFile and MoveFile tables.
996 /// </summary>
997 /// <param name="tables">The collection of all tables.</param>
998 /// <remarks>
999 /// Sets the source/destination property/directory for each DuplicateFile or
1000 /// MoveFile row.
1001 /// </remarks>
1002 private void FinalizeDuplicateMoveFileTables(TableIndexedCollection tables)
1003 {
1004 var duplicateFileTable = tables["DuplicateFile"];
1005 if (null != duplicateFileTable)
1006 {
1007 foreach (var row in duplicateFileTable.Rows)
1008 {
1009 var xCopyFile = this.GetIndexedElement(row);
1010 var destination = row.FieldAsString(4);
1011 if (!String.IsNullOrEmpty(destination))
1012 {
1013 if (this.TryGetIndexedElement("Directory", out var _, destination))
1014 {
1015 xCopyFile.SetAttributeValue("DestinationDirectory", destination);
1016 }
1017 else
1018 {
1019 xCopyFile.SetAttributeValue("DestinationProperty", destination);
1020 }
1021 }
1022 }
1023 }
1024
1025 var moveFileTable = tables["MoveFile"];
1026 if (null != moveFileTable)
1027 {
1028 foreach (var row in moveFileTable.Rows)
1029 {
1030 var xCopyFile = this.GetIndexedElement(row);
1031 var source = row.FieldAsString(4);
1032 if (!String.IsNullOrEmpty(source))
1033 {
1034 if (this.TryGetIndexedElement("Directory", out var _, source))
1035 {
1036 xCopyFile.SetAttributeValue("SourceDirectory", source);
1037 }
1038 else
1039 {
1040 xCopyFile.SetAttributeValue("SourceProperty", source);
1041 }
1042 }
1043
1044 var destination = row.FieldAsString(5);
1045 if (this.TryGetIndexedElement("Directory", out var _, destination))
1046 {
1047 xCopyFile.SetAttributeValue("DestinationDirectory", destination);
1048 }
1049 else
1050 {
1051 xCopyFile.SetAttributeValue("DestinationProperty", destination);
1052 }
1053 }
1054 }
1055 }
1056
1057 /// <summary>
1058 /// Finalize the FamilyFileRanges table.
1059 /// </summary>
1060 /// <param name="tables">The collection of all tables.</param>
1061 private void FinalizeFamilyFileRangesTable(TableIndexedCollection tables)
1062 {
1063 var familyFileRangesTable = tables["FamilyFileRanges"];
1064 if (null != familyFileRangesTable)
1065 {
1066 foreach (var row in familyFileRangesTable.Rows)
1067 {
1068 var xProtectRange = new XElement(Names.ProtectRangeElement);
1069
1070 if (!row.IsColumnNull(2) && !row.IsColumnNull(3))
1071 {
1072 var retainOffsets = row.FieldAsString(2).Split(',');
1073 var retainLengths = row.FieldAsString(3).Split(',');
1074
1075 if (retainOffsets.Length == retainLengths.Length)
1076 {
1077 for (var i = 0; i < retainOffsets.Length; i++)
1078 {
1079 if (retainOffsets[i].StartsWith("0x", StringComparison.Ordinal))
1080 {
1081 xProtectRange.SetAttributeValue("Offset", Convert.ToInt32(retainOffsets[i].Substring(2), 16));
1082 }
1083 else
1084 {
1085 xProtectRange.SetAttributeValue("Offset", Convert.ToInt32(retainOffsets[i], CultureInfo.InvariantCulture));
1086 }
1087
1088 if (retainLengths[i].StartsWith("0x", StringComparison.Ordinal))
1089 {
1090 xProtectRange.SetAttributeValue("Length", Convert.ToInt32(retainLengths[i].Substring(2), 16));
1091 }
1092 else
1093 {
1094 xProtectRange.SetAttributeValue("Length", Convert.ToInt32(retainLengths[i], CultureInfo.InvariantCulture));
1095 }
1096 }
1097 }
1098 else
1099 {
1100 // TODO: warn
1101 }
1102 }
1103 else if (!row.IsColumnNull(2) || !row.IsColumnNull(3))
1104 {
1105 // TODO: warn about mismatch between columns
1106 }
1107
1108 this.IndexElement(row, xProtectRange);
1109 }
1110 }
1111
1112 var usedProtectRanges = new HashSet<XElement>();
1113 var externalFilesTable = tables["ExternalFiles"];
1114 if (null != externalFilesTable)
1115 {
1116 foreach (var row in externalFilesTable.Rows)
1117 {
1118 if (this.TryGetIndexedElement(row, out var xExternalFile)
1119 && this.TryGetIndexedElement("FamilyFileRanges", out var xProtectRange, row.FieldAsString(0), row.FieldAsString(0)))
1120 {
1121 xExternalFile.Add(xProtectRange);
1122 usedProtectRanges.Add(xProtectRange);
1123 }
1124 }
1125 }
1126
1127 var targetFiles_OptionalDataTable = tables["TargetFiles_OptionalData"];
1128 if (null != targetFiles_OptionalDataTable)
1129 {
1130 var targetImagesTable = tables["TargetImages"];
1131 var targetImageRows = targetImagesTable?.Rows.ToDictionary(row => row.FieldAsString(0));
1132
1133 var upgradedImagesTable = tables["UpgradedImages"];
1134 var upgradedImagesRows = upgradedImagesTable?.Rows.ToDictionary(row => row.FieldAsString(0));
1135
1136 foreach (var row in targetFiles_OptionalDataTable.Rows)
1137 {
1138 var xTargetFile = this.PatchTargetFiles[row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)];
1139
1140 if (!targetImageRows.TryGetValue(row.FieldAsString(0), out var targetImageRow))
1141 {
1142 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, targetFiles_OptionalDataTable.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Target", row.FieldAsString(0), "TargetImages"));
1143 continue;
1144 }
1145
1146 if (!upgradedImagesRows.TryGetValue(row.FieldAsString(3), out var upgradedImagesRow))
1147 {
1148 this.Messaging.Write(WarningMessages.ExpectedForeignRow(targetImageRow.SourceLineNumbers, targetImageRow.Table.Name, targetImageRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Upgraded", row.FieldAsString(3), "UpgradedImages"));
1149 continue;
1150 }
1151
1152 if (this.TryGetIndexedElement("FamilyFileRanges", out var xProtectRange, upgradedImagesRow.FieldAsString(4), row.FieldAsString(1)))
1153 {
1154 xTargetFile.Add(xProtectRange);
1155 usedProtectRanges.Add(xProtectRange);
1156 }
1157 }
1158 }
1159
1160 if (null != familyFileRangesTable)
1161 {
1162 foreach (var row in familyFileRangesTable.Rows)
1163 {
1164 var xProtectRange = this.GetIndexedElement(row);
1165
1166 if (!usedProtectRanges.Contains(xProtectRange))
1167 {
1168 var xProtectFile = new XElement(Names.ProtectFileElement, new XAttribute("File", row.FieldAsString(1)));
1169 xProtectFile.Add(xProtectRange);
1170
1171 this.AddChildToParent("ImageFamilies", xProtectFile, row, 0);
1172 }
1173 }
1174 }
1175 }
1176
1177 /// <summary>
1178 /// Finalize the FeatureComponents table.
1179 /// </summary>
1180 /// <param name="tables">The collection of all tables.</param>
1181 /// <remarks>
1182 /// Since tables specifying references to the FeatureComponents table have references to
1183 /// the Feature and Component table separately, but not the FeatureComponents table specifically,
1184 /// the FeatureComponents table and primary features must be decompiled during finalization.
1185 /// </remarks>
1186 private void FinalizeFeatureComponentsTable(TableIndexedCollection tables)
1187 {
1188 var classTable = tables["Class"];
1189 if (null != classTable)
1190 {
1191 foreach (var row in classTable.Rows)
1192 {
1193 this.SetPrimaryFeature(row, 11, 2);
1194 }
1195 }
1196
1197 var extensionTable = tables["Extension"];
1198 if (null != extensionTable)
1199 {
1200 foreach (var row in extensionTable.Rows)
1201 {
1202 this.SetPrimaryFeature(row, 4, 1);
1203 }
1204 }
1205
1206 var msiAssemblyTable = tables["MsiAssembly"];
1207 if (null != msiAssemblyTable)
1208 {
1209 foreach (var row in msiAssemblyTable.Rows)
1210 {
1211 this.SetPrimaryFeature(row, 1, 0);
1212 }
1213 }
1214
1215 var publishComponentTable = tables["PublishComponent"];
1216 if (null != publishComponentTable)
1217 {
1218 foreach (var row in publishComponentTable.Rows)
1219 {
1220 this.SetPrimaryFeature(row, 4, 2);
1221 }
1222 }
1223
1224 var typeLibTable = tables["TypeLib"];
1225 if (null != typeLibTable)
1226 {
1227 foreach (var row in typeLibTable.Rows)
1228 {
1229 this.SetPrimaryFeature(row, 6, 2);
1230 }
1231 }
1232 }
1233
1234 /// <summary>
1235 /// Finalize the File table.
1236 /// </summary>
1237 /// <param name="tables">The collection of all tables.</param>
1238 /// <remarks>
1239 /// Sets the source, diskId, and assembly information for each file.
1240 /// </remarks>
1241 private void FinalizeFileTable(TableIndexedCollection tables)
1242 {
1243 // index the media table by media id
1244 var mediaTable = tables["Media"];
1245 var mediaRows = new RowDictionary<MediaRow>(mediaTable);
1246
1247 // set the disk identifiers and sources for files
1248 foreach (var fileRow in tables["File"]?.Rows.Cast<FileRow>() ?? Enumerable.Empty<FileRow>())
1249 {
1250 var xFile = this.GetIndexedElement("File", fileRow.File);
1251
1252 // Don't bother processing files that are orphaned (and won't show up in the output anyway)
1253 if (null != xFile.Parent)
1254 {
1255 // set the diskid
1256 if (null != mediaTable)
1257 {
1258 foreach (MediaRow mediaRow in mediaTable.Rows)
1259 {
1260 if (fileRow.Sequence <= mediaRow.LastSequence && mediaRow.DiskId != 1)
1261 {
1262 xFile.SetAttributeValue("DiskId", mediaRow.DiskId);
1263 break;
1264 }
1265 }
1266 }
1267
1268 var fileId = xFile?.Attribute("Id")?.Value;
1269 var fileCompressed = xFile?.Attribute("Compressed")?.Value;
1270 var fileShortName = xFile?.Attribute("ShortName")?.Value;
1271 var fileName = xFile?.Attribute("Name")?.Value;
1272
1273 // set the source (done here because it requires information from the Directory table)
1274 if (OutputType.Module == this.OutputType)
1275 {
1276 xFile.SetAttributeValue("Source", String.Concat(this.BaseSourcePath, Path.DirectorySeparatorChar, "File", Path.DirectorySeparatorChar, fileId, '.', this.ModularizationGuid.Substring(1, 36).Replace('-', '_')));
1277 }
1278 else if (fileCompressed == "yes" || (fileCompressed != "no" && this.Compressed) || (OutputType.Product == this.OutputType && this.TreatProductAsModule))
1279 {
1280 xFile.SetAttributeValue("Source", String.Concat(this.BaseSourcePath, Path.DirectorySeparatorChar, "File", Path.DirectorySeparatorChar, fileId));
1281 }
1282 else // uncompressed
1283 {
1284 var name = (!this.ShortNames && !String.IsNullOrEmpty(fileName)) ? fileName : fileShortName ?? fileName;
1285
1286 if (this.Compressed) // uncompressed at the root of the source image
1287 {
1288 xFile.SetAttributeValue("Source", String.Concat("SourceDir", Path.DirectorySeparatorChar, name));
1289 }
1290 else
1291 {
1292 var sourcePath = this.GetSourcePath(xFile);
1293 xFile.SetAttributeValue("Source", Path.Combine(sourcePath, name));
1294 }
1295 }
1296 }
1297 }
1298
1299 // set the file assemblies and manifests
1300 foreach (var row in tables["MsiAssembly"]?.Rows ?? Enumerable.Empty<Row>())
1301 {
1302 if (this.TryGetIndexedElement("Component", out var xComponent, row.FieldAsString(0)))
1303 {
1304 foreach (var xFile in xComponent.Elements(Names.FileElement).Where(x => x.Attribute("KeyPath")?.Value == "yes"))
1305 {
1306 xFile.SetAttributeValue("AssemblyManifest", row.FieldAsString(2));
1307 xFile.SetAttributeValue("AssemblyApplication", row.FieldAsString(3));
1308 xFile.SetAttributeValue("Assembly", row.FieldAsInteger(4) == 0 ? ".net" : "win32");
1309 }
1310 }
1311 else
1312 {
1313 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "MsiAssembly", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(0), "Component"));
1314 }
1315 }
1316
1317 // nest the TypeLib elements
1318 foreach (var row in tables["TypeLib"]?.Rows ?? Enumerable.Empty<Row>())
1319 {
1320 var xComponent = this.GetIndexedElement("Component", row.FieldAsString(2));
1321 var xTypeLib = this.GetIndexedElement(row);
1322
1323 foreach (var xFile in xComponent.Elements(Names.FileElement).Where(x => x.Attribute("KeyPath")?.Value == "yes"))
1324 {
1325 xFile.Add(xTypeLib);
1326 }
1327 }
1328 }
1329
1330 /// <summary>
1331 /// Finalize the MIME table.
1332 /// </summary>
1333 /// <param name="tables">The collection of all tables.</param>
1334 /// <remarks>
1335 /// There is a foreign key shared between the MIME and Extension
1336 /// tables so either one would be valid to be decompiled first, so
1337 /// the only safe way to nest the MIME elements is to do it during finalize.
1338 /// </remarks>
1339 private void FinalizeMIMETable(TableIndexedCollection tables)
1340 {
1341 var extensionRows = tables["Extension"]?.Rows ?? Enumerable.Empty<Row>();
1342 foreach (var row in extensionRows)
1343 {
1344 // set the default MIME element for this extension
1345 var mimeRef = row.FieldAsString(3);
1346 if (null != mimeRef)
1347 {
1348 if (this.TryGetIndexedElement("MIME", out var xMime, mimeRef))
1349 {
1350 xMime.SetAttributeValue("Default", "yes");
1351 }
1352 else
1353 {
1354 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Extension", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "MIME_", row.FieldAsString(3), "MIME"));
1355 }
1356 }
1357 }
1358
1359 var extensionsByExtensionId = this.IndexTableOneToMany(extensionRows);
1360
1361 foreach (var row in tables["MIME"]?.Rows ?? Enumerable.Empty<Row>())
1362 {
1363 var xMime = this.GetIndexedElement(row);
1364
1365 if (extensionsByExtensionId.TryGetValue(row.FieldAsString(1), out var xExtensions))
1366 {
1367 foreach (var extension in xExtensions)
1368 {
1369 extension.Add(xMime);
1370 }
1371 }
1372 else
1373 {
1374 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "MIME", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Extension_", row.FieldAsString(1), "Extension"));
1375 }
1376 }
1377 }
1378
1379 /// <summary>
1380 /// Finalize the ProgId table.
1381 /// </summary>
1382 /// <param name="tables">The collection of all tables.</param>
1383 /// <remarks>
1384 /// Enumerates through all the Class rows, looking for child ProgIds (these are the
1385 /// default ProgIds for a given Class). Then go through the ProgId table and add any
1386 /// remaining ProgIds for each Class. This happens during finalize because there is
1387 /// a circular dependency between the Class and ProgId tables.
1388 /// </remarks>
1389 private void FinalizeProgIdTable(TableIndexedCollection tables)
1390 {
1391 // add the default ProgIds for each class (and index the class table)
1392 var classRows = tables["Class"]?.Rows?.Where(row => row.FieldAsString(3) != null) ?? Enumerable.Empty<Row>();
1393
1394 var classesByCLSID = this.IndexTableOneToMany(classRows);
1395
1396 var addedProgIds = new Dictionary<XElement, string>();
1397
1398 foreach (var row in classRows)
1399 {
1400 var clsid = row.FieldAsString(0);
1401 var xClass = this.GetIndexedElement(row);
1402
1403 if (this.TryGetIndexedElement("ProgId", out var xProgId, row.FieldAsString(3)))
1404 {
1405 if (addedProgIds.TryGetValue(xProgId, out var progid))
1406 {
1407 this.Messaging.Write(WarningMessages.TooManyProgIds(row.SourceLineNumbers, row.FieldAsString(0), row.FieldAsString(3), progid));
1408 }
1409 else
1410 {
1411 xClass.Add(xProgId);
1412 addedProgIds.Add(xProgId, clsid);
1413 }
1414 }
1415 else
1416 {
1417 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Class", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "ProgId_Default", row.FieldAsString(3), "ProgId"));
1418 }
1419 }
1420
1421 // add the remaining non-default ProgId entries for each class
1422 foreach (var row in tables["ProgId"]?.Rows ?? Enumerable.Empty<Row>())
1423 {
1424 var clsid = row.FieldAsString(2);
1425 var xProgId = this.GetIndexedElement(row);
1426
1427 if (!addedProgIds.ContainsKey(xProgId) && null != clsid && null == xProgId.Parent)
1428 {
1429 if (classesByCLSID.TryGetValue(clsid, out var xClasses))
1430 {
1431 foreach (var xClass in xClasses)
1432 {
1433 xClass.Add(xProgId);
1434 addedProgIds.Add(xProgId, clsid);
1435 }
1436 }
1437 else
1438 {
1439 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "ProgId", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Class_", row.FieldAsString(2), "Class"));
1440 }
1441 }
1442 }
1443
1444 // Check for any progIds that are not hooked up to a class and hook them up to the component specified by the extension
1445 var componentsById = this.IndexTableOneToMany(tables, "Component");
1446
1447 foreach (var row in tables["Extension"]?.Rows?.Where(row => row.FieldAsString(2) != null) ?? Enumerable.Empty<Row>())
1448 {
1449 var xProgId = this.GetIndexedElement("ProgId", row.FieldAsString(2));
1450
1451 // Haven't added the progId yet and it doesn't have a parent progId
1452 if (!addedProgIds.ContainsKey(xProgId) && null == xProgId.Parent)
1453 {
1454 if (componentsById.TryGetValue(row.FieldAsString(1), out var xComponents))
1455 {
1456 foreach (var xComponent in xComponents)
1457 {
1458 xComponent.Add(xProgId);
1459 }
1460 }
1461 else
1462 {
1463 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Extension", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(1), "Component"));
1464 }
1465 }
1466 }
1467 }
1468
1469 /// <summary>
1470 /// Finalize the Property table.
1471 /// </summary>
1472 /// <param name="tables">The collection of all tables.</param>
1473 /// <remarks>
1474 /// Removes properties that are generated from other entries.
1475 /// </remarks>
1476 private void FinalizePropertyTable(TableIndexedCollection tables)
1477 {
1478 foreach (var row in tables["CustomAction"]?.Rows ?? Enumerable.Empty<Row>())
1479 {
1480 // If no other fields on the property are set we must have created it in the backend.
1481 var bits = row.FieldAsInteger(1);
1482 if (WindowsInstallerConstants.MsidbCustomActionTypeHideTarget == (bits & WindowsInstallerConstants.MsidbCustomActionTypeHideTarget)
1483 && WindowsInstallerConstants.MsidbCustomActionTypeInScript == (bits & WindowsInstallerConstants.MsidbCustomActionTypeInScript)
1484 && this.TryGetIndexedElement("Property", out var xProperty, row.FieldAsString(0))
1485 && String.IsNullOrEmpty(xProperty.Attribute("Value")?.Value)
1486 && xProperty.Attribute("Secure")?.Value != "yes"
1487 && xProperty.Attribute("SuppressModularization")?.Value != "yes")
1488 {
1489 xProperty.Remove();
1490 }
1491 }
1492 }
1493
1494 /// <summary>
1495 /// Finalize the RemoveFile table.
1496 /// </summary>
1497 /// <param name="tables">The collection of all tables.</param>
1498 /// <remarks>
1499 /// Sets the directory/property for each RemoveFile row.
1500 /// </remarks>
1501 private void FinalizeRemoveFileTable(TableIndexedCollection tables)
1502 {
1503 foreach (var row in tables["RemoveFile"]?.Rows ?? Enumerable.Empty<Row>())
1504 {
1505 var xRemove = this.GetIndexedElement(row);
1506 var property = row.FieldAsString(3);
1507
1508 if (this.TryGetIndexedElement("Directory", out var _, property))
1509 {
1510 xRemove.SetAttributeValue("Directory", property);
1511 }
1512 else
1513 {
1514 xRemove.SetAttributeValue("Property", property);
1515 }
1516 }
1517 }
1518
1519 /// <summary>
1520 /// Finalize the LockPermissions or MsiLockPermissionsEx table.
1521 /// </summary>
1522 /// <param name="tables">The collection of all tables.</param>
1523 /// <param name="tableName">Which table to finalize.</param>
1524 /// <remarks>
1525 /// Nests the Permission elements below their parent elements. There are no declared foreign
1526 /// keys for the parents of the LockPermissions table.
1527 /// </remarks>
1528 private void FinalizePermissionsTable(TableIndexedCollection tables, string tableName)
1529 {
1530 var createFoldersById = this.IndexTableOneToMany(tables, tableName);
1531
1532 foreach (var row in tables[tableName]?.Rows ?? Enumerable.Empty<Row>())
1533 {
1534 var id = row.FieldAsString(0);
1535 var table = row.FieldAsString(1);
1536 var xPermission = this.GetIndexedElement(row);
1537
1538 if ("CreateFolder" == table)
1539 {
1540 if (createFoldersById.TryGetValue(id, out var xCreateFolders))
1541 {
1542 foreach (var xCreateFolder in xCreateFolders)
1543 {
1544 xCreateFolder.Add(xPermission);
1545 }
1546 }
1547 else
1548 {
1549 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, tableName, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "LockObject", id, table));
1550 }
1551 }
1552 else
1553 {
1554 if (this.TryGetIndexedElement(table, out var xParent, id))
1555 {
1556 xParent.Add(xPermission);
1557 }
1558 else
1559 {
1560 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, tableName, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "LockObject", id, table));
1561 }
1562 }
1563 }
1564 }
1565
1566 /// <summary>
1567 /// Finalize the LockPermissions table.
1568 /// </summary>
1569 /// <param name="tables">The collection of all tables.</param>
1570 /// <remarks>
1571 /// Nests the Permission elements below their parent elements. There are no declared foreign
1572 /// keys for the parents of the LockPermissions table.
1573 /// </remarks>
1574 private void FinalizeLockPermissionsTable(TableIndexedCollection tables) => this.FinalizePermissionsTable(tables, "LockPermissions");
1575
1576 /// <summary>
1577 /// Finalize the MsiLockPermissionsEx table.
1578 /// </summary>
1579 /// <param name="tables">The collection of all tables.</param>
1580 /// <remarks>
1581 /// Nests the PermissionEx elements below their parent elements. There are no declared foreign
1582 /// keys for the parents of the MsiLockPermissionsEx table.
1583 /// </remarks>
1584 private void FinalizeMsiLockPermissionsExTable(TableIndexedCollection tables) => this.FinalizePermissionsTable(tables, "MsiLockPermissionsEx");
1585
1586 private static Dictionary<string, List<string>> IndexTable(Table table, int keyColumn, int? dataColumn)
1587 {
1588 if (table == null)
1589 {
1590 return new Dictionary<string, List<string>>();
1591 }
1592
1593 return table.Rows
1594 .ToLookup(row => row.FieldAsString(keyColumn), row => dataColumn.HasValue ? row.FieldAsString(dataColumn.Value) : null)
1595 .ToDictionary(lookup => lookup.Key, lookup => lookup.ToList());
1596 }
1597
1598 private static XElement FindComplianceDrive(XElement xSearch)
1599 {
1600 var xComplianceDrive = xSearch.Element(Names.ComplianceDriveElement);
1601 if (null == xComplianceDrive)
1602 {
1603 xComplianceDrive = new XElement(Names.ComplianceDriveElement);
1604 xSearch.Add(xComplianceDrive);
1605 }
1606
1607 return xComplianceDrive;
1608 }
1609
1610 /// <summary>
1611 /// Finalize the search tables.
1612 /// </summary>
1613 /// <param name="tables">The collection of all tables.</param>
1614 /// <remarks>Does all the complex linking required for the search tables.</remarks>
1615 private void FinalizeSearchTables(TableIndexedCollection tables)
1616 {
1617 var appSearches = IndexTable(tables["AppSearch"], keyColumn: 1, dataColumn: 0);
1618 var ccpSearches = IndexTable(tables["CCPSearch"], keyColumn: 0, dataColumn: null);
1619 var drLocators = tables["DrLocator"]?.Rows.ToDictionary(row => this.GetIndexedElement(row), row => row);
1620
1621 var xComplianceCheck = new XElement(Names.ComplianceCheckElement);
1622 if (ccpSearches.Keys.Any(ccpSignature => !appSearches.ContainsKey(ccpSignature)))
1623 {
1624 this.RootElement.Add(xComplianceCheck);
1625 }
1626
1627 // index the locator tables by their signatures
1628 var locators =
1629 new[] { "CompLocator", "RegLocator", "IniLocator", "DrLocator", "Signature" }
1630 .SelectMany(table => tables[table]?.Rows ?? Enumerable.Empty<Row>())
1631 .ToLookup(row => row.FieldAsString(0), row => row)
1632 .ToDictionary(lookup => lookup.Key, lookup => lookup.ToList());
1633
1634 // move the DrLocator rows with a parent of CCP_DRIVE first to ensure they get FileSearch children (not FileSearchRef)
1635 foreach (var locatorRows in locators.Values)
1636 {
1637 var firstDrLocator = -1;
1638
1639 for (var i = 0; i < locatorRows.Count; i++)
1640 {
1641 var locatorRow = (Row)locatorRows[i];
1642
1643 if ("DrLocator" == locatorRow.TableDefinition.Name)
1644 {
1645 if (-1 == firstDrLocator)
1646 {
1647 firstDrLocator = i;
1648 }
1649
1650 if ("CCP_DRIVE" == Convert.ToString(locatorRow[1]))
1651 {
1652 locatorRows.RemoveAt(i);
1653 locatorRows.Insert(firstDrLocator, locatorRow);
1654 break;
1655 }
1656 }
1657 }
1658 }
1659
1660 var xUsedSearches = new HashSet<XElement>();
1661 var xUnusedSearches = new Dictionary<string, XElement>();
1662
1663 foreach (var signature in locators.Keys)
1664 {
1665 var locatorRows = locators[signature];
1666 var xSignatureSearches = new List<XElement>();
1667
1668 foreach (var locatorRow in locatorRows)
1669 {
1670 var used = true;
1671 var xSearch = this.GetIndexedElement(locatorRow);
1672
1673 if ("Signature" == locatorRow.TableDefinition.Name && 0 < xSignatureSearches.Count)
1674 {
1675 foreach (var xSearchParent in xSignatureSearches)
1676 {
1677 if (!xUsedSearches.Contains(xSearch))
1678 {
1679 xSearchParent.Add(xSearch);
1680 xUsedSearches.Add(xSearch);
1681 }
1682 else
1683 {
1684 var xFileSearchRef = new XElement(Names.FileSearchRefElement,
1685 new XAttribute("Id", signature));
1686
1687 xSearchParent.Add(xFileSearchRef);
1688 }
1689 }
1690 }
1691 else if ("DrLocator" == locatorRow.TableDefinition.Name && !locatorRow.IsColumnNull(1))
1692 {
1693 var parentSignature = locatorRow.FieldAsString(1);
1694
1695 if ("CCP_DRIVE" == parentSignature)
1696 {
1697 if (appSearches.ContainsKey(signature)
1698 && appSearches.TryGetValue(signature, out var appSearchPropertyIds))
1699 {
1700 foreach (var propertyId in appSearchPropertyIds)
1701 {
1702 var xProperty = this.EnsureProperty(propertyId);
1703
1704 if (ccpSearches.ContainsKey(signature))
1705 {
1706 xProperty.SetAttributeValue("ComplianceCheck", "yes");
1707 }
1708
1709 var xComplianceDrive = FindComplianceDrive(xProperty);
1710
1711 if (!xUsedSearches.Contains(xSearch))
1712 {
1713 xComplianceDrive.Add(xSearch);
1714 xUsedSearches.Add(xSearch);
1715 }
1716 else
1717 {
1718 var directorySearchRef = new XElement(Names.DirectorySearchRefElement,
1719 new XAttribute("Id", signature),
1720 XAttributeIfNotNull("Parent", locatorRow, 1),
1721 XAttributeIfNotNull("Path", locatorRow, 2));
1722
1723 xComplianceDrive.Add(directorySearchRef);
1724 xSignatureSearches.Add(directorySearchRef);
1725 }
1726 }
1727 }
1728 else if (ccpSearches.ContainsKey(signature))
1729 {
1730 var xComplianceDrive = FindComplianceDrive(xComplianceCheck);
1731
1732 if (!xUsedSearches.Contains(xSearch))
1733 {
1734 xComplianceDrive.Add(xSearch);
1735 xUsedSearches.Add(xSearch);
1736 }
1737 else
1738 {
1739 var directorySearchRef = new XElement(Names.DirectorySearchRefElement,
1740 new XAttribute("Id", signature),
1741 XAttributeIfNotNull("Parent", locatorRow, 1),
1742 XAttributeIfNotNull("Path", locatorRow, 2));
1743
1744 xComplianceDrive.Add(directorySearchRef);
1745 xSignatureSearches.Add(directorySearchRef);
1746 }
1747 }
1748 }
1749 else
1750 {
1751 var usedDrLocator = false;
1752
1753 if (locators.TryGetValue(parentSignature, out var parentLocatorRows))
1754 {
1755 foreach (var parentLocatorRow in parentLocatorRows)
1756 {
1757 if ("DrLocator" == parentLocatorRow.TableDefinition.Name)
1758 {
1759 var xParentSearch = this.GetIndexedElement(parentLocatorRow);
1760
1761 if (xParentSearch.HasElements)
1762 {
1763 var parentDrLocatorRow = drLocators[xParentSearch];
1764 var xDirectorySearchRef = new XElement(Names.DirectorySearchRefElement,
1765 new XAttribute("Id", parentSignature),
1766 XAttributeIfNotNull("Parent", parentDrLocatorRow, 1),
1767 XAttributeIfNotNull("Path", parentDrLocatorRow, 2));
1768
1769 xParentSearch = xDirectorySearchRef;
1770 xUnusedSearches.Add(parentSignature, xDirectorySearchRef);
1771 }
1772
1773 if (!xUsedSearches.Contains(xSearch))
1774 {
1775 xParentSearch.Add(xSearch);
1776 xUsedSearches.Add(xSearch);
1777 usedDrLocator = true;
1778 }
1779 else
1780 {
1781 var xDirectorySearchRef = new XElement(Names.DirectorySearchRefElement,
1782 new XAttribute("Id", signature),
1783 new XAttribute("Parent", parentSignature),
1784 XAttributeIfNotNull("Path", locatorRow, 2));
1785
1786 xParentSearch.Add(xSearch);
1787 usedDrLocator = true;
1788 }
1789 }
1790 else if ("RegLocator" == parentLocatorRow.TableDefinition.Name)
1791 {
1792 var xParentSearch = this.GetIndexedElement(parentLocatorRow);
1793
1794 xParentSearch.Add(xSearch);
1795 xUsedSearches.Add(xSearch);
1796 usedDrLocator = true;
1797 }
1798 }
1799
1800 // keep track of unused DrLocator rows
1801 if (!usedDrLocator)
1802 {
1803 xUnusedSearches.Add(xSearch.Attribute("Id").Value, xSearch);
1804 }
1805 }
1806 else
1807 {
1808 // TODO: warn
1809 }
1810 }
1811 }
1812 else if (appSearches.ContainsKey(signature)
1813 && appSearches.TryGetValue(signature, out var appSearchPropertyIds))
1814 {
1815 foreach (var propertyId in appSearchPropertyIds)
1816 {
1817 var xProperty = this.EnsureProperty(propertyId);
1818
1819 if (ccpSearches.ContainsKey(signature))
1820 {
1821 xProperty.SetAttributeValue("ComplianceCheck", "yes");
1822 }
1823
1824 if (!xUsedSearches.Contains(xSearch))
1825 {
1826 xProperty.Add(xSearch);
1827 xUsedSearches.Add(xSearch);
1828 }
1829 else if ("RegLocator" == locatorRow.TableDefinition.Name)
1830 {
1831 var xRegistrySearchRef = new XElement(Names.RegistrySearchRefElement,
1832 new XAttribute("Id", signature));
1833
1834 xProperty.Add(xRegistrySearchRef);
1835 xSignatureSearches.Add(xRegistrySearchRef);
1836 }
1837 else
1838 {
1839 // TODO: warn about unavailable Ref element
1840 }
1841 }
1842 }
1843 else if (ccpSearches.ContainsKey(signature))
1844 {
1845 if (!xUsedSearches.Contains(xSearch))
1846 {
1847 xComplianceCheck.Add(xSearch);
1848 xUsedSearches.Add(xSearch);
1849 }
1850 else if ("RegLocator" == locatorRow.TableDefinition.Name)
1851 {
1852 var xRegistrySearchRef = new XElement(Names.RegistrySearchRefElement,
1853 new XAttribute("Id", signature));
1854
1855 xComplianceCheck.Add(xRegistrySearchRef);
1856 xSignatureSearches.Add(xRegistrySearchRef);
1857 }
1858 else
1859 {
1860 // TODO: warn about unavailable Ref element
1861 }
1862 }
1863 else
1864 {
1865 if (xSearch.Name.LocalName == "DirectorySearch" || xSearch.Name.LocalName == "RegistrySearch")
1866 {
1867 xUnusedSearches.Add(xSearch.Attribute("Id").Value, xSearch);
1868 }
1869 else
1870 {
1871 // TODO: warn
1872 used = false;
1873 }
1874 }
1875
1876 // keep track of the search elements for this signature so that nested searches go in the proper parents
1877 if (used)
1878 {
1879 xSignatureSearches.Add(xSearch);
1880 }
1881 }
1882 }
1883
1884 // Iterate through the unused elements through a sorted list of their ids so the output is deterministic.
1885 foreach (var unusedSearch in xUnusedSearches.OrderBy(kvp => kvp.Key))
1886 {
1887 var used = false;
1888
1889 XElement xLeafDirectorySearch = null;
1890 var xUnusedSearch = unusedSearch.Value;
1891 var xParent = xUnusedSearch;
1892 var updatedLeaf = true;
1893 while (updatedLeaf)
1894 {
1895 updatedLeaf = false;
1896
1897 var xDirectorySearch = xParent.Element(Names.DirectorySearchElement);
1898 if (xDirectorySearch != null)
1899 {
1900 xParent = xLeafDirectorySearch = xDirectorySearch;
1901 updatedLeaf = true;
1902 }
1903 }
1904
1905 if (xLeafDirectorySearch != null)
1906 {
1907 var leafDirectorySearchId = xLeafDirectorySearch.Attribute("Id").Value;
1908 if (appSearches.TryGetValue(leafDirectorySearchId, out var appSearchPropertyIds))
1909 {
1910 var xProperty = this.EnsureProperty(appSearchPropertyIds[0]);
1911 xProperty.Add(xUnusedSearch);
1912 used = true;
1913 }
1914 else if (ccpSearches.ContainsKey(leafDirectorySearchId))
1915 {
1916 xComplianceCheck.Add(xUnusedSearch);
1917 used = true;
1918 }
1919 else
1920 {
1921 // TODO: warn
1922 }
1923 }
1924
1925 if (!used)
1926 {
1927 // TODO: warn
1928 }
1929 }
1930 }
1931
1932 /// <summary>
1933 /// Finalize the Shortcut table.
1934 /// </summary>
1935 /// <param name="tables">The collection of all tables.</param>
1936 /// <remarks>
1937 /// Sets Advertise to yes if Target points to a Feature.
1938 /// Occurs during finalization because it has to check against every feature row.
1939 /// </remarks>
1940 private void FinalizeShortcutTable(TableIndexedCollection tables)
1941 {
1942 var shortcutTable = tables["Shortcut"];
1943 if (null == shortcutTable)
1944 {
1945 return;
1946 }
1947
1948 foreach (var row in shortcutTable.Rows)
1949 {
1950 var xShortcut = this.GetIndexedElement(row);
1951
1952 var target = row.FieldAsString(4);
1953
1954 if (this.TryGetIndexedElement("Feature", out var _, target))
1955 {
1956 xShortcut.SetAttributeValue("Advertise", "yes");
1957 this.SetPrimaryFeature(row, 4, 3);
1958 }
1959 else
1960 {
1961 // TODO: use this value to do a "more-correct" nesting under the indicated File or CreateDirectory element
1962 xShortcut.SetAttributeValue("Target", target);
1963 }
1964 }
1965 }
1966
1967 /// <summary>
1968 /// Finalize the sequence tables.
1969 /// </summary>
1970 /// <param name="tables">The collection of all tables.</param>
1971 /// <remarks>
1972 /// Creates the sequence elements. Occurs during finalization because its
1973 /// not known if sequences refer to custom actions or dialogs during decompilation.
1974 /// </remarks>
1975 private void FinalizeSequenceTables(TableIndexedCollection tables)
1976 {
1977 // finalize the normal sequence tables
1978 if (OutputType.Product == this.OutputType && !this.TreatProductAsModule)
1979 {
1980 foreach (SequenceTable sequenceTable in Enum.GetValues(typeof(SequenceTable)))
1981 {
1982 var sequenceTableName = sequenceTable.WindowsInstallerTableName();
1983
1984 // if suppressing UI elements, skip UI-related sequence tables
1985 if (this.SuppressUI && ("AdminUISequence" == sequenceTableName || "InstallUISequence" == sequenceTableName))
1986 {
1987 continue;
1988 }
1989
1990 var table = tables[sequenceTableName];
1991
1992 if (null != table)
1993 {
1994 var actionSymbols = new List<WixActionSymbol>();
1995 var needAbsoluteScheduling = this.SuppressRelativeActionSequencing;
1996 var nonSequencedActionRows = new Dictionary<string, WixActionSymbol>();
1997 var suppressedRelativeActionRows = new Dictionary<string, WixActionSymbol>();
1998
1999 // create a sorted array of actions in this table
2000 foreach (var row in table.Rows)
2001 {
2002 var action = row.FieldAsString(0);
2003 var actionSymbol = new WixActionSymbol(null, new Identifier(AccessModifier.Global, sequenceTable, action));
2004
2005 actionSymbol.Action = action;
2006
2007 if (!row.IsColumnNull(1))
2008 {
2009 actionSymbol.Condition = row.FieldAsString(1);
2010 }
2011
2012 actionSymbol.Sequence = row.FieldAsInteger(2);
2013
2014 actionSymbol.SequenceTable = sequenceTable;
2015
2016 actionSymbols.Add(actionSymbol);
2017 }
2018 actionSymbols = actionSymbols.OrderBy(t => t.Sequence).ToList();
2019
2020 for (var i = 0; i < actionSymbols.Count && !needAbsoluteScheduling; i++)
2021 {
2022 var actionSymbol = actionSymbols[i];
2023 this.StandardActions.TryGetValue(actionSymbol.Id.Id, out var standardActionRow);
2024
2025 // create actions for custom actions, dialogs, AppSearch when its moved, and standard actions with non-standard conditions
2026 if ("AppSearch" == actionSymbol.Action || null == standardActionRow || actionSymbol.Condition != standardActionRow.Condition)
2027 {
2028 WixActionSymbol previousActionSymbol = null;
2029 WixActionSymbol nextActionSymbol = null;
2030
2031 // find the previous action row if there is one
2032 if (0 <= i - 1)
2033 {
2034 previousActionSymbol = actionSymbols[i - 1];
2035 }
2036
2037 // find the next action row if there is one
2038 if (actionSymbols.Count > i + 1)
2039 {
2040 nextActionSymbol = actionSymbols[i + 1];
2041 }
2042
2043 // the logic for setting the before or after attribute for an action:
2044 // 1. If more than one action shares the same sequence number, everything must be absolutely sequenced.
2045 // 2. If the next action is a standard action and is 1 sequence number higher, this action occurs before it.
2046 // 3. If the previous action is a standard action and is 1 sequence number lower, this action occurs after it.
2047 // 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.
2048 // 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.
2049 // 6. If this action is AppSearch and has all standard information, ignore it.
2050 // 7. If this action is standard and has a non-standard condition, create the action without any scheduling information.
2051 // 8. Everything must be absolutely sequenced.
2052 if ((null != previousActionSymbol && actionSymbol.Sequence == previousActionSymbol.Sequence) || (null != nextActionSymbol && actionSymbol.Sequence == nextActionSymbol.Sequence))
2053 {
2054 needAbsoluteScheduling = true;
2055 }
2056 else if (null != nextActionSymbol && this.StandardActions.ContainsKey(nextActionSymbol.Id.Id) && actionSymbol.Sequence + 1 == nextActionSymbol.Sequence)
2057 {
2058 actionSymbol.Before = nextActionSymbol.Action;
2059 }
2060 else if (null != previousActionSymbol && this.StandardActions.ContainsKey(previousActionSymbol.Id.Id) && actionSymbol.Sequence - 1 == previousActionSymbol.Sequence)
2061 {
2062 actionSymbol.After = previousActionSymbol.Action;
2063 }
2064 else if (null == standardActionRow && null != previousActionSymbol && actionSymbol.Sequence - 1 == previousActionSymbol.Sequence && previousActionSymbol.Before != actionSymbol.Action)
2065 {
2066 actionSymbol.After = previousActionSymbol.Action;
2067 }
2068 else if (null == standardActionRow && null != previousActionSymbol && actionSymbol.Sequence != previousActionSymbol.Sequence && null != nextActionSymbol && actionSymbol.Sequence + 1 == nextActionSymbol.Sequence)
2069 {
2070 actionSymbol.Before = nextActionSymbol.Action;
2071 }
2072 else if ("AppSearch" == actionSymbol.Action && null != standardActionRow && actionSymbol.Sequence == standardActionRow.Sequence && actionSymbol.Condition == standardActionRow.Condition)
2073 {
2074 // ignore an AppSearch row which has the WiX standard sequence and a standard condition
2075 }
2076 else if (null != standardActionRow && actionSymbol.Condition != standardActionRow.Condition) // standard actions get their standard sequence numbers
2077 {
2078 nonSequencedActionRows.Add(actionSymbol.Id.Id, actionSymbol);
2079 }
2080 else if (0 < actionSymbol.Sequence)
2081 {
2082 needAbsoluteScheduling = true;
2083 }
2084 }
2085 else
2086 {
2087 suppressedRelativeActionRows.Add(actionSymbol.Id.Id, actionSymbol);
2088 }
2089 }
2090
2091 // create the actions now that we know if they must be absolutely or relatively scheduled
2092 foreach (var actionRow in actionSymbols)
2093 {
2094 var key = actionRow.Id.Id;
2095
2096 if (needAbsoluteScheduling)
2097 {
2098 // remove any before/after information to ensure this is absolutely sequenced
2099 actionRow.Before = null;
2100 actionRow.After = null;
2101 }
2102 else if (nonSequencedActionRows.ContainsKey(key))
2103 {
2104 // clear the sequence attribute to ensure this action is scheduled without a sequence number (or before/after)
2105 actionRow.Sequence = 0;
2106 }
2107 else if (suppressedRelativeActionRows.ContainsKey(key))
2108 {
2109 // skip the suppressed relatively scheduled action rows
2110 continue;
2111 }
2112
2113 // create the action element
2114 this.CreateActionElement(actionRow);
2115 }
2116 }
2117 }
2118 }
2119 else if (OutputType.Module == this.OutputType || this.TreatProductAsModule) // finalize the Module sequence tables
2120 {
2121 foreach (SequenceTable sequenceTable in Enum.GetValues(typeof(SequenceTable)))
2122 {
2123 var sequenceTableName = sequenceTable.WindowsInstallerTableName();
2124
2125 // if suppressing UI elements, skip UI-related sequence tables
2126 if (this.SuppressUI && ("AdminUISequence" == sequenceTableName || "InstallUISequence" == sequenceTableName))
2127 {
2128 continue;
2129 }
2130
2131 var table = tables[String.Concat("Module", sequenceTableName)];
2132
2133 if (null != table)
2134 {
2135 foreach (var row in table.Rows)
2136 {
2137 var actionRow = new WixActionSymbol(null, new Identifier(AccessModifier.Global, sequenceTable, row.FieldAsString(0)));
2138
2139 actionRow.Action = row.FieldAsString(0);
2140
2141 if (!row.IsColumnNull(1))
2142 {
2143 actionRow.Sequence = row.FieldAsInteger(1);
2144 }
2145
2146 if (!row.IsColumnNull(2) && !row.IsColumnNull(3))
2147 {
2148 switch (row.FieldAsInteger(3))
2149 {
2150 case 0:
2151 actionRow.Before = row.FieldAsString(2);
2152 break;
2153 case 1:
2154 actionRow.After = row.FieldAsString(2);
2155 break;
2156 default:
2157 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[3].Column.Name, row[3]));
2158 break;
2159 }
2160 }
2161
2162 if (!row.IsColumnNull(4))
2163 {
2164 actionRow.Condition = row.FieldAsString(4);
2165 }
2166
2167 actionRow.SequenceTable = sequenceTable;
2168
2169 // create action elements for non-standard actions
2170 if (!this.StandardActions.ContainsKey(actionRow.Id.Id) || null != actionRow.After || null != actionRow.Before)
2171 {
2172 this.CreateActionElement(actionRow);
2173 }
2174 }
2175 }
2176 }
2177 }
2178 }
2179
2180 /// <summary>
2181 /// Finalize the Upgrade table.
2182 /// </summary>
2183 /// <param name="tables">The collection of all tables.</param>
2184 /// <remarks>
2185 /// Decompile the rows from the Upgrade and LaunchCondition tables
2186 /// created by the MajorUpgrade element.
2187 /// </remarks>
2188 private void FinalizeUpgradeTable(TableIndexedCollection tables)
2189 {
2190 var launchConditionTable = tables["LaunchCondition"];
2191 var upgradeTable = tables["Upgrade"];
2192 string downgradeErrorMessage = null;
2193 string disallowUpgradeErrorMessage = null;
2194
2195 // find the DowngradePreventedCondition launch condition message
2196 if (null != launchConditionTable && 0 < launchConditionTable.Rows.Count)
2197 {
2198 foreach (var launchRow in launchConditionTable.Rows)
2199 {
2200 if (WixUpgradeConstants.DowngradePreventedCondition == Convert.ToString(launchRow[0]))
2201 {
2202 downgradeErrorMessage = Convert.ToString(launchRow[1]);
2203 }
2204 else if (WixUpgradeConstants.UpgradePreventedCondition == Convert.ToString(launchRow[0]))
2205 {
2206 disallowUpgradeErrorMessage = Convert.ToString(launchRow[1]);
2207 }
2208 }
2209 }
2210
2211 if (null != upgradeTable && 0 < upgradeTable.Rows.Count)
2212 {
2213 XElement xMajorUpgrade = null;
2214
2215 foreach (UpgradeRow upgradeRow in upgradeTable.Rows)
2216 {
2217 if (WixUpgradeConstants.UpgradeDetectedProperty == upgradeRow.ActionProperty)
2218 {
2219 var attr = upgradeRow.Attributes;
2220 var removeFeatures = upgradeRow.Remove;
2221 xMajorUpgrade = xMajorUpgrade ?? new XElement(Names.MajorUpgradeElement);
2222
2223 if (WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive == (attr & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive))
2224 {
2225 xMajorUpgrade.SetAttributeValue("AllowSameVersionUpgrades", "yes");
2226 }
2227
2228 if (WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures != (attr & WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures))
2229 {
2230 xMajorUpgrade.SetAttributeValue("MigrateFeatures", "no");
2231 }
2232
2233 if (WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure == (attr & WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure))
2234 {
2235 xMajorUpgrade.SetAttributeValue("IgnoreRemoveFailure", "yes");
2236 }
2237
2238 if (!String.IsNullOrEmpty(removeFeatures))
2239 {
2240 xMajorUpgrade.SetAttributeValue("RemoveFeatures", removeFeatures);
2241 }
2242 }
2243 else if (WixUpgradeConstants.DowngradeDetectedProperty == upgradeRow.ActionProperty)
2244 {
2245 xMajorUpgrade = xMajorUpgrade ?? new XElement(Names.MajorUpgradeElement);
2246 xMajorUpgrade.SetAttributeValue("DowngradeErrorMessage", downgradeErrorMessage);
2247 }
2248 }
2249
2250 if (xMajorUpgrade != null)
2251 {
2252 if (String.IsNullOrEmpty(downgradeErrorMessage))
2253 {
2254 xMajorUpgrade.SetAttributeValue("AllowDowngrades", "yes");
2255 }
2256
2257 if (!String.IsNullOrEmpty(disallowUpgradeErrorMessage))
2258 {
2259 xMajorUpgrade.SetAttributeValue("Disallow", "yes");
2260 xMajorUpgrade.SetAttributeValue("DisallowUpgradeErrorMessage", disallowUpgradeErrorMessage);
2261 }
2262
2263 var scheduledType = DetermineMajorUpgradeScheduling(tables);
2264 if (scheduledType != "afterInstallValidate")
2265 {
2266 xMajorUpgrade.SetAttributeValue("Schedule", scheduledType);
2267 }
2268
2269 this.RootElement.Add(xMajorUpgrade);
2270 }
2271 }
2272 }
2273
2274 /// <summary>
2275 /// Finalize the Verb table.
2276 /// </summary>
2277 /// <param name="tables">The collection of all tables.</param>
2278 /// <remarks>
2279 /// The Extension table is a foreign table for the Verb table, but the
2280 /// foreign key is only part of the primary key of the Extension table,
2281 /// so it needs special logic to be nested properly.
2282 /// </remarks>
2283 private void FinalizeVerbTable(TableIndexedCollection tables)
2284 {
2285 var xExtensions = this.IndexTableOneToMany(tables["Extension"]);
2286
2287 var verbTable = tables["Verb"];
2288 if (null != verbTable)
2289 {
2290 foreach (var row in verbTable.Rows)
2291 {
2292 if (xExtensions.TryGetValue(row.FieldAsString(0), out var xVerbExtensions))
2293 {
2294 var xVerb = this.GetIndexedElement(row);
2295
2296 foreach (var xVerbExtension in xVerbExtensions)
2297 {
2298 xVerbExtension.Add(xVerb);
2299 }
2300 }
2301 else
2302 {
2303 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, verbTable.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Extension_", row.FieldAsString(0), "Extension"));
2304 }
2305 }
2306 }
2307 }
2308
2309 /// <summary>
2310 /// Get the path to a file in the source image.
2311 /// </summary>
2312 /// <param name="xFile">The file.</param>
2313 /// <returns>The path to the file in the source image.</returns>
2314 private string GetSourcePath(XElement xFile)
2315 {
2316 var sourcePath = new StringBuilder();
2317
2318 var component = xFile.Parent;
2319
2320 for (var xDirectory = component.Parent; null != xDirectory && xDirectory.Name.LocalName == "Directory"; xDirectory = xDirectory.Parent)
2321 {
2322 string name;
2323
2324 var dirSourceName = xDirectory.Attribute("SourceName")?.Value;
2325 var dirShortSourceName = xDirectory.Attribute("ShortSourceName")?.Value;
2326 var dirShortName = xDirectory.Attribute("ShortName")?.Value;
2327 var dirName = xDirectory.Attribute("Name")?.Value;
2328
2329 if (!this.ShortNames && null != dirSourceName)
2330 {
2331 name = dirSourceName;
2332 }
2333 else if (null != dirShortSourceName)
2334 {
2335 name = dirShortSourceName;
2336 }
2337 else if (!this.ShortNames || null == dirShortName)
2338 {
2339 name = dirName;
2340 }
2341 else
2342 {
2343 name = dirShortName;
2344 }
2345
2346 if (0 == sourcePath.Length)
2347 {
2348 sourcePath.Append(name);
2349 }
2350 else
2351 {
2352 sourcePath.Insert(0, Path.DirectorySeparatorChar);
2353 sourcePath.Insert(0, name);
2354 }
2355 }
2356
2357 return sourcePath.ToString();
2358 }
2359
2360 /// <summary>
2361 /// Resolve the dependencies for a table (this is a helper method for GetSortedTableNames).
2362 /// </summary>
2363 /// <param name="tableName">The name of the table to resolve.</param>
2364 /// <param name="unsortedTableNames">The unsorted table names.</param>
2365 /// <param name="sortedTableNames">The sorted table names.</param>
2366 private void ResolveTableDependencies(string tableName, List<string> unsortedTableNames, HashSet<string> sortedTableNames)
2367 {
2368 unsortedTableNames.Remove(tableName);
2369
2370 foreach (var columnDefinition in this.TableDefinitions[tableName].Columns)
2371 {
2372 // no dependency to resolve because this column doesn't reference another table
2373 if (null == columnDefinition.KeyTable)
2374 {
2375 continue;
2376 }
2377
2378 foreach (var keyTable in columnDefinition.KeyTable.Split(';'))
2379 {
2380 if (tableName == keyTable)
2381 {
2382 continue; // self-referencing dependency
2383 }
2384 else if (sortedTableNames.Contains(keyTable))
2385 {
2386 continue; // dependent table has already been sorted
2387 }
2388 else if (!this.TableDefinitions.Contains(keyTable))
2389 {
2390 this.Messaging.Write(ErrorMessages.MissingTableDefinition(keyTable));
2391 }
2392 else if (unsortedTableNames.Contains(keyTable))
2393 {
2394 this.ResolveTableDependencies(keyTable, unsortedTableNames, sortedTableNames);
2395 }
2396 else
2397 {
2398 // found a circular dependency, so ignore it (this assumes that the tables will
2399 // use a finalize method to nest their elements since the ordering will not be
2400 // deterministic
2401 }
2402 }
2403 }
2404
2405 sortedTableNames.Add(tableName);
2406 }
2407
2408 /// <summary>
2409 /// Get the names of the tables to process in the order they should be processed, according to their dependencies.
2410 /// </summary>
2411 /// <returns>A StringCollection containing the ordered table names.</returns>
2412 private HashSet<string> GetOrderedTableNames()
2413 {
2414 var orderedTableNames = new HashSet<string>();
2415 var unsortedTableNames = new List<string>(this.TableDefinitions.Select(t => t.Name));
2416
2417 // resolve the dependencies for each table
2418 while (0 < unsortedTableNames.Count)
2419 {
2420 this.ResolveTableDependencies(unsortedTableNames[0], unsortedTableNames, orderedTableNames);
2421 }
2422
2423 return orderedTableNames;
2424 }
2425
2426 /// <summary>
2427 /// Initialize decompilation.
2428 /// </summary>
2429 /// <param name="tables">The collection of all tables.</param>
2430 /// <param name="codepage"></param>
2431 private void InitializeDecompile(TableIndexedCollection tables, int codepage)
2432 {
2433 // reset all the state information
2434 this.Compressed = false;
2435 this.ShortNames = false;
2436
2437 this.Singletons.Clear();
2438 this.IndexedElements.Clear();
2439 this.PatchTargetFiles.Clear();
2440
2441 // set the codepage if its not neutral (0)
2442 if (0 != codepage)
2443 {
2444 this.RootElement.SetAttributeValue("Codepage", codepage);
2445 }
2446
2447 if (this.OutputType == OutputType.Module)
2448 {
2449 var table = tables["_SummaryInformation"];
2450 var row = table.Rows.SingleOrDefault(r => r.FieldAsInteger(0) == 9);
2451 this.ModularizationGuid = row?.FieldAsString(1);
2452 }
2453
2454 // index the rows from the extension libraries
2455 var indexedExtensionTables = new Dictionary<string, HashSet<string>>();
2456#if TODO_DECOMPILER_EXTENSIONS
2457 foreach (IDecompilerExtension extension in this.extensions)
2458 {
2459 // Get the optional library from the extension with the rows to be removed.
2460 Library library = extension.GetLibraryToRemove(this.tableDefinitions);
2461 if (null != library)
2462 {
2463 foreach (var section in library.Sections)
2464 {
2465 foreach (Table table in section.Tables)
2466 {
2467 foreach (Row row in table.Rows)
2468 {
2469 string primaryKey;
2470 string tableName;
2471
2472 // the Actions table needs to be handled specially
2473 if ("WixAction" == table.Name)
2474 {
2475 primaryKey = row.FieldAsString(1);
2476
2477 if (OutputType.Module == this.outputType)
2478 {
2479 tableName = String.Concat("Module", row.FieldAsString(0));
2480 }
2481 else
2482 {
2483 tableName = row.FieldAsString(0);
2484 }
2485 }
2486 else
2487 {
2488 primaryKey = row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter);
2489 tableName = table.Name;
2490 }
2491
2492 if (null != primaryKey)
2493 {
2494 HashSet<string> indexedExtensionRows;
2495 if (!indexedExtensionTables.TryGetValue(tableName, out indexedExtensionRows))
2496 {
2497 indexedExtensionRows = new HashSet<string>();
2498 indexedExtensionTables.Add(tableName, indexedExtensionRows);
2499 }
2500
2501 indexedExtensionRows.Add(primaryKey);
2502 }
2503 }
2504 }
2505 }
2506 }
2507 }
2508#endif
2509
2510 // remove the rows from the extension libraries (to allow full round-tripping)
2511 foreach (var kvp in indexedExtensionTables)
2512 {
2513 var tableName = kvp.Key;
2514 var indexedExtensionRows = kvp.Value;
2515
2516 var table = tables[tableName];
2517 if (null != table)
2518 {
2519 var originalRows = new RowDictionary<Row>(table);
2520
2521 // remove the original rows so that they can be added back if they should remain
2522 table.Rows.Clear();
2523
2524 foreach (var row in originalRows.Values)
2525 {
2526 if (!indexedExtensionRows.Contains(row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)))
2527 {
2528 table.Rows.Add(row);
2529 }
2530 }
2531 }
2532 }
2533 }
2534
2535 /// <summary>
2536 /// Decompile the tables.
2537 /// </summary>
2538 /// <param name="output">The output being decompiled.</param>
2539 private void DecompileTables(WindowsInstallerData output)
2540 {
2541 var orderedTableNames = this.GetOrderedTableNames();
2542 foreach (var tableName in orderedTableNames)
2543 {
2544 var table = output.Tables[tableName];
2545
2546 // table does not exist in this database or should not be decompiled
2547 if (null == table || !this.DecompilableTable(output, tableName))
2548 {
2549 continue;
2550 }
2551
2552 this.Messaging.Write(VerboseMessages.DecompilingTable(table.Name));
2553
2554 // empty tables may be kept with EnsureTable if the user set the proper option
2555 if (0 == table.Rows.Count && this.SuppressDroppingEmptyTables)
2556 {
2557 this.RootElement.Add(new XElement(Names.EnsureTableElement, new XAttribute("Id", table.Name)));
2558 }
2559
2560 switch (table.Name)
2561 {
2562 case "_SummaryInformation":
2563 // handled in FinalizeDecompile
2564 break;
2565 case "AdminExecuteSequence":
2566 case "AdminUISequence":
2567 case "AdvtExecuteSequence":
2568 case "InstallExecuteSequence":
2569 case "InstallUISequence":
2570 case "ModuleAdminExecuteSequence":
2571 case "ModuleAdminUISequence":
2572 case "ModuleAdvtExecuteSequence":
2573 case "ModuleInstallExecuteSequence":
2574 case "ModuleInstallUISequence":
2575 // handled in FinalizeSequenceTables
2576 break;
2577 case "ActionText":
2578 this.DecompileActionTextTable(table);
2579 break;
2580 case "AdvtUISequence":
2581 this.Messaging.Write(WarningMessages.DeprecatedTable(table.Name));
2582 break;
2583 case "AppId":
2584 this.DecompileAppIdTable(table);
2585 break;
2586 case "AppSearch":
2587 // handled in FinalizeSearchTables
2588 break;
2589 case "BBControl":
2590 this.DecompileBBControlTable(table);
2591 break;
2592 case "Billboard":
2593 this.DecompileBillboardTable(table);
2594 break;
2595 case "Binary":
2596 this.DecompileBinaryTable(table);
2597 break;
2598 case "BindImage":
2599 this.DecompileBindImageTable(table);
2600 break;
2601 case "CCPSearch":
2602 // handled in FinalizeSearchTables
2603 break;
2604 case "CheckBox":
2605 // handled in FinalizeCheckBoxTable
2606 break;
2607 case "Class":
2608 this.DecompileClassTable(table);
2609 break;
2610 case "ComboBox":
2611 this.DecompileComboBoxTable(table);
2612 break;
2613 case "Control":
2614 this.DecompileControlTable(table);
2615 break;
2616 case "ControlCondition":
2617 this.DecompileControlConditionTable(table);
2618 break;
2619 case "ControlEvent":
2620 this.DecompileControlEventTable(table);
2621 break;
2622 case "CreateFolder":
2623 this.DecompileCreateFolderTable(table);
2624 break;
2625 case "CustomAction":
2626 this.DecompileCustomActionTable(table);
2627 break;
2628 case "CompLocator":
2629 this.DecompileCompLocatorTable(table);
2630 break;
2631 case "Complus":
2632 this.DecompileComplusTable(table);
2633 break;
2634 case "Component":
2635 this.DecompileComponentTable(table);
2636 break;
2637 case "Condition":
2638 this.DecompileConditionTable(table);
2639 break;
2640 case "Dialog":
2641 this.DecompileDialogTable(table);
2642 break;
2643 case "Directory":
2644 this.DecompileDirectoryTable(table);
2645 break;
2646 case "DrLocator":
2647 this.DecompileDrLocatorTable(table);
2648 break;
2649 case "DuplicateFile":
2650 this.DecompileDuplicateFileTable(table);
2651 break;
2652 case "Environment":
2653 this.DecompileEnvironmentTable(table);
2654 break;
2655 case "Error":
2656 this.DecompileErrorTable(table);
2657 break;
2658 case "EventMapping":
2659 this.DecompileEventMappingTable(table);
2660 break;
2661 case "Extension":
2662 this.DecompileExtensionTable(table);
2663 break;
2664 case "ExternalFiles":
2665 this.DecompileExternalFilesTable(table);
2666 break;
2667 case "FamilyFileRanges":
2668 // handled in FinalizeFamilyFileRangesTable
2669 break;
2670 case "Feature":
2671 this.DecompileFeatureTable(table);
2672 break;
2673 case "FeatureComponents":
2674 this.DecompileFeatureComponentsTable(table);
2675 break;
2676 case "File":
2677 this.DecompileFileTable(table);
2678 break;
2679 case "FileSFPCatalog":
2680 this.DecompileFileSFPCatalogTable(table);
2681 break;
2682 case "Font":
2683 this.DecompileFontTable(table);
2684 break;
2685 case "Icon":
2686 this.DecompileIconTable(table);
2687 break;
2688 case "ImageFamilies":
2689 this.DecompileImageFamiliesTable(table);
2690 break;
2691 case "IniFile":
2692 this.DecompileIniFileTable(table);
2693 break;
2694 case "IniLocator":
2695 this.DecompileIniLocatorTable(table);
2696 break;
2697 case "IsolatedComponent":
2698 this.DecompileIsolatedComponentTable(table);
2699 break;
2700 case "LaunchCondition":
2701 this.DecompileLaunchConditionTable(table);
2702 break;
2703 case "ListBox":
2704 this.DecompileListBoxTable(table);
2705 break;
2706 case "ListView":
2707 this.DecompileListViewTable(table);
2708 break;
2709 case "LockPermissions":
2710 this.DecompileLockPermissionsTable(table);
2711 break;
2712 case "Media":
2713 this.DecompileMediaTable(table);
2714 break;
2715 case "MIME":
2716 this.DecompileMIMETable(table);
2717 break;
2718 case "ModuleAdvtUISequence":
2719 this.Messaging.Write(WarningMessages.DeprecatedTable(table.Name));
2720 break;
2721 case "ModuleComponents":
2722 // handled by DecompileComponentTable (since the ModuleComponents table
2723 // rows are created by nesting components under the Module element)
2724 break;
2725 case "ModuleConfiguration":
2726 this.DecompileModuleConfigurationTable(table);
2727 break;
2728 case "ModuleDependency":
2729 this.DecompileModuleDependencyTable(table);
2730 break;
2731 case "ModuleExclusion":
2732 this.DecompileModuleExclusionTable(table);
2733 break;
2734 case "ModuleIgnoreTable":
2735 this.DecompileModuleIgnoreTableTable(table);
2736 break;
2737 case "ModuleSignature":
2738 this.DecompileModuleSignatureTable(table);
2739 break;
2740 case "ModuleSubstitution":
2741 this.DecompileModuleSubstitutionTable(table);
2742 break;
2743 case "MoveFile":
2744 this.DecompileMoveFileTable(table);
2745 break;
2746 case "MsiAssembly":
2747 // handled in FinalizeFileTable
2748 break;
2749 case "MsiDigitalCertificate":
2750 this.DecompileMsiDigitalCertificateTable(table);
2751 break;
2752 case "MsiDigitalSignature":
2753 this.DecompileMsiDigitalSignatureTable(table);
2754 break;
2755 case "MsiEmbeddedChainer":
2756 this.DecompileMsiEmbeddedChainerTable(table);
2757 break;
2758 case "MsiEmbeddedUI":
2759 this.DecompileMsiEmbeddedUITable(table);
2760 break;
2761 case "MsiLockPermissionsEx":
2762 this.DecompileMsiLockPermissionsExTable(table);
2763 break;
2764 case "MsiPackageCertificate":
2765 this.DecompileMsiPackageCertificateTable(table);
2766 break;
2767 case "MsiPatchCertificate":
2768 this.DecompileMsiPatchCertificateTable(table);
2769 break;
2770 case "MsiShortcutProperty":
2771 this.DecompileMsiShortcutPropertyTable(table);
2772 break;
2773 case "ODBCAttribute":
2774 this.DecompileODBCAttributeTable(table);
2775 break;
2776 case "ODBCDataSource":
2777 this.DecompileODBCDataSourceTable(table);
2778 break;
2779 case "ODBCDriver":
2780 this.DecompileODBCDriverTable(table);
2781 break;
2782 case "ODBCSourceAttribute":
2783 this.DecompileODBCSourceAttributeTable(table);
2784 break;
2785 case "ODBCTranslator":
2786 this.DecompileODBCTranslatorTable(table);
2787 break;
2788 case "PatchMetadata":
2789 this.DecompilePatchMetadataTable(table);
2790 break;
2791 case "PatchSequence":
2792 this.DecompilePatchSequenceTable(table);
2793 break;
2794 case "ProgId":
2795 this.DecompileProgIdTable(table);
2796 break;
2797 case "Properties":
2798 this.DecompilePropertiesTable(table);
2799 break;
2800 case "Property":
2801 this.DecompilePropertyTable(table);
2802 break;
2803 case "PublishComponent":
2804 this.DecompilePublishComponentTable(table);
2805 break;
2806 case "RadioButton":
2807 this.DecompileRadioButtonTable(table);
2808 break;
2809 case "Registry":
2810 this.DecompileRegistryTable(table);
2811 break;
2812 case "RegLocator":
2813 this.DecompileRegLocatorTable(table);
2814 break;
2815 case "RemoveFile":
2816 this.DecompileRemoveFileTable(table);
2817 break;
2818 case "RemoveIniFile":
2819 this.DecompileRemoveIniFileTable(table);
2820 break;
2821 case "RemoveRegistry":
2822 this.DecompileRemoveRegistryTable(table);
2823 break;
2824 case "ReserveCost":
2825 this.DecompileReserveCostTable(table);
2826 break;
2827 case "SelfReg":
2828 this.DecompileSelfRegTable(table);
2829 break;
2830 case "ServiceControl":
2831 this.DecompileServiceControlTable(table);
2832 break;
2833 case "ServiceInstall":
2834 this.DecompileServiceInstallTable(table);
2835 break;
2836 case "SFPCatalog":
2837 this.DecompileSFPCatalogTable(table);
2838 break;
2839 case "Shortcut":
2840 this.DecompileShortcutTable(table);
2841 break;
2842 case "Signature":
2843 this.DecompileSignatureTable(table);
2844 break;
2845 case "TargetFiles_OptionalData":
2846 this.DecompileTargetFiles_OptionalDataTable(table);
2847 break;
2848 case "TargetImages":
2849 this.DecompileTargetImagesTable(table);
2850 break;
2851 case "TextStyle":
2852 this.DecompileTextStyleTable(table);
2853 break;
2854 case "TypeLib":
2855 this.DecompileTypeLibTable(table);
2856 break;
2857 case "Upgrade":
2858 this.DecompileUpgradeTable(table);
2859 break;
2860 case "UpgradedFiles_OptionalData":
2861 this.DecompileUpgradedFiles_OptionalDataTable(table);
2862 break;
2863 case "UpgradedFilesToIgnore":
2864 this.DecompileUpgradedFilesToIgnoreTable(table);
2865 break;
2866 case "UpgradedImages":
2867 this.DecompileUpgradedImagesTable(table);
2868 break;
2869 case "UIText":
2870 this.DecompileUITextTable(table);
2871 break;
2872 case "Verb":
2873 this.DecompileVerbTable(table);
2874 break;
2875
2876 default:
2877#if TODO_DECOMPILER_EXTENSIONS
2878 if (this.ExtensionsByTableName.TryGetValue(table.Name, out var extension)
2879 {
2880 extension.DecompileTable(table);
2881 }
2882 else
2883#endif
2884 if (!this.SuppressCustomTables)
2885 {
2886 this.DecompileCustomTable(table);
2887 }
2888 break;
2889 }
2890 }
2891 }
2892
2893 /// <summary>
2894 /// Determine if a particular table should be decompiled with the current settings.
2895 /// </summary>
2896 /// <param name="output">The output being decompiled.</param>
2897 /// <param name="tableName">The name of a table.</param>
2898 /// <returns>true if the table should be decompiled; false otherwise.</returns>
2899 private bool DecompilableTable(WindowsInstallerData output, string tableName)
2900 {
2901 switch (tableName)
2902 {
2903 case "ActionText":
2904 case "BBControl":
2905 case "Billboard":
2906 case "CheckBox":
2907 case "Control":
2908 case "ControlCondition":
2909 case "ControlEvent":
2910 case "Dialog":
2911 case "Error":
2912 case "EventMapping":
2913 case "RadioButton":
2914 case "TextStyle":
2915 case "UIText":
2916 return !this.SuppressUI;
2917 case "ModuleAdminExecuteSequence":
2918 case "ModuleAdminUISequence":
2919 case "ModuleAdvtExecuteSequence":
2920 case "ModuleAdvtUISequence":
2921 case "ModuleComponents":
2922 case "ModuleConfiguration":
2923 case "ModuleDependency":
2924 case "ModuleIgnoreTable":
2925 case "ModuleInstallExecuteSequence":
2926 case "ModuleInstallUISequence":
2927 case "ModuleExclusion":
2928 case "ModuleSignature":
2929 case "ModuleSubstitution":
2930 if (OutputType.Module != output.Type)
2931 {
2932 this.Messaging.Write(WarningMessages.SkippingMergeModuleTable(output.SourceLineNumbers, tableName));
2933 return false;
2934 }
2935 else
2936 {
2937 return true;
2938 }
2939 case "ExternalFiles":
2940 case "FamilyFileRanges":
2941 case "ImageFamilies":
2942 case "PatchMetadata":
2943 case "PatchSequence":
2944 case "Properties":
2945 case "TargetFiles_OptionalData":
2946 case "TargetImages":
2947 case "UpgradedFiles_OptionalData":
2948 case "UpgradedFilesToIgnore":
2949 case "UpgradedImages":
2950 if (OutputType.PatchCreation != output.Type)
2951 {
2952 this.Messaging.Write(WarningMessages.SkippingPatchCreationTable(output.SourceLineNumbers, tableName));
2953 return false;
2954 }
2955 else
2956 {
2957 return true;
2958 }
2959 case "MsiPatchHeaders":
2960 case "MsiPatchMetadata":
2961 case "MsiPatchOldAssemblyName":
2962 case "MsiPatchOldAssemblyFile":
2963 case "MsiPatchSequence":
2964 case "Patch":
2965 case "PatchPackage":
2966 this.Messaging.Write(WarningMessages.PatchTable(output.SourceLineNumbers, tableName));
2967 return false;
2968 case "_SummaryInformation":
2969 return true;
2970 case "_Validation":
2971 case "MsiAssemblyName":
2972 case "MsiFileHash":
2973 return false;
2974 default: // all other tables are allowed in any output except for a patch creation package
2975 if (OutputType.PatchCreation == output.Type)
2976 {
2977 this.Messaging.Write(WarningMessages.IllegalPatchCreationTable(output.SourceLineNumbers, tableName));
2978 return false;
2979 }
2980 else
2981 {
2982 return true;
2983 }
2984 }
2985 }
2986
2987 /// <summary>
2988 /// Decompile the _SummaryInformation table.
2989 /// </summary>
2990 /// <param name="tables">The tables to decompile.</param>
2991 private void FinalizeSummaryInformationStream(TableIndexedCollection tables)
2992 {
2993 var table = tables["_SummaryInformation"];
2994
2995 if (OutputType.Module == this.OutputType || OutputType.Product == this.OutputType)
2996 {
2997 var xSummaryInformation = new XElement(Names.SummaryInformationElement);
2998
2999 foreach (var row in table.Rows)
3000 {
3001 var value = row.FieldAsString(1);
3002
3003 if (!String.IsNullOrEmpty(value))
3004 {
3005 switch (row.FieldAsInteger(0))
3006 {
3007 case 1:
3008 if ("1252" != value)
3009 {
3010 xSummaryInformation.SetAttributeValue("Codepage", value);
3011 }
3012 break;
3013 case 3:
3014 {
3015 var productName = this.RootElement.Attribute("Name")?.Value;
3016 if (value != productName)
3017 {
3018 xSummaryInformation.SetAttributeValue("Description", value);
3019 }
3020 break;
3021 }
3022 case 4:
3023 {
3024 var productManufacturer = this.RootElement.Attribute("Manufacturer")?.Value;
3025 if (value != productManufacturer)
3026 {
3027 xSummaryInformation.SetAttributeValue("Manufacturer", value);
3028 }
3029 break;
3030 }
3031 case 5:
3032 if ("Installer" != value)
3033 {
3034 xSummaryInformation.SetAttributeValue("Keywords", value);
3035 }
3036 break;
3037 case 7:
3038 var template = value.Split(';');
3039 if (0 < template.Length && 0 < template[template.Length - 1].Length)
3040 {
3041 this.RootElement.SetAttributeValue("Language", template[template.Length - 1]);
3042 }
3043 break;
3044 case 14:
3045 var installerVersion = row.FieldAsInteger(1);
3046 // Default InstallerVersion.
3047 if (installerVersion != 500)
3048 {
3049 this.RootElement.SetAttributeValue("InstallerVersion", installerVersion);
3050 }
3051 break;
3052 case 15:
3053 var wordCount = row.FieldAsInteger(1);
3054 if (0x1 == (wordCount & 0x1))
3055 {
3056 this.ShortNames = true;
3057 if (OutputType.Product == this.OutputType)
3058 {
3059 this.RootElement.SetAttributeValue("ShortNames", "yes");
3060 }
3061 }
3062
3063 if (0x2 == (wordCount & 0x2))
3064 {
3065 this.Compressed = true;
3066
3067 if (OutputType.Product == this.OutputType)
3068 {
3069 this.RootElement.SetAttributeValue("Compressed", "yes");
3070 }
3071 }
3072
3073 if (OutputType.Product == this.OutputType)
3074 {
3075 if (0x8 == (wordCount & 0x8))
3076 {
3077 this.RootElement.SetAttributeValue("Scope", "perUser");
3078 }
3079 else
3080 {
3081 var xAllUsers = this.RootElement.Elements(Names.PropertyElement).SingleOrDefault(p => p.Attribute("Id")?.Value == "ALLUSERS");
3082 if (xAllUsers?.Attribute("Value")?.Value == "1")
3083 {
3084 xAllUsers?.Remove();
3085 }
3086 }
3087 }
3088
3089 break;
3090 }
3091 }
3092 }
3093
3094 if (xSummaryInformation.HasAttributes)
3095 {
3096 this.RootElement.Add(xSummaryInformation);
3097 }
3098 }
3099 else
3100 {
3101 var xPatchInformation = new XElement(Names.PatchInformationElement);
3102
3103 foreach (var row in table.Rows)
3104 {
3105 var propertyId = row.FieldAsInteger(0);
3106 var value = row.FieldAsString(1);
3107
3108 if (!String.IsNullOrEmpty(value))
3109 {
3110 switch (propertyId)
3111 {
3112 case 1:
3113 if ("1252" != value)
3114 {
3115 xPatchInformation.SetAttributeValue("SummaryCodepage", value);
3116 }
3117 break;
3118 case 3:
3119 xPatchInformation.SetAttributeValue("Description", value);
3120 break;
3121 case 4:
3122 xPatchInformation.SetAttributeValue("Manufacturer", value);
3123 break;
3124 case 5:
3125 if ("Installer,Patching,PCP,Database" != value)
3126 {
3127 xPatchInformation.SetAttributeValue("Keywords", value);
3128 }
3129 break;
3130 case 6:
3131 xPatchInformation.SetAttributeValue("Comments", value);
3132 break;
3133 case 19:
3134 var security = Convert.ToInt32(value, CultureInfo.InvariantCulture);
3135 switch (security)
3136 {
3137 case 0:
3138 xPatchInformation.SetAttributeValue("ReadOnly", "no");
3139 break;
3140 case 4:
3141 xPatchInformation.SetAttributeValue("ReadOnly", "yes");
3142 break;
3143 }
3144 break;
3145 }
3146 }
3147 }
3148
3149 this.RootElement.Add(xPatchInformation);
3150 }
3151 }
3152
3153 /// <summary>
3154 /// Decompile the ActionText table.
3155 /// </summary>
3156 /// <param name="table">The table to decompile.</param>
3157 private void DecompileActionTextTable(Table table)
3158 {
3159 foreach (var row in table.Rows)
3160 {
3161 var progressText = new XElement(Names.ProgressTextElement,
3162 new XAttribute("Action", row.FieldAsString(0)),
3163 row.IsColumnNull(1) ? null : new XAttribute("Message", row.FieldAsString(1)),
3164 row.IsColumnNull(2) ? null : new XAttribute("Template", row.FieldAsString(2)));
3165
3166 this.UIElement.Add(progressText);
3167 }
3168 }
3169
3170 /// <summary>
3171 /// Decompile the AppId table.
3172 /// </summary>
3173 /// <param name="table">The table to decompile.</param>
3174 private void DecompileAppIdTable(Table table)
3175 {
3176 foreach (var row in table.Rows)
3177 {
3178 var appId = new XElement(Names.AppIdElement,
3179 new XAttribute("Advertise", "yes"),
3180 new XAttribute("Id", row.FieldAsString(0)),
3181 row.IsColumnNull(1) ? null : new XAttribute("RemoteServerName", row.FieldAsString(1)),
3182 row.IsColumnNull(2) ? null : new XAttribute("LocalService", row.FieldAsString(2)),
3183 row.IsColumnNull(3) ? null : new XAttribute("ServiceParameters", row.FieldAsString(3)),
3184 row.IsColumnNull(4) ? null : new XAttribute("DllSurrogate", row.FieldAsString(4)),
3185 row.IsColumnNull(5) || row.FieldAsInteger(5) != 1 ? null : new XAttribute("ActivateAtStorage", "yes"),
3186 row.IsColumnNull(6) || row.FieldAsInteger(6) != 1 ? null : new XAttribute("RunAsInteractiveUser", "yes"));
3187
3188 this.RootElement.Add(appId);
3189 this.IndexElement(row, appId);
3190 }
3191 }
3192
3193 /// <summary>
3194 /// Decompile the BBControl table.
3195 /// </summary>
3196 /// <param name="table">The table to decompile.</param>
3197 private void DecompileBBControlTable(Table table)
3198 {
3199 foreach (BBControlRow bbControlRow in table.Rows)
3200 {
3201 var xControl = new XElement(Names.ControlElement,
3202 new XAttribute("Id", bbControlRow.BBControl),
3203 new XAttribute("Type", bbControlRow.Type),
3204 new XAttribute("X", bbControlRow.X),
3205 new XAttribute("Y", bbControlRow.Y),
3206 new XAttribute("Width", bbControlRow.Width),
3207 new XAttribute("Height", bbControlRow.Height),
3208 null == bbControlRow.Text ? null : new XAttribute("Text", bbControlRow.Text));
3209
3210 if (null != bbControlRow[7])
3211 {
3212 SetControlAttributes(bbControlRow.Attributes, xControl);
3213 }
3214
3215 if (this.TryGetIndexedElement("Billboard", out var xBillboard, bbControlRow.Billboard))
3216 {
3217 xBillboard.Add(xControl);
3218 }
3219 else
3220 {
3221 this.Messaging.Write(WarningMessages.ExpectedForeignRow(bbControlRow.SourceLineNumbers, table.Name, bbControlRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Billboard_", bbControlRow.Billboard, "Billboard"));
3222 }
3223 }
3224 }
3225
3226 /// <summary>
3227 /// Decompile the Billboard table.
3228 /// </summary>
3229 /// <param name="table">The table to decompile.</param>
3230 private void DecompileBillboardTable(Table table)
3231 {
3232 var billboards = new SortedList<string, Row>();
3233
3234 foreach (var row in table.Rows)
3235 {
3236 var xBillboard = new XElement(Names.BillboardElement,
3237 new XAttribute("Id", row.FieldAsString(0)),
3238 new XAttribute("Feature", row.FieldAsString(1)));
3239
3240 this.IndexElement(row, xBillboard);
3241 billboards.Add(String.Format(CultureInfo.InvariantCulture, "{0}|{1:0000000000}", row[0], row[3]), row);
3242 }
3243
3244 var billboardActions = new Dictionary<string, XElement>();
3245
3246 foreach (var row in billboards.Values)
3247 {
3248 var xBillboard = this.GetIndexedElement(row);
3249
3250 if (!billboardActions.TryGetValue(row.FieldAsString(2), out var xBillboardAction))
3251 {
3252 xBillboardAction = new XElement(Names.BillboardActionElement,
3253 new XAttribute("Id", row.FieldAsString(2)));
3254
3255 this.UIElement.Add(xBillboardAction);
3256 billboardActions.Add(row.FieldAsString(2), xBillboardAction);
3257 }
3258
3259 xBillboardAction.Add(xBillboard);
3260 }
3261 }
3262
3263 /// <summary>
3264 /// Decompile the Binary table.
3265 /// </summary>
3266 /// <param name="table">The table to decompile.</param>
3267 private void DecompileBinaryTable(Table table)
3268 {
3269 foreach (var row in table.Rows)
3270 {
3271 var xBinary = new XElement(Names.BinaryElement,
3272 new XAttribute("Id", row.FieldAsString(0)),
3273 new XAttribute("SourceFile", row.FieldAsString(1)));
3274
3275 this.RootElement.Add(xBinary);
3276 }
3277 }
3278
3279 /// <summary>
3280 /// Decompile the BindImage table.
3281 /// </summary>
3282 /// <param name="table">The table to decompile.</param>
3283 private void DecompileBindImageTable(Table table)
3284 {
3285 foreach (var row in table.Rows)
3286 {
3287 if (this.TryGetIndexedElement("File", out var xFile, row.FieldAsString(0)))
3288 {
3289 xFile.SetAttributeValue("BindPath", row.FieldAsString(1));
3290 }
3291 else
3292 {
3293 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "File_", row.FieldAsString(0), "File"));
3294 }
3295 }
3296 }
3297
3298 /// <summary>
3299 /// Decompile the Class table.
3300 /// </summary>
3301 /// <param name="table">The table to decompile.</param>
3302 private void DecompileClassTable(Table table)
3303 {
3304 foreach (var row in table.Rows)
3305 {
3306 var xClass = new XElement(Names.ClassElement,
3307 new XAttribute("Id", row.FieldAsString(0)),
3308 new XAttribute("Advertise", "yes"),
3309 new XAttribute("Context", row.FieldAsString(1)),
3310 row.IsColumnNull(4) ? null : new XAttribute("Description", row.FieldAsString(4)),
3311 row.IsColumnNull(5) ? null : new XAttribute("AppId", row.FieldAsString(5)),
3312 row.IsColumnNull(7) ? null : new XAttribute("Icon", row.FieldAsString(7)),
3313 row.IsColumnNull(8) ? null : new XAttribute("IconIndex", row.FieldAsString(8)),
3314 row.IsColumnNull(9) ? null : new XAttribute("Handler", row.FieldAsString(9)),
3315 row.IsColumnNull(10) ? null : new XAttribute("Argument", row.FieldAsString(10)));
3316
3317 if (!row.IsColumnNull(6))
3318 {
3319 var fileTypeMaskStrings = row.FieldAsString(6).Split(';');
3320
3321 try
3322 {
3323 foreach (var fileTypeMaskString in fileTypeMaskStrings)
3324 {
3325 var fileTypeMaskParts = fileTypeMaskString.Split(',');
3326
3327 if (4 == fileTypeMaskParts.Length)
3328 {
3329 var xFileTypeMask = new XElement(Names.FileTypeMaskElement,
3330 new XAttribute("Offset", Convert.ToInt32(fileTypeMaskParts[0], CultureInfo.InvariantCulture)),
3331 new XAttribute("Mask", fileTypeMaskParts[2]),
3332 new XAttribute("Value", fileTypeMaskParts[3]));
3333
3334 xClass.Add(xFileTypeMask);
3335 }
3336 else
3337 {
3338 // TODO: warn
3339 }
3340 }
3341 }
3342 catch (FormatException)
3343 {
3344 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6]));
3345 }
3346 catch (OverflowException)
3347 {
3348 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6]));
3349 }
3350 }
3351
3352 if (!row.IsColumnNull(12))
3353 {
3354 if (1 == row.FieldAsInteger(12))
3355 {
3356 xClass.SetAttributeValue("RelativePath", "yes");
3357 }
3358 else
3359 {
3360 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[12].Column.Name, row[12]));
3361 }
3362 }
3363
3364 this.AddChildToParent("Component", xClass, row, 2);
3365 this.IndexElement(row, xClass);
3366 }
3367 }
3368
3369 /// <summary>
3370 /// Decompile the ComboBox table.
3371 /// </summary>
3372 /// <param name="table">The table to decompile.</param>
3373 private void DecompileComboBoxTable(Table table)
3374 {
3375 // sort the combo boxes by their property and order
3376 var comboBoxRows = table.Rows.Select(row => row).OrderBy(row => String.Format("{0}|{1:0000000000}", row.FieldAsString(0), row.FieldAsInteger(1)));
3377
3378 XElement xComboBox = null;
3379 string property = null;
3380 foreach (var row in comboBoxRows)
3381 {
3382 if (null == xComboBox || row.FieldAsString(0) != property)
3383 {
3384 property = row.FieldAsString(0);
3385
3386 xComboBox = new XElement(Names.ComboBoxElement,
3387 new XAttribute("Property", property));
3388
3389 this.UIElement.Add(xComboBox);
3390 }
3391
3392 var xListItem = new XElement(Names.ListItemElement,
3393 new XAttribute("Value", row.FieldAsString(2)),
3394 row.IsColumnNull(3) ? null : new XAttribute("Text", row.FieldAsString(3)));
3395 xComboBox.Add(xListItem);
3396 }
3397 }
3398
3399 /// <summary>
3400 /// Decompile the Control table.
3401 /// </summary>
3402 /// <param name="table">The table to decompile.</param>
3403 private void DecompileControlTable(Table table)
3404 {
3405 foreach (ControlRow controlRow in table.Rows)
3406 {
3407 var xControl = new XElement(Names.ControlElement,
3408 new XAttribute("Id", controlRow.Control),
3409 new XAttribute("Type", controlRow.Type),
3410 new XAttribute("X", controlRow.X),
3411 new XAttribute("Y", controlRow.Y),
3412 new XAttribute("Width", controlRow.Width),
3413 new XAttribute("Height", controlRow.Height),
3414 new XAttribute("Text", controlRow.Text));
3415
3416 if (!controlRow.IsColumnNull(7))
3417 {
3418 string[] specialAttributes;
3419
3420 // sets various common attributes like Disabled, Indirect, Integer, ...
3421 SetControlAttributes(controlRow.Attributes, xControl);
3422
3423 switch (controlRow.Type)
3424 {
3425 case "Bitmap":
3426 specialAttributes = BitmapControlAttributes;
3427 break;
3428 case "CheckBox":
3429 specialAttributes = CheckboxControlAttributes;
3430 break;
3431 case "ComboBox":
3432 specialAttributes = ComboboxControlAttributes;
3433 break;
3434 case "DirectoryCombo":
3435 specialAttributes = VolumeControlAttributes;
3436 break;
3437 case "Edit":
3438 specialAttributes = EditControlAttributes;
3439 break;
3440 case "Icon":
3441 specialAttributes = IconControlAttributes;
3442 break;
3443 case "ListBox":
3444 specialAttributes = ListboxControlAttributes;
3445 break;
3446 case "ListView":
3447 specialAttributes = ListviewControlAttributes;
3448 break;
3449 case "MaskedEdit":
3450 specialAttributes = EditControlAttributes;
3451 break;
3452 case "PathEdit":
3453 specialAttributes = EditControlAttributes;
3454 break;
3455 case "ProgressBar":
3456 specialAttributes = ProgressControlAttributes;
3457 break;
3458 case "PushButton":
3459 specialAttributes = ButtonControlAttributes;
3460 break;
3461 case "RadioButtonGroup":
3462 specialAttributes = RadioControlAttributes;
3463 break;
3464 case "Text":
3465 specialAttributes = TextControlAttributes;
3466 break;
3467 case "VolumeCostList":
3468 specialAttributes = VolumeControlAttributes;
3469 break;
3470 case "VolumeSelectCombo":
3471 specialAttributes = VolumeControlAttributes;
3472 break;
3473 default:
3474 specialAttributes = null;
3475 break;
3476 }
3477
3478 if (null != specialAttributes)
3479 {
3480 var iconSizeSet = false;
3481
3482 for (var i = 16; 32 > i; i++)
3483 {
3484 if (1 == ((controlRow.Attributes >> i) & 1))
3485 {
3486 string attribute = null;
3487
3488 if (specialAttributes.Length > (i - 16))
3489 {
3490 attribute = specialAttributes[i - 16];
3491 }
3492
3493 // unknown attribute
3494 if (null == attribute)
3495 {
3496 this.Messaging.Write(WarningMessages.IllegalColumnValue(controlRow.SourceLineNumbers, table.Name, controlRow.Fields[7].Column.Name, controlRow.Attributes));
3497 continue;
3498 }
3499
3500 switch (attribute)
3501 {
3502 case "Bitmap":
3503 xControl.SetAttributeValue("Bitmap", "yes");
3504 break;
3505 case "CDROM":
3506 xControl.SetAttributeValue("CDROM", "yes");
3507 break;
3508 case "ComboList":
3509 xControl.SetAttributeValue("ComboList", "yes");
3510 break;
3511 case "ElevationShield":
3512 xControl.SetAttributeValue("ElevationShield", "yes");
3513 break;
3514 case "Fixed":
3515 xControl.SetAttributeValue("Fixed", "yes");
3516 break;
3517 case "FixedSize":
3518 xControl.SetAttributeValue("FixedSize", "yes");
3519 break;
3520 case "Floppy":
3521 xControl.SetAttributeValue("Floppy", "yes");
3522 break;
3523 case "FormatSize":
3524 xControl.SetAttributeValue("FormatSize", "yes");
3525 break;
3526 case "HasBorder":
3527 xControl.SetAttributeValue("HasBorder", "yes");
3528 break;
3529 case "Icon":
3530 xControl.SetAttributeValue("Icon", "yes");
3531 break;
3532 case "Icon16":
3533 if (iconSizeSet)
3534 {
3535 xControl.SetAttributeValue("IconSize", "48");
3536 }
3537 else
3538 {
3539 iconSizeSet = true;
3540 xControl.SetAttributeValue("IconSize", "16");
3541 }
3542 break;
3543 case "Icon32":
3544 if (iconSizeSet)
3545 {
3546 xControl.SetAttributeValue("IconSize", "48");
3547 }
3548 else
3549 {
3550 iconSizeSet = true;
3551 xControl.SetAttributeValue("IconSize", "32");
3552 }
3553 break;
3554 case "Image":
3555 xControl.SetAttributeValue("Image", "yes");
3556 break;
3557 case "Multiline":
3558 xControl.SetAttributeValue("Multiline", "yes");
3559 break;
3560 case "NoPrefix":
3561 xControl.SetAttributeValue("NoPrefix", "yes");
3562 break;
3563 case "NoWrap":
3564 xControl.SetAttributeValue("NoWrap", "yes");
3565 break;
3566 case "Password":
3567 xControl.SetAttributeValue("Password", "yes");
3568 break;
3569 case "ProgressBlocks":
3570 xControl.SetAttributeValue("ProgressBlocks", "yes");
3571 break;
3572 case "PushLike":
3573 xControl.SetAttributeValue("PushLike", "yes");
3574 break;
3575 case "RAMDisk":
3576 xControl.SetAttributeValue("RAMDisk", "yes");
3577 break;
3578 case "Remote":
3579 xControl.SetAttributeValue("Remote", "yes");
3580 break;
3581 case "Removable":
3582 xControl.SetAttributeValue("Removable", "yes");
3583 break;
3584 case "ShowRollbackCost":
3585 xControl.SetAttributeValue("ShowRollbackCost", "yes");
3586 break;
3587 case "Sorted":
3588 xControl.SetAttributeValue("Sorted", "yes");
3589 break;
3590 case "Transparent":
3591 xControl.SetAttributeValue("Transparent", "yes");
3592 break;
3593 case "UserLanguage":
3594 xControl.SetAttributeValue("UserLanguage", "yes");
3595 break;
3596 default:
3597 throw new InvalidOperationException($"Unknown control attribute: '{attribute}'.");
3598 }
3599 }
3600 }
3601 }
3602 else if (0 < (controlRow.Attributes & 0xFFFF0000))
3603 {
3604 this.Messaging.Write(WarningMessages.IllegalColumnValue(controlRow.SourceLineNumbers, table.Name, controlRow.Fields[7].Column.Name, controlRow.Attributes));
3605 }
3606 }
3607
3608 // FinalizeCheckBoxTable adds Control/@Property|@CheckBoxPropertyRef
3609 if (null != controlRow.Property && 0 != String.CompareOrdinal("CheckBox", controlRow.Type))
3610 {
3611 xControl.SetAttributeValue("Property", controlRow.Property);
3612 }
3613
3614 if (null != controlRow.Help)
3615 {
3616 var help = controlRow.Help.Split('|');
3617
3618 if (2 == help.Length)
3619 {
3620 if (0 < help[0].Length)
3621 {
3622 xControl.SetAttributeValue("ToolTip", help[0]);
3623 }
3624
3625 if (0 < help[1].Length)
3626 {
3627 xControl.SetAttributeValue("Help", help[1]);
3628 }
3629 }
3630 }
3631
3632 this.IndexElement(controlRow, xControl);
3633 }
3634 }
3635
3636 /// <summary>
3637 /// Decompile the ControlCondition table.
3638 /// </summary>
3639 /// <param name="table">The table to decompile.</param>
3640 private void DecompileControlConditionTable(Table table)
3641 {
3642 foreach (var row in table.Rows)
3643 {
3644 if (this.TryGetIndexedElement("Control", out var xControl, row.FieldAsString(0), row.FieldAsString(1)))
3645 {
3646 switch (row.FieldAsString(2))
3647 {
3648 case "Default":
3649 xControl.SetAttributeValue("DefaultCondition", row.FieldAsString(3));
3650 break;
3651 case "Disable":
3652 xControl.SetAttributeValue("DisableCondition", row.FieldAsString(3));
3653 break;
3654 case "Enable":
3655 xControl.SetAttributeValue("EnableCondition", row.FieldAsString(3));
3656 break;
3657 case "Hide":
3658 xControl.SetAttributeValue("HideCondition", row.FieldAsString(3));
3659 break;
3660 case "Show":
3661 xControl.SetAttributeValue("ShowCondition", row.FieldAsString(3));
3662 break;
3663 default:
3664 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[2].Column.Name, row[2]));
3665 break;
3666 }
3667 }
3668 else
3669 {
3670 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", row.FieldAsString(0), "Control_", row.FieldAsString(1), "Control"));
3671 }
3672 }
3673 }
3674
3675 /// <summary>
3676 /// Decompile the ControlEvent table.
3677 /// </summary>
3678 /// <param name="table">The table to decompile.</param>
3679 private void DecompileControlEventTable(Table table)
3680 {
3681 var controlEvents = new SortedList<string, Row>();
3682
3683 foreach (var row in table.Rows)
3684 {
3685 var xPublish = new XElement(Names.PublishElement,
3686 new XAttribute("Condition", row.FieldAsString(4)));
3687
3688 var publishEvent = row.FieldAsString(2);
3689 if (publishEvent.StartsWith("[", StringComparison.Ordinal) && publishEvent.EndsWith("]", StringComparison.Ordinal))
3690 {
3691 xPublish.SetAttributeValue("Property", publishEvent.Substring(1, publishEvent.Length - 2));
3692
3693 if ("{}" != row.FieldAsString(3))
3694 {
3695 xPublish.SetAttributeValue("Value", row.FieldAsString(3));
3696 }
3697 }
3698 else
3699 {
3700 xPublish.SetAttributeValue("Event", publishEvent);
3701 xPublish.SetAttributeValue("Value", row.FieldAsString(3));
3702 }
3703
3704 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);
3705
3706 this.IndexElement(row, xPublish);
3707 }
3708
3709 foreach (var row in controlEvents.Values)
3710 {
3711 if (this.TryGetIndexedElement("Control", out var xControl, row.FieldAsString(0), row.FieldAsString(1)))
3712 {
3713 var xPublish = this.GetIndexedElement(row);
3714 xControl.Add(xPublish);
3715 }
3716 else
3717 {
3718 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", row.FieldAsString(0), "Control_", row.FieldAsString(1), "Control"));
3719 }
3720 }
3721 }
3722
3723 /// <summary>
3724 /// Decompile a custom table.
3725 /// </summary>
3726 /// <param name="table">The table to decompile.</param>
3727 private void DecompileCustomTable(Table table)
3728 {
3729 if (0 < table.Rows.Count || this.SuppressDroppingEmptyTables)
3730 {
3731 this.Messaging.Write(WarningMessages.DecompilingAsCustomTable(table.Rows[0].SourceLineNumbers, table.Name));
3732
3733 var xCustomTable = new XElement(Names.CustomTableElement,
3734 new XAttribute("Id", table.Name));
3735
3736 foreach (var columnDefinition in table.Definition.Columns)
3737 {
3738 var xColumn = new XElement(Names.ColumnElement,
3739 new XAttribute("Id", columnDefinition.Name),
3740 columnDefinition.Description == null ? null : new XAttribute("Description", columnDefinition.Description),
3741 columnDefinition.KeyTable == null ? null : new XAttribute("KeyTable", columnDefinition.KeyTable),
3742 !columnDefinition.KeyColumn.HasValue ? null : new XAttribute("KeyColumn", columnDefinition.KeyColumn.Value),
3743 !columnDefinition.IsLocalizable ? null : new XAttribute("Localizable", "yes"),
3744 !columnDefinition.MaxValue.HasValue ? null : new XAttribute("MaxValue", columnDefinition.MaxValue.Value),
3745 !columnDefinition.MinValue.HasValue ? null : new XAttribute("MinValue", columnDefinition.MinValue.Value),
3746 !columnDefinition.Nullable ? null : new XAttribute("Nullable", "yes"),
3747 !columnDefinition.PrimaryKey ? null : new XAttribute("PrimaryKey", "yes"),
3748 columnDefinition.Possibilities == null ? null : new XAttribute("Possibilities", "yes"),
3749 new XAttribute("Width", columnDefinition.Length));
3750
3751 if (ColumnCategory.Unknown != columnDefinition.Category)
3752 {
3753 switch (columnDefinition.Category)
3754 {
3755 case ColumnCategory.Text:
3756 xColumn.SetAttributeValue("Category", "text");
3757 break;
3758 case ColumnCategory.UpperCase:
3759 xColumn.SetAttributeValue("Category", "upperCase");
3760 break;
3761 case ColumnCategory.LowerCase:
3762 xColumn.SetAttributeValue("Category", "lowerCase");
3763 break;
3764 case ColumnCategory.Integer:
3765 xColumn.SetAttributeValue("Category", "integer");
3766 break;
3767 case ColumnCategory.DoubleInteger:
3768 xColumn.SetAttributeValue("Category", "doubleInteger");
3769 break;
3770 case ColumnCategory.TimeDate:
3771 xColumn.SetAttributeValue("Category", "timeDate");
3772 break;
3773 case ColumnCategory.Identifier:
3774 xColumn.SetAttributeValue("Category", "identifier");
3775 break;
3776 case ColumnCategory.Property:
3777 xColumn.SetAttributeValue("Category", "property");
3778 break;
3779 case ColumnCategory.Filename:
3780 xColumn.SetAttributeValue("Category", "filename");
3781 break;
3782 case ColumnCategory.WildCardFilename:
3783 xColumn.SetAttributeValue("Category", "wildCardFilename");
3784 break;
3785 case ColumnCategory.Path:
3786 xColumn.SetAttributeValue("Category", "path");
3787 break;
3788 case ColumnCategory.Paths:
3789 xColumn.SetAttributeValue("Category", "paths");
3790 break;
3791 case ColumnCategory.AnyPath:
3792 xColumn.SetAttributeValue("Category", "anyPath");
3793 break;
3794 case ColumnCategory.DefaultDir:
3795 xColumn.SetAttributeValue("Category", "defaultDir");
3796 break;
3797 case ColumnCategory.RegPath:
3798 xColumn.SetAttributeValue("Category", "regPath");
3799 break;
3800 case ColumnCategory.Formatted:
3801 xColumn.SetAttributeValue("Category", "formatted");
3802 break;
3803 case ColumnCategory.FormattedSDDLText:
3804 xColumn.SetAttributeValue("Category", "formattedSddl");
3805 break;
3806 case ColumnCategory.Template:
3807 xColumn.SetAttributeValue("Category", "template");
3808 break;
3809 case ColumnCategory.Condition:
3810 xColumn.SetAttributeValue("Category", "condition");
3811 break;
3812 case ColumnCategory.Guid:
3813 xColumn.SetAttributeValue("Category", "guid");
3814 break;
3815 case ColumnCategory.Version:
3816 xColumn.SetAttributeValue("Category", "version");
3817 break;
3818 case ColumnCategory.Language:
3819 xColumn.SetAttributeValue("Category", "language");
3820 break;
3821 case ColumnCategory.Binary:
3822 xColumn.SetAttributeValue("Category", "binary");
3823 break;
3824 case ColumnCategory.CustomSource:
3825 xColumn.SetAttributeValue("Category", "customSource");
3826 break;
3827 case ColumnCategory.Cabinet:
3828 xColumn.SetAttributeValue("Category", "cabinet");
3829 break;
3830 case ColumnCategory.Shortcut:
3831 xColumn.SetAttributeValue("Category", "shortcut");
3832 break;
3833 default:
3834 throw new InvalidOperationException($"Unknown custom column category '{columnDefinition.Category.ToString()}'.");
3835 }
3836 }
3837
3838 if (ColumnModularizeType.None != columnDefinition.ModularizeType)
3839 {
3840 switch (columnDefinition.ModularizeType)
3841 {
3842 case ColumnModularizeType.Column:
3843 xColumn.SetAttributeValue("Modularize", "Column");
3844 break;
3845 case ColumnModularizeType.Condition:
3846 xColumn.SetAttributeValue("Modularize", "Condition");
3847 break;
3848 case ColumnModularizeType.Icon:
3849 xColumn.SetAttributeValue("Modularize", "Icon");
3850 break;
3851 case ColumnModularizeType.Property:
3852 xColumn.SetAttributeValue("Modularize", "Property");
3853 break;
3854 case ColumnModularizeType.SemicolonDelimited:
3855 xColumn.SetAttributeValue("Modularize", "SemicolonDelimited");
3856 break;
3857 default:
3858 throw new InvalidOperationException($"Unknown custom column modularization type '{columnDefinition.ModularizeType.ToString()}'.");
3859 }
3860 }
3861
3862 if (ColumnType.Unknown != columnDefinition.Type)
3863 {
3864 switch (columnDefinition.Type)
3865 {
3866 case ColumnType.Localized:
3867 xColumn.SetAttributeValue("Localizable", "yes");
3868 xColumn.SetAttributeValue("Type", "string");
3869 break;
3870 case ColumnType.Number:
3871 xColumn.SetAttributeValue("Type", "int");
3872 break;
3873 case ColumnType.Object:
3874 xColumn.SetAttributeValue("Type", "binary");
3875 break;
3876 case ColumnType.Preserved:
3877 case ColumnType.String:
3878 xColumn.SetAttributeValue("Type", "string");
3879 break;
3880 default:
3881 throw new InvalidOperationException($"Unknown custom column type '{columnDefinition.Type}'.");
3882 }
3883 }
3884
3885 xCustomTable.Add(xColumn);
3886 }
3887
3888 foreach (var row in table.Rows)
3889 {
3890 var xRow = new XElement(Names.RowElement);
3891
3892 foreach (var field in row.Fields.Where(f => f.Data != null))
3893 {
3894 var xData = new XElement(Names.DataElement,
3895 new XAttribute("Column", field.Column.Name),
3896 new XAttribute("Value", field.AsString()));
3897
3898 xRow.Add(xData);
3899 }
3900
3901 xCustomTable.Add(xRow);
3902 }
3903
3904 this.RootElement.Add(xCustomTable);
3905 }
3906 }
3907
3908 /// <summary>
3909 /// Decompile the CreateFolder table.
3910 /// </summary>
3911 /// <param name="table">The table to decompile.</param>
3912 private void DecompileCreateFolderTable(Table table)
3913 {
3914 foreach (var row in table.Rows)
3915 {
3916 var xCreateFolder = new XElement(Names.CreateFolderElement,
3917 new XAttribute("Directory", row.FieldAsString(0)));
3918
3919 this.AddChildToParent("Component", xCreateFolder, row, 1);
3920 this.IndexElement(row, xCreateFolder);
3921 }
3922 }
3923
3924 /// <summary>
3925 /// Decompile the CustomAction table.
3926 /// </summary>
3927 /// <param name="table">The table to decompile.</param>
3928 private void DecompileCustomActionTable(Table table)
3929 {
3930 foreach (var row in table.Rows)
3931 {
3932 var xCustomAction = new XElement(Names.CustomActionElement,
3933 new XAttribute("Id", row.FieldAsString(0)));
3934
3935 var type = row.FieldAsInteger(1);
3936
3937 if (WindowsInstallerConstants.MsidbCustomActionTypeHideTarget == (type & WindowsInstallerConstants.MsidbCustomActionTypeHideTarget))
3938 {
3939 xCustomAction.SetAttributeValue("HideTarget", "yes");
3940 }
3941
3942 if (WindowsInstallerConstants.MsidbCustomActionTypeNoImpersonate == (type & WindowsInstallerConstants.MsidbCustomActionTypeNoImpersonate))
3943 {
3944 xCustomAction.SetAttributeValue("Impersonate", "no");
3945 }
3946
3947 if (WindowsInstallerConstants.MsidbCustomActionTypeTSAware == (type & WindowsInstallerConstants.MsidbCustomActionTypeTSAware))
3948 {
3949 xCustomAction.SetAttributeValue("TerminalServerAware", "yes");
3950 }
3951
3952 if (WindowsInstallerConstants.MsidbCustomActionType64BitScript == (type & WindowsInstallerConstants.MsidbCustomActionType64BitScript))
3953 {
3954 xCustomAction.SetAttributeValue("Bitness", "always64");
3955 }
3956 else if (WindowsInstallerConstants.MsidbCustomActionTypeVBScript == (type & WindowsInstallerConstants.MsidbCustomActionTypeVBScript) ||
3957 WindowsInstallerConstants.MsidbCustomActionTypeJScript == (type & WindowsInstallerConstants.MsidbCustomActionTypeJScript))
3958 {
3959 xCustomAction.SetAttributeValue("Bitness", "always32");
3960 }
3961
3962 switch (type & WindowsInstallerConstants.MsidbCustomActionTypeExecuteBits)
3963 {
3964 case 0:
3965 // this is the default value
3966 break;
3967 case WindowsInstallerConstants.MsidbCustomActionTypeFirstSequence:
3968 xCustomAction.SetAttributeValue("Execute", "firstSequence");
3969 break;
3970 case WindowsInstallerConstants.MsidbCustomActionTypeOncePerProcess:
3971 xCustomAction.SetAttributeValue("Execute", "oncePerProcess");
3972 break;
3973 case WindowsInstallerConstants.MsidbCustomActionTypeClientRepeat:
3974 xCustomAction.SetAttributeValue("Execute", "secondSequence");
3975 break;
3976 case WindowsInstallerConstants.MsidbCustomActionTypeInScript:
3977 xCustomAction.SetAttributeValue("Execute", "deferred");
3978 break;
3979 case WindowsInstallerConstants.MsidbCustomActionTypeInScript + WindowsInstallerConstants.MsidbCustomActionTypeRollback:
3980 xCustomAction.SetAttributeValue("Execute", "rollback");
3981 break;
3982 case WindowsInstallerConstants.MsidbCustomActionTypeInScript + WindowsInstallerConstants.MsidbCustomActionTypeCommit:
3983 xCustomAction.SetAttributeValue("Execute", "commit");
3984 break;
3985 default:
3986 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1]));
3987 break;
3988 }
3989
3990 switch (type & WindowsInstallerConstants.MsidbCustomActionTypeReturnBits)
3991 {
3992 case 0:
3993 // this is the default value
3994 break;
3995 case WindowsInstallerConstants.MsidbCustomActionTypeContinue:
3996 xCustomAction.SetAttributeValue("Return", "ignore");
3997 break;
3998 case WindowsInstallerConstants.MsidbCustomActionTypeAsync:
3999 xCustomAction.SetAttributeValue("Return", "asyncWait");
4000 break;
4001 case WindowsInstallerConstants.MsidbCustomActionTypeAsync + WindowsInstallerConstants.MsidbCustomActionTypeContinue:
4002 xCustomAction.SetAttributeValue("Return", "asyncNoWait");
4003 break;
4004 default:
4005 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1]));
4006 break;
4007 }
4008
4009 var source = type & WindowsInstallerConstants.MsidbCustomActionTypeSourceBits;
4010 switch (source)
4011 {
4012 case WindowsInstallerConstants.MsidbCustomActionTypeBinaryData:
4013 xCustomAction.SetAttributeValue("BinaryRef", row.FieldAsString(2));
4014 break;
4015 case WindowsInstallerConstants.MsidbCustomActionTypeSourceFile:
4016 if (!row.IsColumnNull(2))
4017 {
4018 xCustomAction.SetAttributeValue("FileRef", row.FieldAsString(2));
4019 }
4020 break;
4021 case WindowsInstallerConstants.MsidbCustomActionTypeDirectory:
4022 if (!row.IsColumnNull(2))
4023 {
4024 xCustomAction.SetAttributeValue("Directory", row.FieldAsString(2));
4025 }
4026 break;
4027 case WindowsInstallerConstants.MsidbCustomActionTypeProperty:
4028 xCustomAction.SetAttributeValue("Property", row.FieldAsString(2));
4029 break;
4030 default:
4031 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1]));
4032 break;
4033 }
4034
4035 switch (type & WindowsInstallerConstants.MsidbCustomActionTypeTargetBits)
4036 {
4037 case WindowsInstallerConstants.MsidbCustomActionTypeDll:
4038 xCustomAction.SetAttributeValue("DllEntry", row.FieldAsString(3));
4039 break;
4040 case WindowsInstallerConstants.MsidbCustomActionTypeExe:
4041 xCustomAction.SetAttributeValue("ExeCommand", row.FieldAsString(3));
4042 break;
4043 case WindowsInstallerConstants.MsidbCustomActionTypeTextData:
4044 if (WindowsInstallerConstants.MsidbCustomActionTypeSourceFile == source)
4045 {
4046 xCustomAction.SetAttributeValue("Error", row.FieldAsString(3));
4047 }
4048 else
4049 {
4050 xCustomAction.SetAttributeValue("Value", row.FieldAsString(3));
4051 }
4052 break;
4053 case WindowsInstallerConstants.MsidbCustomActionTypeJScript:
4054 if (WindowsInstallerConstants.MsidbCustomActionTypeDirectory == source)
4055 {
4056 xCustomAction.SetAttributeValue("Script", "jscript");
4057 // TODO: Extract to @ScriptFile?
4058 // xCustomAction.Content = row.FieldAsString(3);
4059 }
4060 else
4061 {
4062 xCustomAction.SetAttributeValue("JScriptCall", row.FieldAsString(3));
4063 }
4064 break;
4065 case WindowsInstallerConstants.MsidbCustomActionTypeVBScript:
4066 if (WindowsInstallerConstants.MsidbCustomActionTypeDirectory == source)
4067 {
4068 xCustomAction.SetAttributeValue("Script", "vbscript");
4069 // TODO: Extract to @ScriptFile?
4070 // xCustomAction.Content = row.FieldAsString(3);
4071 }
4072 else
4073 {
4074 xCustomAction.SetAttributeValue("VBScriptCall", row.FieldAsString(3));
4075 }
4076 break;
4077 case WindowsInstallerConstants.MsidbCustomActionTypeInstall:
4078 this.Messaging.Write(WarningMessages.NestedInstall(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1]));
4079 continue;
4080 default:
4081 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1]));
4082 break;
4083 }
4084
4085 var extype = 4 < row.Fields.Length && !row.IsColumnNull(4) ? row.FieldAsInteger(4) : 0;
4086 if (WindowsInstallerConstants.MsidbCustomActionTypePatchUninstall == (extype & WindowsInstallerConstants.MsidbCustomActionTypePatchUninstall))
4087 {
4088 xCustomAction.SetAttributeValue("PatchUninstall", "yes");
4089 }
4090
4091 this.RootElement.Add(xCustomAction);
4092 this.IndexElement(row, xCustomAction);
4093 }
4094 }
4095
4096 /// <summary>
4097 /// Decompile the CompLocator table.
4098 /// </summary>
4099 /// <param name="table">The table to decompile.</param>
4100 private void DecompileCompLocatorTable(Table table)
4101 {
4102 foreach (var row in table.Rows)
4103 {
4104 var xComponentSearch = new XElement(Names.ComponentSearchElement,
4105 new XAttribute("Id", row.FieldAsString(0)),
4106 new XAttribute("Guid", row.FieldAsString(1)));
4107
4108 if (!row.IsColumnNull(2))
4109 {
4110 switch (row.FieldAsInteger(2))
4111 {
4112 case WindowsInstallerConstants.MsidbLocatorTypeDirectory:
4113 xComponentSearch.SetAttributeValue("Type", "directory");
4114 break;
4115 case WindowsInstallerConstants.MsidbLocatorTypeFileName:
4116 // this is the default value
4117 break;
4118 default:
4119 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[2].Column.Name, row[2]));
4120 break;
4121 }
4122 }
4123
4124 this.IndexElement(row, xComponentSearch);
4125 }
4126 }
4127
4128 /// <summary>
4129 /// Decompile the Complus table.
4130 /// </summary>
4131 /// <param name="table">The table to decompile.</param>
4132 private void DecompileComplusTable(Table table)
4133 {
4134 foreach (var row in table.Rows)
4135 {
4136 if (!row.IsColumnNull(1))
4137 {
4138 if (this.TryGetIndexedElement("Component", out var xComponent, row.FieldAsString(0)))
4139 {
4140 xComponent.SetAttributeValue("ComPlusFlags", row.FieldAsInteger(1));
4141 }
4142 else
4143 {
4144 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(0), "Component"));
4145 }
4146 }
4147 }
4148 }
4149
4150 /// <summary>
4151 /// Decompile the Component table.
4152 /// </summary>
4153 /// <param name="table">The table to decompile.</param>
4154 private void DecompileComponentTable(Table table)
4155 {
4156 foreach (var row in table.Rows)
4157 {
4158 var xComponent = new XElement(Names.ComponentElement,
4159 new XAttribute("Id", row.FieldAsString(0)),
4160 new XAttribute("Guid", row.FieldAsString(1) ?? String.Empty));
4161
4162 var attributes = row.FieldAsInteger(3);
4163
4164 if (WindowsInstallerConstants.MsidbComponentAttributesSourceOnly == (attributes & WindowsInstallerConstants.MsidbComponentAttributesSourceOnly))
4165 {
4166 xComponent.SetAttributeValue("Location", "source");
4167 }
4168 else if (WindowsInstallerConstants.MsidbComponentAttributesOptional == (attributes & WindowsInstallerConstants.MsidbComponentAttributesOptional))
4169 {
4170 xComponent.SetAttributeValue("Location", "either");
4171 }
4172
4173 if (WindowsInstallerConstants.MsidbComponentAttributesSharedDllRefCount == (attributes & WindowsInstallerConstants.MsidbComponentAttributesSharedDllRefCount))
4174 {
4175 xComponent.SetAttributeValue("SharedDllRefCount", "yes");
4176 }
4177
4178 if (WindowsInstallerConstants.MsidbComponentAttributesPermanent == (attributes & WindowsInstallerConstants.MsidbComponentAttributesPermanent))
4179 {
4180 xComponent.SetAttributeValue("Permanent", "yes");
4181 }
4182
4183 if (WindowsInstallerConstants.MsidbComponentAttributesTransitive == (attributes & WindowsInstallerConstants.MsidbComponentAttributesTransitive))
4184 {
4185 xComponent.SetAttributeValue("Transitive", "yes");
4186 }
4187
4188 if (WindowsInstallerConstants.MsidbComponentAttributesNeverOverwrite == (attributes & WindowsInstallerConstants.MsidbComponentAttributesNeverOverwrite))
4189 {
4190 xComponent.SetAttributeValue("NeverOverwrite", "yes");
4191 }
4192
4193 if (WindowsInstallerConstants.MsidbComponentAttributes64bit == (attributes & WindowsInstallerConstants.MsidbComponentAttributes64bit))
4194 {
4195 xComponent.SetAttributeValue("Bitness", "always64");
4196 }
4197 else
4198 {
4199 xComponent.SetAttributeValue("Bitness", "always32");
4200 }
4201
4202 if (WindowsInstallerConstants.MsidbComponentAttributesDisableRegistryReflection == (attributes & WindowsInstallerConstants.MsidbComponentAttributesDisableRegistryReflection))
4203 {
4204 xComponent.SetAttributeValue("DisableRegistryReflection", "yes");
4205 }
4206
4207 if (WindowsInstallerConstants.MsidbComponentAttributesUninstallOnSupersedence == (attributes & WindowsInstallerConstants.MsidbComponentAttributesUninstallOnSupersedence))
4208 {
4209 xComponent.SetAttributeValue("UninstallWhenSuperseded", "yes");
4210 }
4211
4212 if (WindowsInstallerConstants.MsidbComponentAttributesShared == (attributes & WindowsInstallerConstants.MsidbComponentAttributesShared))
4213 {
4214 xComponent.SetAttributeValue("Shared", "yes");
4215 }
4216
4217 if (!row.IsColumnNull(4))
4218 {
4219 xComponent.SetAttributeValue("Condition", row.FieldAsString(4));
4220 }
4221
4222 this.AddChildToParent("Directory", xComponent, row, 2);
4223 this.IndexElement(row, xComponent);
4224 }
4225 }
4226
4227 /// <summary>
4228 /// Decompile the Condition table.
4229 /// </summary>
4230 /// <param name="table">The table to decompile.</param>
4231 private void DecompileConditionTable(Table table)
4232 {
4233 foreach (var row in table.Rows)
4234 {
4235 if (this.TryGetIndexedElement("Feature", out var xFeature, row.FieldAsString(0)))
4236 {
4237 var xLevel = new XElement(Names.LevelElement,
4238 row.IsColumnNull(2) ? null : new XAttribute("Condition", row.FieldAsString(2)),
4239 new XAttribute("Level", row.FieldAsInteger(1)));
4240
4241 xFeature.Add(xLevel);
4242 }
4243 else
4244 {
4245 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Feature_", row.FieldAsString(0), "Feature"));
4246 }
4247 }
4248 }
4249
4250 /// <summary>
4251 /// Decompile the Dialog table.
4252 /// </summary>
4253 /// <param name="table">The table to decompile.</param>
4254 private void DecompileDialogTable(Table table)
4255 {
4256 foreach (var row in table.Rows)
4257 {
4258 var attributes = row.FieldAsNullableInteger(5) ?? 0;
4259
4260 var xDialog = new XElement(Names.DialogElement,
4261 new XAttribute("Id", row.FieldAsString(0)),
4262 new XAttribute("X", row.FieldAsString(1)),
4263 new XAttribute("Y", row.FieldAsString(2)),
4264 new XAttribute("Width", row.FieldAsString(3)),
4265 new XAttribute("Height", row.FieldAsString(4)),
4266 0 == (attributes & WindowsInstallerConstants.MsidbDialogAttributesVisible) ? new XAttribute("Hidden", "yes") : null,
4267 0 == (attributes & WindowsInstallerConstants.MsidbDialogAttributesModal) ? new XAttribute("Modeless", "yes") : null,
4268 0 == (attributes & WindowsInstallerConstants.MsidbDialogAttributesMinimize) ? new XAttribute("NoMinimize", "yes") : null,
4269 0 == (attributes & WindowsInstallerConstants.MsidbDialogAttributesMinimize) ? new XAttribute("NoMinimize", "yes") : null,
4270 WindowsInstallerConstants.MsidbDialogAttributesSysModal == (attributes & WindowsInstallerConstants.MsidbDialogAttributesSysModal) ? new XAttribute("SystemModal", "yes") : null,
4271 WindowsInstallerConstants.MsidbDialogAttributesKeepModeless == (attributes & WindowsInstallerConstants.MsidbDialogAttributesKeepModeless) ? new XAttribute("KeepModeless", "yes") : null,
4272 WindowsInstallerConstants.MsidbDialogAttributesTrackDiskSpace == (attributes & WindowsInstallerConstants.MsidbDialogAttributesTrackDiskSpace) ? new XAttribute("TrackDiskSpace", "yes") : null,
4273 WindowsInstallerConstants.MsidbDialogAttributesUseCustomPalette == (attributes & WindowsInstallerConstants.MsidbDialogAttributesUseCustomPalette) ? new XAttribute("CustomPalette", "yes") : null,
4274 WindowsInstallerConstants.MsidbDialogAttributesLeftScroll == (attributes & WindowsInstallerConstants.MsidbDialogAttributesLeftScroll) ? new XAttribute("LeftScroll", "yes") : null,
4275 WindowsInstallerConstants.MsidbDialogAttributesError == (attributes & WindowsInstallerConstants.MsidbDialogAttributesError) ? new XAttribute("ErrorDialog", "yes") : null,
4276 !row.IsColumnNull(6) ? new XAttribute("Title", row.FieldAsString(6)) : null);
4277
4278 this.UIElement.Add(xDialog);
4279 this.IndexElement(row, xDialog);
4280 }
4281 }
4282
4283 /// <summary>
4284 /// Decompile the Directory table.
4285 /// </summary>
4286 /// <param name="table">The table to decompile.</param>
4287 private void DecompileDirectoryTable(Table table)
4288 {
4289 foreach (var row in table.Rows)
4290 {
4291 var id = row.FieldAsString(0);
4292 var elementName = WindowsInstallerStandard.IsStandardDirectory(id) ? Names.StandardDirectoryElement : Names.DirectoryElement;
4293 var xDirectory = new XElement(elementName,
4294 new XAttribute("Id", id));
4295
4296 if (!WindowsInstallerStandard.IsStandardDirectory(id))
4297 {
4298 var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(2));
4299
4300 if (id == "TARGETDIR" && names[0] != "SourceDir")
4301 {
4302 this.Messaging.Write(WarningMessages.TargetDirCorrectedDefaultDir());
4303 xDirectory.SetAttributeValue("Name", "SourceDir");
4304 }
4305 else
4306 {
4307 if (null != names[0] && "." != names[0])
4308 {
4309 if (null != names[1])
4310 {
4311 xDirectory.SetAttributeValue("ShortName", names[0]);
4312 }
4313 else
4314 {
4315 xDirectory.SetAttributeValue("Name", names[0]);
4316 }
4317 }
4318
4319 if (null != names[1])
4320 {
4321 xDirectory.SetAttributeValue("Name", names[1]);
4322 }
4323 }
4324
4325 if (null != names[2])
4326 {
4327 if (null != names[3])
4328 {
4329 xDirectory.SetAttributeValue("ShortSourceName", names[2]);
4330 }
4331 else
4332 {
4333 xDirectory.SetAttributeValue("SourceName", names[2]);
4334 }
4335 }
4336
4337 if (null != names[3])
4338 {
4339 xDirectory.SetAttributeValue("SourceName", names[3]);
4340 }
4341 }
4342
4343 this.IndexElement(row, xDirectory);
4344 }
4345
4346 // nest the directories
4347 foreach (var row in table.Rows)
4348 {
4349 var xDirectory = this.GetIndexedElement(row);
4350
4351 var id = row.FieldAsString(0);
4352
4353 if (id == "TARGETDIR")
4354 {
4355 // Skip TARGETDIR (but see below!).
4356 }
4357 else if (row.IsColumnNull(1) || WindowsInstallerStandard.IsStandardDirectory(id))
4358 {
4359 this.RootElement.Add(xDirectory);
4360 }
4361 else
4362 {
4363 var parentDirectoryId = row.FieldAsString(1);
4364
4365 if (!this.TryGetIndexedElement("Directory", out var xParentDirectory, parentDirectoryId))
4366 {
4367 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Directory_Parent", row.FieldAsString(1), "Directory"));
4368 }
4369 else if (xParentDirectory == xDirectory) // another way to specify a root directory
4370 {
4371 this.RootElement.Add(xDirectory);
4372 }
4373 else
4374 {
4375 // TARGETDIR is omitted but if this directory is a first-generation descendant, add it as a root.
4376 if (parentDirectoryId == "TARGETDIR")
4377 {
4378 this.RootElement.Add(xDirectory);
4379 }
4380 else
4381 {
4382 xParentDirectory.Add(xDirectory);
4383 }
4384 }
4385 }
4386 }
4387 }
4388
4389 /// <summary>
4390 /// Decompile the DrLocator table.
4391 /// </summary>
4392 /// <param name="table">The table to decompile.</param>
4393 private void DecompileDrLocatorTable(Table table)
4394 {
4395 foreach (var row in table.Rows)
4396 {
4397 var xDirectorySearch = new XElement(Names.DirectorySearchElement,
4398 new XAttribute("Id", row.FieldAsString(0)),
4399 XAttributeIfNotNull("Path", row, 2),
4400 XAttributeIfNotNull("Depth", row, 3));
4401
4402 this.IndexElement(row, xDirectorySearch);
4403 }
4404 }
4405
4406 /// <summary>
4407 /// Decompile the DuplicateFile table.
4408 /// </summary>
4409 /// <param name="table">The table to decompile.</param>
4410 private void DecompileDuplicateFileTable(Table table)
4411 {
4412 foreach (var row in table.Rows)
4413 {
4414 var xCopyFile = new XElement(Names.CopyFileElement,
4415 new XAttribute("Id", row.FieldAsString(0)),
4416 new XAttribute("FileId", row.FieldAsString(2)));
4417
4418 if (!row.IsColumnNull(3))
4419 {
4420 var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(3));
4421 if (null != names[0] && null != names[1])
4422 {
4423 xCopyFile.SetAttributeValue("DestinationShortName", names[0]);
4424 xCopyFile.SetAttributeValue("DestinationName", names[1]);
4425 }
4426 else if (null != names[0])
4427 {
4428 xCopyFile.SetAttributeValue("DestinationName", names[0]);
4429 }
4430 }
4431
4432 // destination directory/property is set in FinalizeDuplicateMoveFileTables
4433
4434 this.AddChildToParent("Component", xCopyFile, row, 1);
4435 this.IndexElement(row, xCopyFile);
4436 }
4437 }
4438
4439 /// <summary>
4440 /// Decompile the Environment table.
4441 /// </summary>
4442 /// <param name="table">The table to decompile.</param>
4443 private void DecompileEnvironmentTable(Table table)
4444 {
4445 foreach (var row in table.Rows)
4446 {
4447 var xEnvironment = new XElement(Names.EnvironmentElement,
4448 new XAttribute("Id", row.FieldAsString(0)));
4449
4450 var done = false;
4451 var permanent = true;
4452 var name = row.FieldAsString(1);
4453 for (var i = 0; i < name.Length && !done; i++)
4454 {
4455 switch (name[i])
4456 {
4457 case '=':
4458 xEnvironment.SetAttributeValue("Action", "set");
4459 break;
4460 case '+':
4461 xEnvironment.SetAttributeValue("Action", "create");
4462 break;
4463 case '-':
4464 permanent = false;
4465 break;
4466 case '!':
4467 xEnvironment.SetAttributeValue("Action", "remove");
4468 break;
4469 case '*':
4470 xEnvironment.SetAttributeValue("System", "yes");
4471 break;
4472 default:
4473 xEnvironment.SetAttributeValue("Name", name.Substring(i));
4474 done = true;
4475 break;
4476 }
4477 }
4478
4479 if (permanent)
4480 {
4481 xEnvironment.SetAttributeValue("Permanent", "yes");
4482 }
4483
4484 if (!row.IsColumnNull(2))
4485 {
4486 var value = row.FieldAsString(2);
4487
4488 if (value.StartsWith("[~]", StringComparison.Ordinal))
4489 {
4490 xEnvironment.SetAttributeValue("Part", "last");
4491
4492 if (3 < value.Length)
4493 {
4494 xEnvironment.SetAttributeValue("Separator", value.Substring(3, 1));
4495 xEnvironment.SetAttributeValue("Value", value.Substring(4));
4496 }
4497 }
4498 else if (value.EndsWith("[~]", StringComparison.Ordinal))
4499 {
4500 xEnvironment.SetAttributeValue("Part", "first");
4501
4502 if (3 < value.Length)
4503 {
4504 xEnvironment.SetAttributeValue("Separator", value.Substring(value.Length - 4, 1));
4505 xEnvironment.SetAttributeValue("Value", value.Substring(0, value.Length - 4));
4506 }
4507 }
4508 else
4509 {
4510 xEnvironment.SetAttributeValue("Value", value);
4511 }
4512 }
4513
4514 this.AddChildToParent("Component", xEnvironment, row, 3);
4515 }
4516 }
4517
4518 /// <summary>
4519 /// Decompile the Error table.
4520 /// </summary>
4521 /// <param name="table">The table to decompile.</param>
4522 private void DecompileErrorTable(Table table)
4523 {
4524 foreach (var row in table.Rows)
4525 {
4526 var xError = new XElement(Names.ErrorElement,
4527 new XAttribute("Id", row.FieldAsString(0)),
4528 new XAttribute("Message", row.FieldAsString(1)));
4529
4530 this.UIElement.Add(xError);
4531 }
4532 }
4533
4534 /// <summary>
4535 /// Decompile the EventMapping table.
4536 /// </summary>
4537 /// <param name="table">The table to decompile.</param>
4538 private void DecompileEventMappingTable(Table table)
4539 {
4540 foreach (var row in table.Rows)
4541 {
4542 var xSubscribe = new XElement(Names.SubscribeElement,
4543 new XAttribute("Event", row.FieldAsString(2)),
4544 new XAttribute("Attribute", row.FieldAsString(3)));
4545
4546 if (this.TryGetIndexedElement("Control", out var xControl, row.FieldAsString(0), row.FieldAsString(1)))
4547 {
4548 xControl.Add(xSubscribe);
4549 }
4550 else
4551 {
4552 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", row.FieldAsString(0), "Control_", row.FieldAsString(1), "Control"));
4553 }
4554 }
4555 }
4556
4557 /// <summary>
4558 /// Decompile the Extension table.
4559 /// </summary>
4560 /// <param name="table">The table to decompile.</param>
4561 private void DecompileExtensionTable(Table table)
4562 {
4563 foreach (var row in table.Rows)
4564 {
4565 var xExtension = new XElement(Names.ExtensionElement,
4566 new XAttribute("Id", row.FieldAsString(0)),
4567 new XAttribute("Advertise", "yes"));
4568
4569 if (!row.IsColumnNull(3))
4570 {
4571 if (this.TryGetIndexedElement("MIME", out var xMime, row.FieldAsString(3)))
4572 {
4573 xMime.SetAttributeValue("Default", "yes");
4574 }
4575 else
4576 {
4577 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "MIME_", row.FieldAsString(3), "MIME"));
4578 }
4579 }
4580
4581 if (!row.IsColumnNull(2))
4582 {
4583 this.AddChildToParent("ProgId", xExtension, row, 2);
4584 }
4585 else
4586 {
4587 this.AddChildToParent("Component", xExtension, row, 1);
4588 }
4589
4590 this.IndexElement(row, xExtension);
4591 }
4592 }
4593
4594 /// <summary>
4595 /// Decompile the ExternalFiles table.
4596 /// </summary>
4597 /// <param name="table">The table to decompile.</param>
4598 private void DecompileExternalFilesTable(Table table)
4599 {
4600 foreach (var row in table.Rows)
4601 {
4602 var xExternalFile = new XElement(Names.ExternalFileElement,
4603 new XAttribute("File", row.FieldAsString(1)),
4604 new XAttribute("Source", row.FieldAsString(2)));
4605
4606 AddSymbolPaths(row, 3, xExternalFile);
4607
4608 if (!row.IsColumnNull(4) && !row.IsColumnNull(5))
4609 {
4610 var ignoreOffsets = row.FieldAsString(4).Split(',');
4611 var ignoreLengths = row.FieldAsString(5).Split(',');
4612
4613 if (ignoreOffsets.Length == ignoreLengths.Length)
4614 {
4615 for (var i = 0; i < ignoreOffsets.Length; i++)
4616 {
4617 var xIgnoreRange = new XElement(Names.IgnoreRangeElement);
4618
4619 if (ignoreOffsets[i].StartsWith("0x", StringComparison.Ordinal))
4620 {
4621 xIgnoreRange.SetAttributeValue("Offset", Convert.ToInt32(ignoreOffsets[i].Substring(2), 16));
4622 }
4623 else
4624 {
4625 xIgnoreRange.SetAttributeValue("Offset", Convert.ToInt32(ignoreOffsets[i], CultureInfo.InvariantCulture));
4626 }
4627
4628 if (ignoreLengths[i].StartsWith("0x", StringComparison.Ordinal))
4629 {
4630 xIgnoreRange.SetAttributeValue("Length", Convert.ToInt32(ignoreLengths[i].Substring(2), 16));
4631 }
4632 else
4633 {
4634 xIgnoreRange.SetAttributeValue("Length", Convert.ToInt32(ignoreLengths[i], CultureInfo.InvariantCulture));
4635 }
4636
4637 xExternalFile.Add(xIgnoreRange);
4638 }
4639 }
4640 else
4641 {
4642 // TODO: warn
4643 }
4644 }
4645 else if (!row.IsColumnNull(4) || !row.IsColumnNull(5))
4646 {
4647 // TODO: warn about mismatch between columns
4648 }
4649
4650 // the RetainOffsets column is handled in FinalizeFamilyFileRangesTable
4651
4652 if (!row.IsColumnNull(7))
4653 {
4654 xExternalFile.SetAttributeValue("Order", row.FieldAsInteger(7));
4655 }
4656
4657 this.AddChildToParent("ImageFamilies", xExternalFile, row, 0);
4658 this.IndexElement(row, xExternalFile);
4659 }
4660 }
4661
4662 /// <summary>
4663 /// Decompile the Feature table.
4664 /// </summary>
4665 /// <param name="table">The table to decompile.</param>
4666 private void DecompileFeatureTable(Table table)
4667 {
4668 var sortedFeatures = new SortedList<string, Row>();
4669
4670 foreach (var row in table.Rows)
4671 {
4672 var feature = new XElement(Names.FeatureElement,
4673 new XAttribute("Id", row.FieldAsString(0)),
4674 row.IsColumnNull(2) ? null : new XAttribute("Title", row.FieldAsString(2)),
4675 row.IsColumnNull(3) ? null : new XAttribute("Description", row.FieldAsString(3)),
4676 new XAttribute("Level", row.FieldAsInteger(5)),
4677 row.IsColumnNull(6) ? null : new XAttribute("ConfigurableDirectory", row.FieldAsString(6)));
4678
4679 if (row.IsColumnNull(4))
4680 {
4681 feature.SetAttributeValue("Display", "hidden");
4682 }
4683 else
4684 {
4685 var display = row.FieldAsInteger(4);
4686
4687 if (0 == display)
4688 {
4689 feature.SetAttributeValue("Display", "hidden");
4690 }
4691 else if (1 == display % 2)
4692 {
4693 feature.SetAttributeValue("Display", "expand");
4694 }
4695 }
4696
4697 var attributes = row.FieldAsInteger(7);
4698
4699 if (WindowsInstallerConstants.MsidbFeatureAttributesFavorSource == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFavorSource) && WindowsInstallerConstants.MsidbFeatureAttributesFollowParent == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFollowParent))
4700 {
4701 // TODO: display a warning for setting favor local and follow parent together
4702 }
4703 else if (WindowsInstallerConstants.MsidbFeatureAttributesFavorSource == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFavorSource))
4704 {
4705 feature.SetAttributeValue("InstallDefault", "source");
4706 }
4707 else if (WindowsInstallerConstants.MsidbFeatureAttributesFollowParent == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFollowParent))
4708 {
4709 feature.SetAttributeValue("InstallDefault", "followParent");
4710 }
4711
4712 if (WindowsInstallerConstants.MsidbFeatureAttributesFavorAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFavorAdvertise))
4713 {
4714 feature.SetAttributeValue("InstallDefault", "advertise");
4715 }
4716
4717 if (WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise) &&
4718 WindowsInstallerConstants.MsidbFeatureAttributesNoUnsupportedAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesNoUnsupportedAdvertise))
4719 {
4720 this.Messaging.Write(WarningMessages.InvalidAttributeCombination(row.SourceLineNumbers, "msidbFeatureAttributesDisallowAdvertise", "msidbFeatureAttributesNoUnsupportedAdvertise", "Feature.AllowAdvertiseType", "no"));
4721 feature.SetAttributeValue("AllowAdvertise", "no");
4722 }
4723 else if (WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise))
4724 {
4725 feature.SetAttributeValue("AllowAdvertise", "no");
4726 }
4727 else if (WindowsInstallerConstants.MsidbFeatureAttributesNoUnsupportedAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesNoUnsupportedAdvertise))
4728 {
4729 feature.SetAttributeValue("AllowAdvertise", "system");
4730 }
4731
4732 if (WindowsInstallerConstants.MsidbFeatureAttributesUIDisallowAbsent == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesUIDisallowAbsent))
4733 {
4734 feature.SetAttributeValue("Absent", "disallow");
4735 }
4736
4737 this.IndexElement(row, feature);
4738
4739 // sort the features by their display column (and append the identifier to ensure unique keys)
4740 sortedFeatures.Add(String.Format(CultureInfo.InvariantCulture, "{0:00000}|{1}", row.FieldAsInteger(4), row[0]), row);
4741 }
4742
4743 // nest the features
4744 foreach (var row in sortedFeatures.Values)
4745 {
4746 var xFeature = this.GetIndexedElement("Feature", row.FieldAsString(0));
4747
4748 if (row.IsColumnNull(1))
4749 {
4750 this.RootElement.Add(xFeature);
4751 }
4752 else
4753 {
4754 if (this.TryGetIndexedElement("Feature", out var xParentFeature, row.FieldAsString(1)))
4755 {
4756 if (xParentFeature == xFeature)
4757 {
4758 // TODO: display a warning about self-nesting
4759 }
4760 else
4761 {
4762 xParentFeature.Add(xFeature);
4763 }
4764 }
4765 else
4766 {
4767 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Feature_Parent", row.FieldAsString(1), "Feature"));
4768 }
4769 }
4770 }
4771 }
4772
4773 /// <summary>
4774 /// Decompile the FeatureComponents table.
4775 /// </summary>
4776 /// <param name="table">The table to decompile.</param>
4777 private void DecompileFeatureComponentsTable(Table table)
4778 {
4779 foreach (var row in table.Rows)
4780 {
4781 var xComponentRef = new XElement(Names.ComponentRefElement,
4782 new XAttribute("Id", row.FieldAsString(1)));
4783
4784 this.AddChildToParent("Feature", xComponentRef, row, 0);
4785 this.IndexElement(row, xComponentRef);
4786 }
4787 }
4788
4789 /// <summary>
4790 /// Decompile the File table.
4791 /// </summary>
4792 /// <param name="table">The table to decompile.</param>
4793 private void DecompileFileTable(Table table)
4794 {
4795 foreach (FileRow fileRow in table.Rows)
4796 {
4797 var xFile = new XElement(Names.FileElement,
4798 new XAttribute("Id", fileRow.File),
4799 WindowsInstallerConstants.MsidbFileAttributesReadOnly == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesReadOnly) ? new XAttribute("ReadOnly", "yes") : null,
4800 WindowsInstallerConstants.MsidbFileAttributesHidden == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesHidden) ? new XAttribute("Hidden", "yes") : null,
4801 WindowsInstallerConstants.MsidbFileAttributesSystem == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesSystem) ? new XAttribute("System", "yes") : null,
4802 WindowsInstallerConstants.MsidbFileAttributesChecksum == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesChecksum) ? new XAttribute("Checksum", "yes") : null,
4803 WindowsInstallerConstants.MsidbFileAttributesVital != (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesVital) ? new XAttribute("Vital", "no") : null,
4804 null != fileRow.Version && 0 < fileRow.Version.Length && !Char.IsDigit(fileRow.Version[0]) ? new XAttribute("CompanionFile", fileRow.Version) : null);
4805
4806 var names = this.BackendHelper.SplitMsiFileName(fileRow.FileName);
4807 if (null != names[0] && null != names[1])
4808 {
4809 xFile.SetAttributeValue("ShortName", names[0]);
4810 xFile.SetAttributeValue("Name", names[1]);
4811 }
4812 else if (null != names[0])
4813 {
4814 xFile.SetAttributeValue("Name", names[0]);
4815 }
4816
4817 if (WindowsInstallerConstants.MsidbFileAttributesNoncompressed == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesNoncompressed) &&
4818 WindowsInstallerConstants.MsidbFileAttributesCompressed == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesCompressed))
4819 {
4820 // TODO: error
4821 }
4822 else if (WindowsInstallerConstants.MsidbFileAttributesNoncompressed == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesNoncompressed))
4823 {
4824 xFile.SetAttributeValue("Compressed", "no");
4825 }
4826 else if (WindowsInstallerConstants.MsidbFileAttributesCompressed == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesCompressed))
4827 {
4828 xFile.SetAttributeValue("Compressed", "yes");
4829 }
4830
4831 this.IndexElement(fileRow, xFile);
4832 }
4833 }
4834
4835 /// <summary>
4836 /// Decompile the FileSFPCatalog table.
4837 /// </summary>
4838 /// <param name="table">The table to decompile.</param>
4839 private void DecompileFileSFPCatalogTable(Table table)
4840 {
4841 foreach (var row in table.Rows)
4842 {
4843 var xSfpFile = new XElement(Names.SFPFileElement,
4844 new XAttribute("Id", row.FieldAsString(0)));
4845
4846 this.AddChildToParent("SFPCatalog", xSfpFile, row, 1);
4847 }
4848 }
4849
4850 /// <summary>
4851 /// Decompile the Font table.
4852 /// </summary>
4853 /// <param name="table">The table to decompile.</param>
4854 private void DecompileFontTable(Table table)
4855 {
4856 foreach (var row in table.Rows)
4857 {
4858 if (this.TryGetIndexedElement("File", out var xFile, row.FieldAsString(0)))
4859 {
4860 if (!row.IsColumnNull(1))
4861 {
4862 xFile.SetAttributeValue("FontTitle", row.FieldAsString(1));
4863 }
4864 else
4865 {
4866 xFile.SetAttributeValue("TrueType", "yes");
4867 }
4868 }
4869 else
4870 {
4871 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "File_", row.FieldAsString(0), "File"));
4872 }
4873 }
4874 }
4875
4876 /// <summary>
4877 /// Decompile the Icon table.
4878 /// </summary>
4879 /// <param name="table">The table to decompile.</param>
4880 private void DecompileIconTable(Table table)
4881 {
4882 foreach (var row in table.Rows)
4883 {
4884 var icon = new XElement(Names.IconElement,
4885 new XAttribute("Id", row.FieldAsString(0)),
4886 new XAttribute("SourceFile", row.FieldAsString(1)));
4887
4888 this.RootElement.Add(icon);
4889 }
4890 }
4891
4892 /// <summary>
4893 /// Decompile the ImageFamilies table.
4894 /// </summary>
4895 /// <param name="table">The table to decompile.</param>
4896 private void DecompileImageFamiliesTable(Table table)
4897 {
4898 foreach (var row in table.Rows)
4899 {
4900 var family = new XElement(Names.FamilyElement,
4901 new XAttribute("Name", row.FieldAsString(0)),
4902 row.IsColumnNull(1) ? null : new XAttribute("MediaSrcProp", row.FieldAsString(1)),
4903 row.IsColumnNull(2) ? null : new XAttribute("DiskId", row.FieldAsString(2)),
4904 row.IsColumnNull(3) ? null : new XAttribute("SequenceStart", row.FieldAsString(3)),
4905 row.IsColumnNull(4) ? null : new XAttribute("DiskPrompt", row.FieldAsString(4)),
4906 row.IsColumnNull(5) ? null : new XAttribute("VolumeLabel", row.FieldAsString(5)));
4907
4908 this.RootElement.Add(family);
4909 this.IndexElement(row, family);
4910 }
4911 }
4912
4913 /// <summary>
4914 /// Decompile the IniFile table.
4915 /// </summary>
4916 /// <param name="table">The table to decompile.</param>
4917 private void DecompileIniFileTable(Table table)
4918 {
4919 foreach (var row in table.Rows)
4920 {
4921 var xIniFile = new XElement(Names.IniFileElement,
4922 new XAttribute("Id", row.FieldAsString(0)),
4923 new XAttribute("Section", row.FieldAsString(3)),
4924 new XAttribute("Key", row.FieldAsString(4)),
4925 new XAttribute("Value", row.FieldAsString(5)),
4926 row.IsColumnNull(2) ? null : new XAttribute("Directory", row.FieldAsString(2)));
4927
4928 var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(1));
4929
4930 if (null != names[0])
4931 {
4932 if (null == names[1])
4933 {
4934 xIniFile.SetAttributeValue("Name", names[0]);
4935 }
4936 else
4937 {
4938 xIniFile.SetAttributeValue("ShortName", names[0]);
4939 }
4940 }
4941
4942 if (null != names[1])
4943 {
4944 xIniFile.SetAttributeValue("Name", names[1]);
4945 }
4946
4947 switch (row.FieldAsInteger(6))
4948 {
4949 case WindowsInstallerConstants.MsidbIniFileActionAddLine:
4950 xIniFile.SetAttributeValue("Action", "addLine");
4951 break;
4952 case WindowsInstallerConstants.MsidbIniFileActionCreateLine:
4953 xIniFile.SetAttributeValue("Action", "createLine");
4954 break;
4955 case WindowsInstallerConstants.MsidbIniFileActionAddTag:
4956 xIniFile.SetAttributeValue("Action", "addTag");
4957 break;
4958 default:
4959 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6]));
4960 break;
4961 }
4962
4963 this.AddChildToParent("Component", xIniFile, row, 7);
4964 }
4965 }
4966
4967 /// <summary>
4968 /// Decompile the IniLocator table.
4969 /// </summary>
4970 /// <param name="table">The table to decompile.</param>
4971 private void DecompileIniLocatorTable(Table table)
4972 {
4973 foreach (var row in table.Rows)
4974 {
4975 var xIniFileSearch = new XElement(Names.IniFileSearchElement,
4976 new XAttribute("Id", row.FieldAsString(0)),
4977 new XAttribute("Section", row.FieldAsString(2)),
4978 new XAttribute("Key", row.FieldAsString(3)),
4979 row.IsColumnNull(4) || row.FieldAsInteger(4) == 0 ? null : new XAttribute("Field", row.FieldAsInteger(4)));
4980
4981 var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(1));
4982 if (null != names[0] && null != names[1])
4983 {
4984 xIniFileSearch.SetAttributeValue("ShortName", names[0]);
4985 xIniFileSearch.SetAttributeValue("Name", names[1]);
4986 }
4987 else if (null != names[0])
4988 {
4989 xIniFileSearch.SetAttributeValue("Name", names[0]);
4990 }
4991
4992 if (!row.IsColumnNull(5))
4993 {
4994 switch (row.FieldAsInteger(5))
4995 {
4996 case WindowsInstallerConstants.MsidbLocatorTypeDirectory:
4997 xIniFileSearch.SetAttributeValue("Type", "directory");
4998 break;
4999 case WindowsInstallerConstants.MsidbLocatorTypeFileName:
5000 // this is the default value
5001 break;
5002 case WindowsInstallerConstants.MsidbLocatorTypeRawValue:
5003 xIniFileSearch.SetAttributeValue("Type", "raw");
5004 break;
5005 default:
5006 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[5].Column.Name, row[5]));
5007 break;
5008 }
5009 }
5010
5011 this.IndexElement(row, xIniFileSearch);
5012 }
5013 }
5014
5015 /// <summary>
5016 /// Decompile the IsolatedComponent table.
5017 /// </summary>
5018 /// <param name="table">The table to decompile.</param>
5019 private void DecompileIsolatedComponentTable(Table table)
5020 {
5021 foreach (var row in table.Rows)
5022 {
5023 var xIsolateComponent = new XElement(Names.IsolateComponentElement,
5024 new XAttribute("Shared", row.FieldAsString(0)));
5025
5026 this.AddChildToParent("Component", xIsolateComponent, row, 1);
5027 }
5028 }
5029
5030 /// <summary>
5031 /// Decompile the LaunchCondition table.
5032 /// </summary>
5033 /// <param name="table">The table to decompile.</param>
5034 private void DecompileLaunchConditionTable(Table table)
5035 {
5036 foreach (var row in table.Rows)
5037 {
5038 if (WixUpgradeConstants.DowngradePreventedCondition == row.FieldAsString(0) || WixUpgradeConstants.UpgradePreventedCondition == row.FieldAsString(0))
5039 {
5040 continue; // MajorUpgrade rows processed in FinalizeUpgradeTable
5041 }
5042
5043 var condition = new XElement(Names.LaunchElement,
5044 new XAttribute("Condition", row.FieldAsString(0)),
5045 new XAttribute("Message", row.FieldAsString(1)));
5046
5047 this.RootElement.Add(condition);
5048 }
5049 }
5050
5051 /// <summary>
5052 /// Decompile the ListBox table.
5053 /// </summary>
5054 /// <param name="table">The table to decompile.</param>
5055 private void DecompileListBoxTable(Table table)
5056 {
5057 // sort the list boxes by their property and order
5058 var listBoxRows = table.Rows.OrderBy(row => row.FieldAsString(0)).ThenBy(row => row.FieldAsInteger(1)).ToList();
5059
5060 XElement xListBox = null;
5061 foreach (Row row in listBoxRows)
5062 {
5063 if (null == xListBox || row.FieldAsString(0) != xListBox.Attribute("Property")?.Value)
5064 {
5065 xListBox = new XElement(Names.ListBoxElement,
5066 new XAttribute("Property", row.FieldAsString(0)));
5067
5068 this.UIElement.Add(xListBox);
5069 }
5070
5071 var listItem = new XElement(Names.ListItemElement,
5072 new XAttribute("Value", row.FieldAsString(2)),
5073 row.IsColumnNull(3) ? null : new XAttribute("Text", row.FieldAsString(3)));
5074
5075 xListBox.Add(listItem);
5076 }
5077 }
5078
5079 /// <summary>
5080 /// Decompile the ListView table.
5081 /// </summary>
5082 /// <param name="table">The table to decompile.</param>
5083 private void DecompileListViewTable(Table table)
5084 {
5085 // sort the list views by their property and order
5086 var listViewRows = table.Rows.OrderBy(row => row.FieldAsString(0)).ThenBy(row => row.FieldAsInteger(1)).ToList();
5087
5088 XElement xListView = null;
5089 foreach (var row in listViewRows)
5090 {
5091 if (null == xListView || row.FieldAsString(0) != xListView.Attribute("Property")?.Value)
5092 {
5093 xListView = new XElement(Names.ListViewElement,
5094 new XAttribute("Property", row.FieldAsString(0)));
5095
5096 this.UIElement.Add(xListView);
5097 }
5098
5099 var listItem = new XElement(Names.ListItemElement,
5100 new XAttribute("Value", row.FieldAsString(2)),
5101 row.IsColumnNull(3) ? null : new XAttribute("Text", row.FieldAsString(3)),
5102 row.IsColumnNull(4) ? null : new XAttribute("Icon", row.FieldAsString(4)));
5103
5104 xListView.Add(listItem);
5105 }
5106 }
5107
5108 /// <summary>
5109 /// Decompile the LockPermissions table.
5110 /// </summary>
5111 /// <param name="table">The table to decompile.</param>
5112 private void DecompileLockPermissionsTable(Table table)
5113 {
5114 foreach (var row in table.Rows)
5115 {
5116 var xPermission = new XElement(Names.PermissionElement,
5117 row.IsColumnNull(2) ? null : new XAttribute("Domain", row.FieldAsString(2)),
5118 new XAttribute("User", row.FieldAsString(3)));
5119
5120 string[] specialPermissions;
5121
5122 switch (row.FieldAsString(1))
5123 {
5124 case "CreateFolder":
5125 specialPermissions = LockPermissionConstants.FolderPermissions;
5126 break;
5127 case "File":
5128 specialPermissions = LockPermissionConstants.FilePermissions;
5129 break;
5130 case "Registry":
5131 specialPermissions = LockPermissionConstants.RegistryPermissions;
5132 break;
5133 default:
5134 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, row.Table.Name, row.Fields[1].Column.Name, row[1]));
5135 return;
5136 }
5137
5138 var permissionBits = row.FieldAsInteger(4);
5139 for (var i = 0; i < 32; i++)
5140 {
5141 if (0 != ((permissionBits >> i) & 1))
5142 {
5143 string name = null;
5144
5145 if (specialPermissions.Length > i)
5146 {
5147 name = specialPermissions[i];
5148 }
5149 else if (16 > i && specialPermissions.Length <= i)
5150 {
5151 name = "SpecificRightsAll";
5152 }
5153 else if (28 > i && LockPermissionConstants.StandardPermissions.Length > (i - 16))
5154 {
5155 name = LockPermissionConstants.StandardPermissions[i - 16];
5156 }
5157 else if (0 <= (i - 28) && LockPermissionConstants.GenericPermissions.Length > (i - 28))
5158 {
5159 name = LockPermissionConstants.GenericPermissions[i - 28];
5160 }
5161
5162 if (null == name)
5163 {
5164 this.Messaging.Write(WarningMessages.UnknownPermission(row.SourceLineNumbers, row.Table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), i));
5165 }
5166 else
5167 {
5168 switch (name)
5169 {
5170 case "Append":
5171 case "ChangePermission":
5172 case "CreateChild":
5173 case "CreateFile":
5174 case "CreateLink":
5175 case "CreateSubkeys":
5176 case "Delete":
5177 case "DeleteChild":
5178 case "EnumerateSubkeys":
5179 case "Execute":
5180 case "FileAllRights":
5181 case "GenericAll":
5182 case "GenericExecute":
5183 case "GenericRead":
5184 case "GenericWrite":
5185 case "Notify":
5186 case "Read":
5187 case "ReadAttributes":
5188 case "ReadExtendedAttributes":
5189 case "ReadPermission":
5190 case "SpecificRightsAll":
5191 case "Synchronize":
5192 case "TakeOwnership":
5193 case "Traverse":
5194 case "Write":
5195 case "WriteAttributes":
5196 case "WriteExtendedAttributes":
5197 xPermission.SetAttributeValue(name, "yes");
5198 break;
5199 default:
5200 throw new InvalidOperationException($"Unknown permission attribute '{name}'.");
5201 }
5202 }
5203 }
5204 }
5205
5206 this.IndexElement(row, xPermission);
5207 }
5208 }
5209
5210 /// <summary>
5211 /// Decompile the Media table.
5212 /// </summary>
5213 /// <param name="table">The table to decompile.</param>
5214 private void DecompileMediaTable(Table table)
5215 {
5216 foreach (MediaRow mediaRow in table.Rows)
5217 {
5218 var xMedia = new XElement(Names.MediaElement,
5219 new XAttribute("Id", mediaRow.DiskId),
5220 mediaRow.DiskPrompt == null ? null : new XAttribute("DiskPrompt", mediaRow.DiskPrompt),
5221 mediaRow.VolumeLabel == null ? null : new XAttribute("VolumeLabel", mediaRow.VolumeLabel));
5222
5223 if (null != mediaRow.Cabinet)
5224 {
5225 var cabinet = mediaRow.Cabinet;
5226
5227 if (cabinet.StartsWith("#", StringComparison.Ordinal))
5228 {
5229 xMedia.SetAttributeValue("EmbedCab", "yes");
5230 cabinet = cabinet.Substring(1);
5231 }
5232
5233 xMedia.SetAttributeValue("Cabinet", cabinet);
5234 }
5235
5236 this.RootElement.Add(xMedia);
5237 this.IndexElement(mediaRow, xMedia);
5238 }
5239 }
5240
5241 /// <summary>
5242 /// Decompile the MIME table.
5243 /// </summary>
5244 /// <param name="table">The table to decompile.</param>
5245 private void DecompileMIMETable(Table table)
5246 {
5247 foreach (var row in table.Rows)
5248 {
5249 var mime = new XElement(Names.MIMEElement,
5250 new XAttribute("ContentType", row.FieldAsString(0)),
5251 row.IsColumnNull(2) ? null : new XAttribute("Class", row.FieldAsString(2)));
5252
5253 this.IndexElement(row, mime);
5254 }
5255 }
5256
5257 /// <summary>
5258 /// Decompile the ModuleConfiguration table.
5259 /// </summary>
5260 /// <param name="table">The table to decompile.</param>
5261 private void DecompileModuleConfigurationTable(Table table)
5262 {
5263 foreach (var row in table.Rows)
5264 {
5265 var configuration = new XElement(Names.ConfigurationElement,
5266 new XAttribute("Name", row.FieldAsString(0)),
5267 XAttributeIfNotNull("Type", row, 2),
5268 XAttributeIfNotNull("ContextData", row, 3),
5269 XAttributeIfNotNull("DefaultValue", row, 4),
5270 XAttributeIfNotNull("DisplayName", row, 6),
5271 XAttributeIfNotNull("Description", row, 7),
5272 XAttributeIfNotNull("HelpLocation", row, 8),
5273 XAttributeIfNotNull("HelpKeyword", row, 9));
5274
5275 switch (row.FieldAsInteger(1))
5276 {
5277 case 0:
5278 configuration.SetAttributeValue("Format", "Text");
5279 break;
5280 case 1:
5281 configuration.SetAttributeValue("Format", "Key");
5282 break;
5283 case 2:
5284 configuration.SetAttributeValue("Format", "Integer");
5285 break;
5286 case 3:
5287 configuration.SetAttributeValue("Format", "Bitfield");
5288 break;
5289 default:
5290 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1]));
5291 break;
5292 }
5293
5294 if (!row.IsColumnNull(5))
5295 {
5296 var attributes = row.FieldAsInteger(5);
5297
5298 if (WindowsInstallerConstants.MsidbMsmConfigurableOptionKeyNoOrphan == (attributes & WindowsInstallerConstants.MsidbMsmConfigurableOptionKeyNoOrphan))
5299 {
5300 configuration.SetAttributeValue("KeyNoOrphan", "yes");
5301 }
5302
5303 if (WindowsInstallerConstants.MsidbMsmConfigurableOptionNonNullable == (attributes & WindowsInstallerConstants.MsidbMsmConfigurableOptionNonNullable))
5304 {
5305 configuration.SetAttributeValue("NonNullable", "yes");
5306 }
5307
5308 if (3 < attributes)
5309 {
5310 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[5].Column.Name, row[5]));
5311 }
5312 }
5313
5314 this.RootElement.Add(configuration);
5315 }
5316 }
5317
5318 /// <summary>
5319 /// Decompile the ModuleDependency table.
5320 /// </summary>
5321 /// <param name="table">The table to decompile.</param>
5322 private void DecompileModuleDependencyTable(Table table)
5323 {
5324 foreach (var row in table.Rows)
5325 {
5326 var xDependency = new XElement(Names.DependencyElement,
5327 new XAttribute("RequiredId", row.FieldAsString(2)),
5328 new XAttribute("RequiredLanguage", row.FieldAsString(3)),
5329 XAttributeIfNotNull("RequiredVersion", row, 4));
5330
5331 this.RootElement.Add(xDependency);
5332 }
5333 }
5334
5335 /// <summary>
5336 /// Decompile the ModuleExclusion table.
5337 /// </summary>
5338 /// <param name="table">The table to decompile.</param>
5339 private void DecompileModuleExclusionTable(Table table)
5340 {
5341 foreach (var row in table.Rows)
5342 {
5343 var xExclusion = new XElement(Names.ExclusionElement,
5344 new XAttribute("ExcludedId", row.FieldAsString(2)),
5345 XAttributeIfNotNull("ExcludedMinVersion", row, 4),
5346 XAttributeIfNotNull("ExcludedMaxVersion", row, 5));
5347
5348 var excludedLanguage = row.FieldAsInteger(3);
5349 if (0 < excludedLanguage)
5350 {
5351 xExclusion.SetAttributeValue("ExcludeLanguage", excludedLanguage);
5352 }
5353 else if (0 > excludedLanguage)
5354 {
5355 xExclusion.SetAttributeValue("ExcludeExceptLanguage", -excludedLanguage);
5356 }
5357
5358 this.RootElement.Add(xExclusion);
5359 }
5360 }
5361
5362 /// <summary>
5363 /// Decompile the ModuleIgnoreTable table.
5364 /// </summary>
5365 /// <param name="table">The table to decompile.</param>
5366 private void DecompileModuleIgnoreTableTable(Table table)
5367 {
5368 foreach (var row in table.Rows)
5369 {
5370 var tableName = row.FieldAsString(0);
5371
5372 // the linker automatically adds a ModuleIgnoreTable row for some tables
5373 if ("ModuleConfiguration" != tableName && "ModuleSubstitution" != tableName)
5374 {
5375 var xIgnoreTable = new XElement(Names.IgnoreTableElement,
5376 new XAttribute("Id", tableName));
5377
5378 this.RootElement.Add(xIgnoreTable);
5379 }
5380 }
5381 }
5382
5383 /// <summary>
5384 /// Decompile the ModuleSignature table.
5385 /// </summary>
5386 /// <param name="table">The table to decompile.</param>
5387 private void DecompileModuleSignatureTable(Table table)
5388 {
5389 if (1 == table.Rows.Count)
5390 {
5391 var row = table.Rows[0];
5392
5393 this.RootElement.SetAttributeValue("Id", row.FieldAsString(0));
5394 // support Language columns that are treated as integers as well as strings (the WiX default, to support localizability)
5395 this.RootElement.SetAttributeValue("Language", row.FieldAsString(1));
5396 this.RootElement.SetAttributeValue("Version", row.FieldAsString(2));
5397 }
5398 else
5399 {
5400 // TODO: warn
5401 }
5402 }
5403
5404 /// <summary>
5405 /// Decompile the ModuleSubstitution table.
5406 /// </summary>
5407 /// <param name="table">The table to decompile.</param>
5408 private void DecompileModuleSubstitutionTable(Table table)
5409 {
5410 foreach (var row in table.Rows)
5411 {
5412 var xSubstitution = new XElement(Names.SubstitutionElement,
5413 new XAttribute("Table", row.FieldAsString(0)),
5414 new XAttribute("Row", row.FieldAsString(1)),
5415 new XAttribute("Column", row.FieldAsString(2)),
5416 XAttributeIfNotNull("Value", row, 3));
5417
5418 this.RootElement.Add(xSubstitution);
5419 }
5420 }
5421
5422 /// <summary>
5423 /// Decompile the MoveFile table.
5424 /// </summary>
5425 /// <param name="table">The table to decompile.</param>
5426 private void DecompileMoveFileTable(Table table)
5427 {
5428 foreach (var row in table.Rows)
5429 {
5430 var xCopyFile = new XElement(Names.CopyFileElement,
5431 new XAttribute("Id", row.FieldAsString(0)),
5432 XAttributeIfNotNull("SourceName", row, 2));
5433
5434 if (!row.IsColumnNull(3))
5435 {
5436 var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(3));
5437 if (null != names[0] && null != names[1])
5438 {
5439 xCopyFile.SetAttributeValue("DestinationShortName", names[0]);
5440 xCopyFile.SetAttributeValue("DestinationName", names[1]);
5441 }
5442 else if (null != names[0])
5443 {
5444 xCopyFile.SetAttributeValue("DestinationName", names[0]);
5445 }
5446 }
5447
5448 // source/destination directory/property is set in FinalizeDuplicateMoveFileTables
5449
5450 switch (row.FieldAsInteger(6))
5451 {
5452 case 0:
5453 break;
5454 case WindowsInstallerConstants.MsidbMoveFileOptionsMove:
5455 xCopyFile.SetAttributeValue("Delete", "yes");
5456 break;
5457 default:
5458 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6]));
5459 break;
5460 }
5461
5462 this.AddChildToParent("Component", xCopyFile, row, 1);
5463 this.IndexElement(row, xCopyFile);
5464 }
5465 }
5466
5467 /// <summary>
5468 /// Decompile the MsiDigitalCertificate table.
5469 /// </summary>
5470 /// <param name="table">The table to decompile.</param>
5471 private void DecompileMsiDigitalCertificateTable(Table table)
5472 {
5473 foreach (var row in table.Rows)
5474 {
5475 var xDigitalCertificate = new XElement(Names.DigitalCertificateElement,
5476 new XAttribute("Id", row.FieldAsString(0)),
5477 new XAttribute("SourceFile", row.FieldAsString(1)));
5478
5479 this.IndexElement(row, xDigitalCertificate);
5480 }
5481 }
5482
5483 /// <summary>
5484 /// Decompile the MsiDigitalSignature table.
5485 /// </summary>
5486 /// <param name="table">The table to decompile.</param>
5487 private void DecompileMsiDigitalSignatureTable(Table table)
5488 {
5489 foreach (var row in table.Rows)
5490 {
5491 var xDigitalSignature = new XElement(Names.DigitalSignatureElement,
5492 XAttributeIfNotNull("SourceFile", row, 3));
5493
5494 this.AddChildToParent("MsiDigitalCertificate", xDigitalSignature, row, 2);
5495
5496 if (this.TryGetIndexedElement(row.FieldAsString(0), out var xParentElement, row.FieldAsString(1)))
5497 {
5498 xParentElement.Add(xDigitalSignature);
5499 }
5500 else
5501 {
5502 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "SignObject", row.FieldAsString(1), row.FieldAsString(0)));
5503 }
5504 }
5505 }
5506
5507 /// <summary>
5508 /// Decompile the MsiEmbeddedChainer table.
5509 /// </summary>
5510 /// <param name="table">The table to decompile.</param>
5511 private void DecompileMsiEmbeddedChainerTable(Table table)
5512 {
5513 foreach (var row in table.Rows)
5514 {
5515 var xEmbeddedChainer = new XElement(Names.EmbeddedChainerElement,
5516 new XAttribute("Id", row.FieldAsString(0)),
5517 new XAttribute("Condition", row.FieldAsString(1)),
5518 XAttributeIfNotNull("CommandLine", row, 2));
5519
5520 switch (row.FieldAsInteger(4))
5521 {
5522 case WindowsInstallerConstants.MsidbCustomActionTypeExe + WindowsInstallerConstants.MsidbCustomActionTypeBinaryData:
5523 xEmbeddedChainer.SetAttributeValue("BinarySource", row.FieldAsString(3));
5524 break;
5525 case WindowsInstallerConstants.MsidbCustomActionTypeExe + WindowsInstallerConstants.MsidbCustomActionTypeSourceFile:
5526 xEmbeddedChainer.SetAttributeValue("FileSource", row.FieldAsString(3));
5527 break;
5528 case WindowsInstallerConstants.MsidbCustomActionTypeExe + WindowsInstallerConstants.MsidbCustomActionTypeProperty:
5529 xEmbeddedChainer.SetAttributeValue("PropertySource", row.FieldAsString(3));
5530 break;
5531 default:
5532 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4]));
5533 break;
5534 }
5535
5536 this.RootElement.Add(xEmbeddedChainer);
5537 }
5538 }
5539
5540 /// <summary>
5541 /// Decompile the MsiEmbeddedUI table.
5542 /// </summary>
5543 /// <param name="table">The table to decompile.</param>
5544 private void DecompileMsiEmbeddedUITable(Table table)
5545 {
5546 var xEmbeddedUI = new XElement(Names.EmbeddedUIElement);
5547
5548 var foundEmbeddedUI = false;
5549 var foundEmbeddedResources = false;
5550
5551 foreach (var row in table.Rows)
5552 {
5553 var attributes = row.FieldAsInteger(2);
5554
5555 if (WindowsInstallerConstants.MsidbEmbeddedUI == (attributes & WindowsInstallerConstants.MsidbEmbeddedUI))
5556 {
5557 if (foundEmbeddedUI)
5558 {
5559 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[2].Column.Name, row[2]));
5560 }
5561 else
5562 {
5563 xEmbeddedUI.SetAttributeValue("Id", row.FieldAsString(0));
5564 xEmbeddedUI.SetAttributeValue("Name", row.FieldAsString(1));
5565
5566 var messageFilter = row.FieldAsInteger(3);
5567 if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_FATALEXIT))
5568 {
5569 xEmbeddedUI.SetAttributeValue("IgnoreFatalExit", "yes");
5570 }
5571
5572 if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_ERROR))
5573 {
5574 xEmbeddedUI.SetAttributeValue("IgnoreError", "yes");
5575 }
5576
5577 if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_WARNING))
5578 {
5579 xEmbeddedUI.SetAttributeValue("IgnoreWarning", "yes");
5580 }
5581
5582 if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_USER))
5583 {
5584 xEmbeddedUI.SetAttributeValue("IgnoreUser", "yes");
5585 }
5586
5587 if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_INFO))
5588 {
5589 xEmbeddedUI.SetAttributeValue("IgnoreInfo", "yes");
5590 }
5591
5592 if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_FILESINUSE))
5593 {
5594 xEmbeddedUI.SetAttributeValue("IgnoreFilesInUse", "yes");
5595 }
5596
5597 if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_RESOLVESOURCE))
5598 {
5599 xEmbeddedUI.SetAttributeValue("IgnoreResolveSource", "yes");
5600 }
5601
5602 if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_OUTOFDISKSPACE))
5603 {
5604 xEmbeddedUI.SetAttributeValue("IgnoreOutOfDiskSpace", "yes");
5605 }
5606
5607 if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_ACTIONSTART))
5608 {
5609 xEmbeddedUI.SetAttributeValue("IgnoreActionStart", "yes");
5610 }
5611
5612 if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_ACTIONDATA))
5613 {
5614 xEmbeddedUI.SetAttributeValue("IgnoreActionData", "yes");
5615 }
5616
5617 if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_PROGRESS))
5618 {
5619 xEmbeddedUI.SetAttributeValue("IgnoreProgress", "yes");
5620 }
5621
5622 if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_COMMONDATA))
5623 {
5624 xEmbeddedUI.SetAttributeValue("IgnoreCommonData", "yes");
5625 }
5626
5627 if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_INITIALIZE))
5628 {
5629 xEmbeddedUI.SetAttributeValue("IgnoreInitialize", "yes");
5630 }
5631
5632 if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_TERMINATE))
5633 {
5634 xEmbeddedUI.SetAttributeValue("IgnoreTerminate", "yes");
5635 }
5636
5637 if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_SHOWDIALOG))
5638 {
5639 xEmbeddedUI.SetAttributeValue("IgnoreShowDialog", "yes");
5640 }
5641
5642 if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_RMFILESINUSE))
5643 {
5644 xEmbeddedUI.SetAttributeValue("IgnoreRMFilesInUse", "yes");
5645 }
5646
5647 if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_INSTALLSTART))
5648 {
5649 xEmbeddedUI.SetAttributeValue("IgnoreInstallStart", "yes");
5650 }
5651
5652 if (0 == (messageFilter & WindowsInstallerConstants.INSTALLLOGMODE_INSTALLEND))
5653 {
5654 xEmbeddedUI.SetAttributeValue("IgnoreInstallEnd", "yes");
5655 }
5656
5657 if (WindowsInstallerConstants.MsidbEmbeddedHandlesBasic == (attributes & WindowsInstallerConstants.MsidbEmbeddedHandlesBasic))
5658 {
5659 xEmbeddedUI.SetAttributeValue("SupportBasicUI", "yes");
5660 }
5661
5662 xEmbeddedUI.SetAttributeValue("SourceFile", row.FieldAsString(4));
5663
5664 this.UIElement.Add(xEmbeddedUI);
5665 foundEmbeddedUI = true;
5666 }
5667 }
5668 else
5669 {
5670 var xEmbeddedResource = new XElement(Names.EmbeddedUIResourceElement,
5671 new XAttribute("Id", row.FieldAsString(0)),
5672 new XAttribute("Name", row.FieldAsString(1)),
5673 new XAttribute("SourceFile", row.FieldAsString(4)));
5674
5675 xEmbeddedUI.Add(xEmbeddedResource);
5676 foundEmbeddedResources = true;
5677 }
5678 }
5679
5680 if (!foundEmbeddedUI && foundEmbeddedResources)
5681 {
5682 // TODO: warn
5683 }
5684 }
5685
5686 /// <summary>
5687 /// Decompile the MsiLockPermissionsEx table.
5688 /// </summary>
5689 /// <param name="table">The table to decompile.</param>
5690 private void DecompileMsiLockPermissionsExTable(Table table)
5691 {
5692 foreach (var row in table.Rows)
5693 {
5694 var xPermissionEx = new XElement(Names.PermissionExElement,
5695 new XAttribute("Id", row.FieldAsString(0)),
5696 new XAttribute("Sddl", row.FieldAsString(3)),
5697 XAttributeIfNotNull("Condition", row, 4));
5698
5699 switch (row.FieldAsString(2))
5700 {
5701 case "CreateFolder":
5702 case "File":
5703 case "Registry":
5704 case "ServiceInstall":
5705 break;
5706 default:
5707 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, row.Table.Name, row.Fields[1].Column.Name, row[1]));
5708 return;
5709 }
5710
5711 this.IndexElement(row, xPermissionEx);
5712 }
5713 }
5714
5715 /// <summary>
5716 /// Decompile the MsiPackageCertificate table.
5717 /// </summary>
5718 /// <param name="table">The table to decompile.</param>
5719 private void DecompileMsiPackageCertificateTable(Table table)
5720 {
5721 if (0 < table.Rows.Count)
5722 {
5723 var xPackageCertificates = new XElement(Names.PatchCertificatesElement);
5724 this.RootElement.Add(xPackageCertificates);
5725 this.AddCertificates(table, xPackageCertificates);
5726 }
5727 }
5728
5729 /// <summary>
5730 /// Decompile the MsiPatchCertificate table.
5731 /// </summary>
5732 /// <param name="table">The table to decompile.</param>
5733 private void DecompileMsiPatchCertificateTable(Table table)
5734 {
5735 if (0 < table.Rows.Count)
5736 {
5737 var xPatchCertificates = new XElement(Names.PatchCertificatesElement);
5738 this.RootElement.Add(xPatchCertificates);
5739 this.AddCertificates(table, xPatchCertificates);
5740 }
5741 }
5742
5743 /// <summary>
5744 /// Insert DigitalCertificate records associated with passed msiPackageCertificate or msiPatchCertificate table.
5745 /// </summary>
5746 /// <param name="table">The table being decompiled.</param>
5747 /// <param name="parent">DigitalCertificate parent</param>
5748 private void AddCertificates(Table table, XElement parent)
5749 {
5750 foreach (var row in table.Rows)
5751 {
5752 if (this.TryGetIndexedElement("MsiDigitalCertificate", out var xDigitalCertificate, row.FieldAsString(1)))
5753 {
5754 parent.Add(xDigitalCertificate);
5755 }
5756 else
5757 {
5758 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "DigitalCertificate_", row.FieldAsString(1), "MsiDigitalCertificate"));
5759 }
5760 }
5761 }
5762
5763 /// <summary>
5764 /// Decompile the MsiShortcutProperty table.
5765 /// </summary>
5766 /// <param name="table">The table to decompile.</param>
5767 private void DecompileMsiShortcutPropertyTable(Table table)
5768 {
5769 foreach (var row in table.Rows)
5770 {
5771 var xProperty = new XElement(Names.ShortcutPropertyElement,
5772 new XAttribute("Id", row.FieldAsString(0)),
5773 new XAttribute("Key", row.FieldAsString(2)),
5774 new XAttribute("Value", row.FieldAsString(3)));
5775
5776 this.AddChildToParent("Shortcut", xProperty, row, 1);
5777 }
5778 }
5779
5780 /// <summary>
5781 /// Decompile the ODBCAttribute table.
5782 /// </summary>
5783 /// <param name="table">The table to decompile.</param>
5784 private void DecompileODBCAttributeTable(Table table)
5785 {
5786 foreach (var row in table.Rows)
5787 {
5788 var xProperty = new XElement(Names.PropertyElement,
5789 new XAttribute("Id", row.FieldAsString(1)),
5790 row.IsColumnNull(2) ? null : new XAttribute("Value", row.FieldAsString(2)));
5791
5792 this.AddChildToParent("ODBCDriver", xProperty, row, 0);
5793 }
5794 }
5795
5796 /// <summary>
5797 /// Decompile the ODBCDataSource table.
5798 /// </summary>
5799 /// <param name="table">The table to decompile.</param>
5800 private void DecompileODBCDataSourceTable(Table table)
5801 {
5802 foreach (var row in table.Rows)
5803 {
5804 var xOdbcDataSource = new XElement(Names.ODBCDataSourceElement,
5805 new XAttribute("Id", row.FieldAsString(0)),
5806 new XAttribute("Name", row.FieldAsString(2)),
5807 new XAttribute("DriverName", row.FieldAsString(3)));
5808
5809 switch (row.FieldAsInteger(4))
5810 {
5811 case WindowsInstallerConstants.MsidbODBCDataSourceRegistrationPerMachine:
5812 xOdbcDataSource.SetAttributeValue("Registration", "machine");
5813 break;
5814 case WindowsInstallerConstants.MsidbODBCDataSourceRegistrationPerUser:
5815 xOdbcDataSource.SetAttributeValue("Registration", "user");
5816 break;
5817 default:
5818 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4]));
5819 break;
5820 }
5821
5822 this.IndexElement(row, xOdbcDataSource);
5823 }
5824 }
5825
5826 /// <summary>
5827 /// Decompile the ODBCDriver table.
5828 /// </summary>
5829 /// <param name="table">The table to decompile.</param>
5830 private void DecompileODBCDriverTable(Table table)
5831 {
5832 foreach (var row in table.Rows)
5833 {
5834 var xOdbcDriver = new XElement(Names.ODBCDriverElement,
5835 new XAttribute("Id", row.FieldAsString(0)),
5836 new XAttribute("Name", row.FieldAsString(2)),
5837 new XAttribute("File", row.FieldAsString(3)),
5838 XAttributeIfNotNull("SetupFile", row, 4));
5839
5840 this.AddChildToParent("Component", xOdbcDriver, row, 1);
5841 this.IndexElement(row, xOdbcDriver);
5842 }
5843 }
5844
5845 /// <summary>
5846 /// Decompile the ODBCSourceAttribute table.
5847 /// </summary>
5848 /// <param name="table">The table to decompile.</param>
5849 private void DecompileODBCSourceAttributeTable(Table table)
5850 {
5851 foreach (var row in table.Rows)
5852 {
5853 var xProperty = new XElement(Names.PropertyElement,
5854 new XAttribute("Id", row.FieldAsString(1)),
5855 XAttributeIfNotNull("Value", row, 2));
5856
5857 this.AddChildToParent("ODBCDataSource", xProperty, row, 0);
5858 }
5859 }
5860
5861 /// <summary>
5862 /// Decompile the ODBCTranslator table.
5863 /// </summary>
5864 /// <param name="table">The table to decompile.</param>
5865 private void DecompileODBCTranslatorTable(Table table)
5866 {
5867 foreach (var row in table.Rows)
5868 {
5869 var xOdbcTranslator = new XElement(Names.ODBCTranslatorElement,
5870 new XAttribute("Id", row.FieldAsString(0)),
5871 new XAttribute("Name", row.FieldAsString(2)),
5872 new XAttribute("File", row.FieldAsString(3)),
5873 XAttributeIfNotNull("SetupFile", row, 4));
5874
5875 this.AddChildToParent("Component", xOdbcTranslator, row, 1);
5876 }
5877 }
5878
5879 /// <summary>
5880 /// Decompile the PatchMetadata table.
5881 /// </summary>
5882 /// <param name="table">The table to decompile.</param>
5883 private void DecompilePatchMetadataTable(Table table)
5884 {
5885 if (0 < table.Rows.Count)
5886 {
5887 var xPatchMetadata = new XElement(Names.PatchMetadataElement);
5888
5889 foreach (var row in table.Rows)
5890 {
5891 var value = row.FieldAsString(2);
5892
5893 switch (row.FieldAsString(1))
5894 {
5895 case "AllowRemoval":
5896 if ("1" == value)
5897 {
5898 xPatchMetadata.SetAttributeValue("AllowRemoval", "yes");
5899 }
5900 break;
5901 case "Classification":
5902 if (null != value)
5903 {
5904 xPatchMetadata.SetAttributeValue("Classification", value);
5905 }
5906 break;
5907 case "CreationTimeUTC":
5908 if (null != value)
5909 {
5910 xPatchMetadata.SetAttributeValue("CreationTimeUTC", value);
5911 }
5912 break;
5913 case "Description":
5914 if (null != value)
5915 {
5916 xPatchMetadata.SetAttributeValue("Description", value);
5917 }
5918 break;
5919 case "DisplayName":
5920 if (null != value)
5921 {
5922 xPatchMetadata.SetAttributeValue("DisplayName", value);
5923 }
5924 break;
5925 case "ManufacturerName":
5926 if (null != value)
5927 {
5928 xPatchMetadata.SetAttributeValue("ManufacturerName", value);
5929 }
5930 break;
5931 case "MinorUpdateTargetRTM":
5932 if (null != value)
5933 {
5934 xPatchMetadata.SetAttributeValue("MinorUpdateTargetRTM", value);
5935 }
5936 break;
5937 case "MoreInfoURL":
5938 if (null != value)
5939 {
5940 xPatchMetadata.SetAttributeValue("MoreInfoURL", value);
5941 }
5942 break;
5943 case "OptimizeCA":
5944 var xOptimizeCustomActions = new XElement(Names.OptimizeCustomActionsElement);
5945 var optimizeCA = Int32.Parse(value, CultureInfo.InvariantCulture);
5946 if (0 != (Convert.ToInt32(OptimizeCAFlags.SkipAssignment) & optimizeCA))
5947 {
5948 xOptimizeCustomActions.SetAttributeValue("SkipAssignment", "yes");
5949 }
5950
5951 if (0 != (Convert.ToInt32(OptimizeCAFlags.SkipImmediate) & optimizeCA))
5952 {
5953 xOptimizeCustomActions.SetAttributeValue("SkipImmediate", "yes");
5954 }
5955
5956 if (0 != (Convert.ToInt32(OptimizeCAFlags.SkipDeferred) & optimizeCA))
5957 {
5958 xOptimizeCustomActions.SetAttributeValue("SkipDeferred", "yes");
5959 }
5960
5961 xPatchMetadata.Add(xOptimizeCustomActions);
5962 break;
5963 case "OptimizedInstallMode":
5964 if ("1" == value)
5965 {
5966 xPatchMetadata.SetAttributeValue("OptimizedInstallMode", "yes");
5967 }
5968 break;
5969 case "TargetProductName":
5970 if (null != value)
5971 {
5972 xPatchMetadata.SetAttributeValue("TargetProductName", value);
5973 }
5974 break;
5975 default:
5976 var xCustomProperty = new XElement(Names.CustomPropertyElement,
5977 XAttributeIfNotNull("Company", row, 0),
5978 XAttributeIfNotNull("Property", row, 1),
5979 XAttributeIfNotNull("Value", row, 2));
5980
5981 xPatchMetadata.Add(xCustomProperty);
5982 break;
5983 }
5984 }
5985
5986 this.RootElement.Add(xPatchMetadata);
5987 }
5988 }
5989
5990 /// <summary>
5991 /// Decompile the PatchSequence table.
5992 /// </summary>
5993 /// <param name="table">The table to decompile.</param>
5994 private void DecompilePatchSequenceTable(Table table)
5995 {
5996 foreach (var row in table.Rows)
5997 {
5998 var patchSequence = new XElement(Names.PatchSequenceElement,
5999 new XAttribute("PatchFamily", row.FieldAsString(0)));
6000
6001 if (!row.IsColumnNull(1))
6002 {
6003 try
6004 {
6005 var guid = new Guid(row.FieldAsString(1));
6006
6007 patchSequence.SetAttributeValue("ProductCode", row.FieldAsString(1));
6008 }
6009 catch // non-guid value
6010 {
6011 patchSequence.SetAttributeValue("TargetImage", row.FieldAsString(1));
6012 }
6013 }
6014
6015 if (!row.IsColumnNull(2))
6016 {
6017 patchSequence.SetAttributeValue("Sequence", row.FieldAsString(2));
6018 }
6019
6020 if (!row.IsColumnNull(3) && 0x1 == row.FieldAsInteger(3))
6021 {
6022 patchSequence.SetAttributeValue("Supersede", "yes");
6023 }
6024
6025 this.RootElement.Add(patchSequence);
6026 }
6027 }
6028
6029 /// <summary>
6030 /// Decompile the ProgId table.
6031 /// </summary>
6032 /// <param name="table">The table to decompile.</param>
6033 private void DecompileProgIdTable(Table table)
6034 {
6035 foreach (var row in table.Rows)
6036 {
6037 var xProgId = new XElement(Names.ProgIdElement,
6038 new XAttribute("Advertise", "yes"),
6039 new XAttribute("Id", row.FieldAsString(0)),
6040 XAttributeIfNotNull("Description", row, 3),
6041 XAttributeIfNotNull("Icon", row, 4),
6042 XAttributeIfNotNull("IconIndex", row, 5));
6043
6044 this.IndexElement(row, xProgId);
6045 }
6046
6047 // nest the ProgIds
6048 foreach (var row in table.Rows)
6049 {
6050 var xProgId = this.GetIndexedElement(row);
6051
6052 if (!row.IsColumnNull(1))
6053 {
6054 this.AddChildToParent("ProgId", xProgId, row, 1);
6055 }
6056 else if (!row.IsColumnNull(2))
6057 {
6058 // nesting is handled in FinalizeProgIdTable
6059 }
6060 else
6061 {
6062 // TODO: warn for orphaned ProgId
6063 }
6064 }
6065 }
6066
6067 /// <summary>
6068 /// Decompile the Properties table.
6069 /// </summary>
6070 /// <param name="table">The table to decompile.</param>
6071 private void DecompilePropertiesTable(Table table)
6072 {
6073 foreach (var row in table.Rows)
6074 {
6075 var name = row.FieldAsString(0);
6076 var value = row.FieldAsString(1);
6077
6078 switch (name)
6079 {
6080 case "AllowProductCodeMismatches":
6081 if ("1" == value)
6082 {
6083 this.RootElement.SetAttributeValue("AllowProductCodeMismatches", "yes");
6084 }
6085 break;
6086 case "AllowProductVersionMajorMismatches":
6087 if ("1" == value)
6088 {
6089 this.RootElement.SetAttributeValue("AllowMajorVersionMismatches", "yes");
6090 }
6091 break;
6092 case "ApiPatchingSymbolFlags":
6093 if (null != value)
6094 {
6095 try
6096 {
6097 // remove the leading "0x" if its present
6098 if (value.StartsWith("0x", StringComparison.Ordinal))
6099 {
6100 value = value.Substring(2);
6101 }
6102
6103 this.RootElement.SetAttributeValue("SymbolFlags", Convert.ToInt32(value, 16));
6104 }
6105 catch
6106 {
6107 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1]));
6108 }
6109 }
6110 break;
6111 case "DontRemoveTempFolderWhenFinished":
6112 if ("1" == value)
6113 {
6114 this.RootElement.SetAttributeValue("CleanWorkingFolder", "no");
6115 }
6116 break;
6117 case "IncludeWholeFilesOnly":
6118 if ("1" == value)
6119 {
6120 this.RootElement.SetAttributeValue("WholeFilesOnly", "yes");
6121 }
6122 break;
6123 case "ListOfPatchGUIDsToReplace":
6124 if (null != value)
6125 {
6126 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}\}");
6127 var guidMatches = guidRegex.Matches(value);
6128
6129 foreach (Match guidMatch in guidMatches)
6130 {
6131 var xReplacePatch = new XElement(Names.ReplacePatchElement,
6132 new XAttribute("Id", guidMatch.Value));
6133
6134 this.RootElement.Add(xReplacePatch);
6135 }
6136 }
6137 break;
6138 case "ListOfTargetProductCodes":
6139 if (null != value)
6140 {
6141 var targetProductCodes = value.Split(';');
6142
6143 foreach (var targetProductCodeString in targetProductCodes)
6144 {
6145 var xTargetProductCode = new XElement(Names.TargetProductCodeElement,
6146 new XAttribute("Id", targetProductCodeString));
6147
6148 this.RootElement.Add(xTargetProductCode);
6149 }
6150 }
6151 break;
6152 case "PatchGUID":
6153 this.RootElement.SetAttributeValue("Id", value);
6154 break;
6155 case "PatchSourceList":
6156 this.RootElement.SetAttributeValue("SourceList", value);
6157 break;
6158 case "PatchOutputPath":
6159 this.RootElement.SetAttributeValue("OutputPath", value);
6160 break;
6161 default:
6162 var patchProperty = new XElement(Names.PatchPropertyElement,
6163 new XAttribute("Name", name),
6164 new XAttribute("Value", value));
6165
6166 this.RootElement.Add(patchProperty);
6167 break;
6168 }
6169 }
6170 }
6171
6172 /// <summary>
6173 /// Decompile the Property table.
6174 /// </summary>
6175 /// <param name="table">The table to decompile.</param>
6176 private void DecompilePropertyTable(Table table)
6177 {
6178 foreach (var row in table.Rows)
6179 {
6180 var id = row.FieldAsString(0);
6181 var value = row.FieldAsString(1);
6182
6183 if ("AdminProperties" == id || "MsiHiddenProperties" == id || "SecureCustomProperties" == id)
6184 {
6185 if (0 < value.Length)
6186 {
6187 foreach (var propertyId in value.Split(';'))
6188 {
6189 if (WixUpgradeConstants.DowngradeDetectedProperty == propertyId || WixUpgradeConstants.UpgradeDetectedProperty == propertyId)
6190 {
6191 continue;
6192 }
6193
6194 var property = propertyId;
6195 var suppressModulularization = false;
6196 if (OutputType.Module == this.OutputType)
6197 {
6198 if (propertyId.EndsWith(this.ModularizationGuid.Substring(1, 36).Replace('-', '_'), StringComparison.Ordinal))
6199 {
6200 property = propertyId.Substring(0, propertyId.Length - this.ModularizationGuid.Length + 1);
6201 }
6202 else
6203 {
6204 suppressModulularization = true;
6205 }
6206 }
6207
6208 var xSpecialProperty = this.EnsureProperty(property);
6209 if (suppressModulularization)
6210 {
6211 xSpecialProperty.SetAttributeValue("SuppressModularization", "yes");
6212 }
6213
6214 switch (id)
6215 {
6216 case "AdminProperties":
6217 xSpecialProperty.SetAttributeValue("Admin", "yes");
6218 break;
6219 case "MsiHiddenProperties":
6220 xSpecialProperty.SetAttributeValue("Hidden", "yes");
6221 break;
6222 case "SecureCustomProperties":
6223 xSpecialProperty.SetAttributeValue("Secure", "yes");
6224 break;
6225 }
6226 }
6227 }
6228
6229 continue;
6230 }
6231 else if (OutputType.Product == this.OutputType)
6232 {
6233 switch (id)
6234 {
6235 case "Manufacturer":
6236 this.RootElement.SetAttributeValue("Manufacturer", value);
6237 continue;
6238 case "ProductCode":
6239 this.RootElement.SetAttributeValue("ProductCode", value.ToUpper(CultureInfo.InvariantCulture));
6240 continue;
6241 case "ProductLanguage":
6242 this.RootElement.SetAttributeValue("Language", value);
6243 continue;
6244 case "ProductName":
6245 this.RootElement.SetAttributeValue("Name", value);
6246 continue;
6247 case "ProductVersion":
6248 this.RootElement.SetAttributeValue("Version", value);
6249 continue;
6250 case "UpgradeCode":
6251 this.RootElement.SetAttributeValue("UpgradeCode", value);
6252 continue;
6253 }
6254 }
6255
6256 if (!this.SuppressUI || "ErrorDialog" != id)
6257 {
6258 var xProperty = this.EnsureProperty(id);
6259
6260 xProperty.SetAttributeValue("Value", value);
6261 }
6262 }
6263 }
6264
6265 /// <summary>
6266 /// Decompile the PublishComponent table.
6267 /// </summary>
6268 /// <param name="table">The table to decompile.</param>
6269 private void DecompilePublishComponentTable(Table table)
6270 {
6271 foreach (var row in table.Rows)
6272 {
6273 var category = new XElement(Names.CategoryElement,
6274 new XAttribute("Id", row.FieldAsString(0)),
6275 new XAttribute("Qualifier", row.FieldAsString(1)),
6276 XAttributeIfNotNull("AppData", row, 3));
6277
6278 this.AddChildToParent("Component", category, row, 2);
6279 }
6280 }
6281
6282 /// <summary>
6283 /// Decompile the RadioButton table.
6284 /// </summary>
6285 /// <param name="table">The table to decompile.</param>
6286 private void DecompileRadioButtonTable(Table table)
6287 {
6288 foreach (var row in table.Rows)
6289 {
6290 var radioButton = new XElement(Names.RadioButtonElement,
6291 new XAttribute("Value", row.FieldAsString(2)),
6292 new XAttribute("X", row.FieldAsInteger(3)),
6293 new XAttribute("Y", row.FieldAsInteger(4)),
6294 new XAttribute("Width", row.FieldAsInteger(5)),
6295 new XAttribute("Height", row.FieldAsInteger(6)),
6296 XAttributeIfNotNull("Text", row, 7));
6297
6298 if (!row.IsColumnNull(8))
6299 {
6300 var help = (row.FieldAsString(8)).Split('|');
6301
6302 if (2 == help.Length)
6303 {
6304 if (0 < help[0].Length)
6305 {
6306 radioButton.SetAttributeValue("ToolTip", help[0]);
6307 }
6308
6309 if (0 < help[1].Length)
6310 {
6311 radioButton.SetAttributeValue("Help", help[1]);
6312 }
6313 }
6314 }
6315
6316 this.IndexElement(row, radioButton);
6317 }
6318
6319 // nest the radio buttons
6320 var xRadioButtonGroups = new Dictionary<string, XElement>();
6321 foreach (var row in table.Rows.OrderBy(row => row.FieldAsString(0)).ThenBy(row => row.FieldAsInteger(1)))
6322 {
6323 var xRadioButton = this.GetIndexedElement(row);
6324
6325 if (!xRadioButtonGroups.TryGetValue(row.FieldAsString(0), out var xRadioButtonGroup))
6326 {
6327 xRadioButtonGroup = new XElement(Names.RadioButtonGroupElement,
6328 new XAttribute("Property", row.FieldAsString(0)));
6329
6330 this.UIElement.Add(xRadioButtonGroup);
6331 xRadioButtonGroups.Add(row.FieldAsString(0), xRadioButtonGroup);
6332 }
6333
6334 xRadioButtonGroup.Add(xRadioButton);
6335 }
6336 }
6337
6338 /// <summary>
6339 /// Decompile the Registry table.
6340 /// </summary>
6341 /// <param name="table">The table to decompile.</param>
6342 private void DecompileRegistryTable(Table table)
6343 {
6344 foreach (var row in table.Rows)
6345 {
6346 if (("-" == row.FieldAsString(3) || "+" == row.FieldAsString(3) || "*" == row.FieldAsString(3)) && row.IsColumnNull(4))
6347 {
6348 var xRegistryKey = new XElement(Names.RegistryKeyElement,
6349 new XAttribute("Id", row.FieldAsString(0)),
6350 new XAttribute("Key", row.FieldAsString(2)));
6351
6352 if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out var registryRootType))
6353 {
6354 xRegistryKey.SetAttributeValue("Root", registryRootType);
6355 }
6356
6357 switch (row.FieldAsString(3))
6358 {
6359 case "+":
6360 xRegistryKey.SetAttributeValue("ForceCreateOnInstall", "yes");
6361 break;
6362 case "-":
6363 xRegistryKey.SetAttributeValue("ForceDeleteOnUninstall", "yes");
6364 break;
6365 case "*":
6366 xRegistryKey.SetAttributeValue("ForceCreateOnInstall", "yes");
6367 xRegistryKey.SetAttributeValue("ForceDeleteOnUninstall", "yes");
6368 break;
6369 }
6370
6371 this.IndexElement(row, xRegistryKey);
6372 }
6373 else
6374 {
6375 var xRegistryValue = new XElement(Names.RegistryValueElement,
6376 new XAttribute("Id", row.FieldAsString(0)),
6377 new XAttribute("Key", row.FieldAsString(2)),
6378 XAttributeIfNotNull("Name", row, 3));
6379
6380 if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out var registryRootType))
6381 {
6382 xRegistryValue.SetAttributeValue("Root", registryRootType);
6383 }
6384
6385 if (!row.IsColumnNull(4))
6386 {
6387 var value = row.FieldAsString(4);
6388
6389 if (value.StartsWith("#x", StringComparison.Ordinal))
6390 {
6391 xRegistryValue.SetAttributeValue("Type", "binary");
6392 xRegistryValue.SetAttributeValue("Value", value.Substring(2));
6393 }
6394 else if (value.StartsWith("#%", StringComparison.Ordinal))
6395 {
6396 xRegistryValue.SetAttributeValue("Type", "expandable");
6397 xRegistryValue.SetAttributeValue("Value", value.Substring(2));
6398 }
6399 else if (value.StartsWith("#", StringComparison.Ordinal) && !value.StartsWith("##", StringComparison.Ordinal))
6400 {
6401 xRegistryValue.SetAttributeValue("Type", "integer");
6402 xRegistryValue.SetAttributeValue("Value", value.Substring(1));
6403 }
6404 else
6405 {
6406 if (value.StartsWith("##", StringComparison.Ordinal))
6407 {
6408 value = value.Substring(1);
6409 }
6410
6411 if (0 <= value.IndexOf("[~]", StringComparison.Ordinal))
6412 {
6413 xRegistryValue.SetAttributeValue("Type", "multiString");
6414
6415 if ("[~]" == value)
6416 {
6417 value = String.Empty;
6418 }
6419 else if (value.StartsWith("[~]", StringComparison.Ordinal) && value.EndsWith("[~]", StringComparison.Ordinal))
6420 {
6421 value = value.Substring(3, value.Length - 6);
6422 }
6423 else if (value.StartsWith("[~]", StringComparison.Ordinal))
6424 {
6425 xRegistryValue.SetAttributeValue("Action", "append");
6426 value = value.Substring(3);
6427 }
6428 else if (value.EndsWith("[~]", StringComparison.Ordinal))
6429 {
6430 xRegistryValue.SetAttributeValue("Action", "prepend");
6431 value = value.Substring(0, value.Length - 3);
6432 }
6433
6434 var multiValues = NullSplitter.Split(value);
6435 foreach (var multiValue in multiValues)
6436 {
6437 var xMultiStringValue = new XElement(Names.MultiStringElement,
6438 new XAttribute("Value", multiValue));
6439
6440 xRegistryValue.Add(xMultiStringValue);
6441 }
6442 }
6443 else
6444 {
6445 xRegistryValue.SetAttributeValue("Type", "string");
6446 xRegistryValue.SetAttributeValue("Value", value);
6447 }
6448 }
6449 }
6450 else
6451 {
6452 xRegistryValue.SetAttributeValue("Type", "string");
6453 xRegistryValue.SetAttributeValue("Value", String.Empty);
6454 }
6455
6456 this.IndexElement(row, xRegistryValue);
6457 }
6458 }
6459 }
6460
6461 /// <summary>
6462 /// Decompile the RegLocator table.
6463 /// </summary>
6464 /// <param name="table">The table to decompile.</param>
6465 private void DecompileRegLocatorTable(Table table)
6466 {
6467 foreach (var row in table.Rows)
6468 {
6469 var xRegistrySearch = new XElement(Names.RegistrySearchElement,
6470 new XAttribute("Id", row.FieldAsString(0)),
6471 new XAttribute("Key", row.FieldAsString(2)),
6472 XAttributeIfNotNull("Name", row, 3));
6473
6474 switch (row.FieldAsInteger(1))
6475 {
6476 case WindowsInstallerConstants.MsidbRegistryRootClassesRoot:
6477 xRegistrySearch.SetAttributeValue("Root", "HKCR");
6478 break;
6479 case WindowsInstallerConstants.MsidbRegistryRootCurrentUser:
6480 xRegistrySearch.SetAttributeValue("Root", "HKCU");
6481 break;
6482 case WindowsInstallerConstants.MsidbRegistryRootLocalMachine:
6483 xRegistrySearch.SetAttributeValue("Root", "HKLM");
6484 break;
6485 case WindowsInstallerConstants.MsidbRegistryRootUsers:
6486 xRegistrySearch.SetAttributeValue("Root", "HKU");
6487 break;
6488 default:
6489 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1]));
6490 break;
6491 }
6492
6493 if (row.IsColumnNull(4))
6494 {
6495 xRegistrySearch.SetAttributeValue("Type", "file");
6496 }
6497 else
6498 {
6499 var type = row.FieldAsInteger(4);
6500
6501 if (WindowsInstallerConstants.MsidbLocatorType64bit == (type & WindowsInstallerConstants.MsidbLocatorType64bit))
6502 {
6503 xRegistrySearch.SetAttributeValue("Bitness", "always64");
6504 type &= ~WindowsInstallerConstants.MsidbLocatorType64bit;
6505 }
6506 else
6507 {
6508 xRegistrySearch.SetAttributeValue("Bitness", "always32");
6509 }
6510
6511 switch (type)
6512 {
6513 case WindowsInstallerConstants.MsidbLocatorTypeDirectory:
6514 xRegistrySearch.SetAttributeValue("Type", "directory");
6515 break;
6516 case WindowsInstallerConstants.MsidbLocatorTypeFileName:
6517 xRegistrySearch.SetAttributeValue("Type", "file");
6518 break;
6519 case WindowsInstallerConstants.MsidbLocatorTypeRawValue:
6520 xRegistrySearch.SetAttributeValue("Type", "raw");
6521 break;
6522 default:
6523 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4]));
6524 break;
6525 }
6526 }
6527
6528 this.IndexElement(row, xRegistrySearch);
6529 }
6530 }
6531
6532 /// <summary>
6533 /// Decompile the RemoveFile table.
6534 /// </summary>
6535 /// <param name="table">The table to decompile.</param>
6536 private void DecompileRemoveFileTable(Table table)
6537 {
6538 foreach (var row in table.Rows)
6539 {
6540 if (row.IsColumnNull(2))
6541 {
6542 var xRemoveFolder = new XElement(Names.RemoveFolderElement,
6543 new XAttribute("Id", row.FieldAsString(0)));
6544
6545 // directory/property is set in FinalizeDecompile
6546
6547 switch (row.FieldAsInteger(4))
6548 {
6549 case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnInstall:
6550 xRemoveFolder.SetAttributeValue("On", "install");
6551 break;
6552 case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnRemove:
6553 xRemoveFolder.SetAttributeValue("On", "uninstall");
6554 break;
6555 case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnBoth:
6556 xRemoveFolder.SetAttributeValue("On", "both");
6557 break;
6558 default:
6559 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4]));
6560 break;
6561 }
6562
6563 this.AddChildToParent("Component", xRemoveFolder, row, 1);
6564 this.IndexElement(row, xRemoveFolder);
6565 }
6566 else
6567 {
6568 var xRemoveFile = new XElement(Names.RemoveFileElement,
6569 new XAttribute("Id", row.FieldAsString(0)));
6570
6571 var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(2));
6572 if (null != names[0] && null != names[1])
6573 {
6574 xRemoveFile.SetAttributeValue("ShortName", names[0]);
6575 xRemoveFile.SetAttributeValue("Name", names[1]);
6576 }
6577 else if (null != names[0])
6578 {
6579 xRemoveFile.SetAttributeValue("Name", names[0]);
6580 }
6581
6582 // directory/property is set in FinalizeDecompile
6583
6584 switch (row.FieldAsInteger(4))
6585 {
6586 case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnInstall:
6587 xRemoveFile.SetAttributeValue("On", "install");
6588 break;
6589 case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnRemove:
6590 xRemoveFile.SetAttributeValue("On", "uninstall");
6591 break;
6592 case WindowsInstallerConstants.MsidbRemoveFileInstallModeOnBoth:
6593 xRemoveFile.SetAttributeValue("On", "both");
6594 break;
6595 default:
6596 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4]));
6597 break;
6598 }
6599
6600 this.AddChildToParent("Component", xRemoveFile, row, 1);
6601 this.IndexElement(row, xRemoveFile);
6602 }
6603 }
6604 }
6605
6606 /// <summary>
6607 /// Decompile the RemoveIniFile table.
6608 /// </summary>
6609 /// <param name="table">The table to decompile.</param>
6610 private void DecompileRemoveIniFileTable(Table table)
6611 {
6612 foreach (var row in table.Rows)
6613 {
6614 var xIniFile = new XElement(Names.IniFileElement,
6615 new XAttribute("Id", row.FieldAsString(0)),
6616 XAttributeIfNotNull("Directory", row, 2),
6617 new XAttribute("Section", row.FieldAsString(3)),
6618 new XAttribute("Key", row.FieldAsString(4)),
6619 XAttributeIfNotNull("Value", row, 5));
6620
6621 var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(1));
6622 if (null != names[0] && null != names[1])
6623 {
6624 xIniFile.SetAttributeValue("ShortName", names[0]);
6625 xIniFile.SetAttributeValue("Name", names[1]);
6626 }
6627 else if (null != names[0])
6628 {
6629 xIniFile.SetAttributeValue("Name", names[0]);
6630 }
6631
6632 switch (row.FieldAsInteger(6))
6633 {
6634 case WindowsInstallerConstants.MsidbIniFileActionRemoveLine:
6635 xIniFile.SetAttributeValue("Action", "removeLine");
6636 break;
6637 case WindowsInstallerConstants.MsidbIniFileActionRemoveTag:
6638 xIniFile.SetAttributeValue("Action", "removeTag");
6639 break;
6640 default:
6641 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6]));
6642 break;
6643 }
6644
6645 this.AddChildToParent("Component", xIniFile, row, 7);
6646 }
6647 }
6648
6649 /// <summary>
6650 /// Decompile the RemoveRegistry table.
6651 /// </summary>
6652 /// <param name="table">The table to decompile.</param>
6653 private void DecompileRemoveRegistryTable(Table table)
6654 {
6655 foreach (var row in table.Rows)
6656 {
6657 if ("-" == row.FieldAsString(3))
6658 {
6659 var xRemoveRegistryKey = new XElement(Names.RemoveRegistryKeyElement,
6660 new XAttribute("Id", row.FieldAsString(0)),
6661 new XAttribute("Key", row.FieldAsString(2)),
6662 new XAttribute("Action", "removeOnInstall"));
6663
6664 if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out var registryRootType))
6665 {
6666 xRemoveRegistryKey.SetAttributeValue("Root", registryRootType);
6667 }
6668
6669 this.AddChildToParent("Component", xRemoveRegistryKey, row, 4);
6670 }
6671 else
6672 {
6673 var xRemoveRegistryValue = new XElement(Names.RemoveRegistryValueElement,
6674 new XAttribute("Id", row.FieldAsString(0)),
6675 new XAttribute("Key", row.FieldAsString(2)),
6676 XAttributeIfNotNull("Name", row, 3));
6677
6678 if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out var registryRootType))
6679 {
6680 xRemoveRegistryValue.SetAttributeValue("Root", registryRootType);
6681 }
6682
6683 this.AddChildToParent("Component", xRemoveRegistryValue, row, 4);
6684 }
6685 }
6686 }
6687
6688 /// <summary>
6689 /// Decompile the ReserveCost table.
6690 /// </summary>
6691 /// <param name="table">The table to decompile.</param>
6692 private void DecompileReserveCostTable(Table table)
6693 {
6694 foreach (var row in table.Rows)
6695 {
6696 var xReserveCost = new XElement(Names.ReserveCostElement,
6697 new XAttribute("Id", row.FieldAsString(0)),
6698 XAttributeIfNotNull("Directory", row, 2),
6699 new XAttribute("RunLocal", row.FieldAsString(3)),
6700 new XAttribute("RunFromSource", row.FieldAsString(4)));
6701
6702 this.AddChildToParent("Component", xReserveCost, row, 4);
6703 }
6704 }
6705
6706 /// <summary>
6707 /// Decompile the SelfReg table.
6708 /// </summary>
6709 /// <param name="table">The table to decompile.</param>
6710 private void DecompileSelfRegTable(Table table)
6711 {
6712 foreach (var row in table.Rows)
6713 {
6714 if (this.TryGetIndexedElement("File", out var xFile, row.FieldAsString(0)))
6715 {
6716 xFile.SetAttributeValue("SelfRegCost", row.IsColumnNull(1) ? 0 : row.FieldAsInteger(1));
6717 }
6718 else
6719 {
6720 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "File_", row.FieldAsString(0), "File"));
6721 }
6722 }
6723 }
6724
6725 /// <summary>
6726 /// Decompile the ServiceControl table.
6727 /// </summary>
6728 /// <param name="table">The table to decompile.</param>
6729 private void DecompileServiceControlTable(Table table)
6730 {
6731 foreach (var row in table.Rows)
6732 {
6733 var xServiceControl = new XElement(Names.ServiceControlElement,
6734 new XAttribute("Id", row.FieldAsString(0)),
6735 new XAttribute("Name", row.FieldAsString(1)));
6736
6737 var eventValue = row.FieldAsInteger(2);
6738 if (WindowsInstallerConstants.MsidbServiceControlEventStart == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventStart) &&
6739 WindowsInstallerConstants.MsidbServiceControlEventUninstallStart == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallStart))
6740 {
6741 xServiceControl.SetAttributeValue("Start", "both");
6742 }
6743 else if (WindowsInstallerConstants.MsidbServiceControlEventStart == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventStart))
6744 {
6745 xServiceControl.SetAttributeValue("Start", "install");
6746 }
6747 else if (WindowsInstallerConstants.MsidbServiceControlEventUninstallStart == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallStart))
6748 {
6749 xServiceControl.SetAttributeValue("Start", "uninstall");
6750 }
6751
6752 if (WindowsInstallerConstants.MsidbServiceControlEventStop == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventStop) &&
6753 WindowsInstallerConstants.MsidbServiceControlEventUninstallStop == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallStop))
6754 {
6755 xServiceControl.SetAttributeValue("Stop", "both");
6756 }
6757 else if (WindowsInstallerConstants.MsidbServiceControlEventStop == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventStop))
6758 {
6759 xServiceControl.SetAttributeValue("Stop", "install");
6760 }
6761 else if (WindowsInstallerConstants.MsidbServiceControlEventUninstallStop == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallStop))
6762 {
6763 xServiceControl.SetAttributeValue("Stop", "uninstall");
6764 }
6765
6766 if (WindowsInstallerConstants.MsidbServiceControlEventDelete == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventDelete) &&
6767 WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete))
6768 {
6769 xServiceControl.SetAttributeValue("Remove", "both");
6770 }
6771 else if (WindowsInstallerConstants.MsidbServiceControlEventDelete == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventDelete))
6772 {
6773 xServiceControl.SetAttributeValue("Remove", "install");
6774 }
6775 else if (WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete == (eventValue & WindowsInstallerConstants.MsidbServiceControlEventUninstallDelete))
6776 {
6777 xServiceControl.SetAttributeValue("Remove", "uninstall");
6778 }
6779
6780 if (!row.IsColumnNull(3))
6781 {
6782 var arguments = NullSplitter.Split(row.FieldAsString(3));
6783
6784 foreach (var argument in arguments)
6785 {
6786 var xServiceArgument = new XElement(Names.ServiceArgumentElement,
6787 new XAttribute("Value", argument));
6788
6789 xServiceControl.Add(xServiceArgument);
6790 }
6791 }
6792
6793 if (!row.IsColumnNull(4))
6794 {
6795 xServiceControl.SetAttributeValue("Wait", row.FieldAsInteger(4) == 0 ? "no" : "yes");
6796 }
6797
6798 this.AddChildToParent("Component", xServiceControl, row, 5);
6799 }
6800 }
6801
6802 /// <summary>
6803 /// Decompile the ServiceInstall table.
6804 /// </summary>
6805 /// <param name="table">The table to decompile.</param>
6806 private void DecompileServiceInstallTable(Table table)
6807 {
6808 foreach (var row in table.Rows)
6809 {
6810 var xServiceInstall = new XElement(Names.ServiceInstallElement,
6811 new XAttribute("Id", row.FieldAsString(0)),
6812 new XAttribute("Name", row.FieldAsString(1)),
6813 XAttributeIfNotNull("DisplayName", row, 2),
6814 XAttributeIfNotNull("LoadOrderGroup", row, 6),
6815 XAttributeIfNotNull("Account", row, 8),
6816 XAttributeIfNotNull("Password", row, 9),
6817 XAttributeIfNotNull("Arguments", row, 10),
6818 XAttributeIfNotNull("Description", row, 12));
6819
6820 var serviceType = row.FieldAsInteger(3);
6821 if (WindowsInstallerConstants.MsidbServiceInstallInteractive == (serviceType & WindowsInstallerConstants.MsidbServiceInstallInteractive))
6822 {
6823 xServiceInstall.SetAttributeValue("Interactive", "yes");
6824 }
6825
6826 if (WindowsInstallerConstants.MsidbServiceInstallOwnProcess == (serviceType & WindowsInstallerConstants.MsidbServiceInstallOwnProcess) &&
6827 WindowsInstallerConstants.MsidbServiceInstallShareProcess == (serviceType & WindowsInstallerConstants.MsidbServiceInstallShareProcess))
6828 {
6829 // TODO: warn
6830 }
6831 else if (WindowsInstallerConstants.MsidbServiceInstallOwnProcess == (serviceType & WindowsInstallerConstants.MsidbServiceInstallOwnProcess))
6832 {
6833 xServiceInstall.SetAttributeValue("Type", "ownProcess");
6834 }
6835 else if (WindowsInstallerConstants.MsidbServiceInstallShareProcess == (serviceType & WindowsInstallerConstants.MsidbServiceInstallShareProcess))
6836 {
6837 xServiceInstall.SetAttributeValue("Type", "shareProcess");
6838 }
6839
6840 var startType = row.FieldAsInteger(4);
6841 if (WindowsInstallerConstants.MsidbServiceInstallDisabled == startType)
6842 {
6843 xServiceInstall.SetAttributeValue("Start", "disabled");
6844 }
6845 else if (WindowsInstallerConstants.MsidbServiceInstallDemandStart == startType)
6846 {
6847 xServiceInstall.SetAttributeValue("Start", "demand");
6848 }
6849 else if (WindowsInstallerConstants.MsidbServiceInstallAutoStart == startType)
6850 {
6851 xServiceInstall.SetAttributeValue("Start", "auto");
6852 }
6853 else
6854 {
6855 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4]));
6856 }
6857
6858 var errorControl = row.FieldAsInteger(5);
6859 if (WindowsInstallerConstants.MsidbServiceInstallErrorCritical == (errorControl & WindowsInstallerConstants.MsidbServiceInstallErrorCritical))
6860 {
6861 xServiceInstall.SetAttributeValue("ErrorControl", "critical");
6862 }
6863 else if (WindowsInstallerConstants.MsidbServiceInstallErrorNormal == (errorControl & WindowsInstallerConstants.MsidbServiceInstallErrorNormal))
6864 {
6865 xServiceInstall.SetAttributeValue("ErrorControl", "normal");
6866 }
6867 else
6868 {
6869 xServiceInstall.SetAttributeValue("ErrorControl", "ignore");
6870 }
6871
6872 if (WindowsInstallerConstants.MsidbServiceInstallErrorControlVital == (errorControl & WindowsInstallerConstants.MsidbServiceInstallErrorControlVital))
6873 {
6874 xServiceInstall.SetAttributeValue("Vital", "yes");
6875 }
6876
6877 if (!row.IsColumnNull(7))
6878 {
6879 var dependencies = NullSplitter.Split(row.FieldAsString(7));
6880
6881 foreach (var dependency in dependencies)
6882 {
6883 if (0 < dependency.Length)
6884 {
6885 var xServiceDependency = new XElement(Names.ServiceDependencyElement);
6886
6887 if (dependency.StartsWith("+", StringComparison.Ordinal))
6888 {
6889 xServiceDependency.SetAttributeValue("Group", "yes");
6890 xServiceDependency.SetAttributeValue("Id", dependency.Substring(1));
6891 }
6892 else
6893 {
6894 xServiceDependency.SetAttributeValue("Id", dependency);
6895 }
6896
6897 xServiceInstall.Add(xServiceDependency);
6898 }
6899 }
6900 }
6901
6902 this.AddChildToParent("Component", xServiceInstall, row, 11);
6903 this.IndexElement(row, xServiceInstall);
6904 }
6905 }
6906
6907 /// <summary>
6908 /// Decompile the SFPCatalog table.
6909 /// </summary>
6910 /// <param name="table">The table to decompile.</param>
6911 private void DecompileSFPCatalogTable(Table table)
6912 {
6913 foreach (var row in table.Rows)
6914 {
6915 var xSfpCatalog = new XElement(Names.SFPCatalogElement,
6916 new XAttribute("Name", row.FieldAsString(0)),
6917 new XAttribute("SourceFile", row.FieldAsString(1)));
6918
6919 this.IndexElement(row, xSfpCatalog);
6920 }
6921
6922 // nest the SFPCatalog elements
6923 foreach (var row in table.Rows)
6924 {
6925 var xSfpCatalog = this.GetIndexedElement(row);
6926
6927 if (!row.IsColumnNull(2))
6928 {
6929 if (this.TryGetIndexedElement("SFPCatalog", out var xParentSFPCatalog, row.FieldAsString(2)))
6930 {
6931 xParentSFPCatalog.Add(xSfpCatalog);
6932 }
6933 else
6934 {
6935 xSfpCatalog.SetAttributeValue("Dependency", row.FieldAsString(2));
6936
6937 this.RootElement.Add(xSfpCatalog);
6938 }
6939 }
6940 else
6941 {
6942 this.RootElement.Add(xSfpCatalog);
6943 }
6944 }
6945 }
6946
6947 /// <summary>
6948 /// Decompile the Shortcut table.
6949 /// </summary>
6950 /// <param name="table">The table to decompile.</param>
6951 private void DecompileShortcutTable(Table table)
6952 {
6953 foreach (var row in table.Rows)
6954 {
6955 var xShortcut = new XElement(Names.ShortcutElement,
6956 new XAttribute("Id", row.FieldAsString(0)),
6957 new XAttribute("Directory", row.FieldAsString(1)),
6958 XAttributeIfNotNull("Arguments", row, 5),
6959 XAttributeIfNotNull("Description", row, 6),
6960 XAttributeIfNotNull("Hotkey", row, 7),
6961 XAttributeIfNotNull("Icon", row, 8),
6962 XAttributeIfNotNull("IconIndex", row, 9),
6963 XAttributeIfNotNull("WorkingDirectory", row, 11));
6964
6965 var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(2));
6966 if (null != names[0] && null != names[1])
6967 {
6968 xShortcut.SetAttributeValue("ShortName", names[0]);
6969 xShortcut.SetAttributeValue("Name", names[1]);
6970 }
6971 else if (null != names[0])
6972 {
6973 xShortcut.SetAttributeValue("Name", names[0]);
6974 }
6975
6976 if (!row.IsColumnNull(10))
6977 {
6978 switch (row.FieldAsInteger(10))
6979 {
6980 case 1:
6981 xShortcut.SetAttributeValue("Show", "normal");
6982 break;
6983 case 3:
6984 xShortcut.SetAttributeValue("Show", "maximized");
6985 break;
6986 case 7:
6987 xShortcut.SetAttributeValue("Show", "minimized");
6988 break;
6989 default:
6990 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[10].Column.Name, row[10]));
6991 break;
6992 }
6993 }
6994
6995 // Only try to read the MSI 4.0-specific columns if they actually exist
6996 if (15 < row.Fields.Length)
6997 {
6998 if (!row.IsColumnNull(12))
6999 {
7000 xShortcut.SetAttributeValue("DisplayResourceDll", row.FieldAsString(12));
7001 }
7002
7003 if (null != row[13])
7004 {
7005 xShortcut.SetAttributeValue("DisplayResourceId", row.FieldAsInteger(13));
7006 }
7007
7008 if (null != row[14])
7009 {
7010 xShortcut.SetAttributeValue("DescriptionResourceDll", row.FieldAsString(14));
7011 }
7012
7013 if (null != row[15])
7014 {
7015 xShortcut.SetAttributeValue("DescriptionResourceId", row.FieldAsInteger(15));
7016 }
7017 }
7018
7019 this.AddChildToParent("Component", xShortcut, row, 3);
7020 this.IndexElement(row, xShortcut);
7021 }
7022 }
7023
7024 /// <summary>
7025 /// Decompile the Signature table.
7026 /// </summary>
7027 /// <param name="table">The table to decompile.</param>
7028 private void DecompileSignatureTable(Table table)
7029 {
7030 foreach (var row in table.Rows)
7031 {
7032 var fileSearch = new XElement(Names.FileSearchElement,
7033 new XAttribute("Id", row.FieldAsString(0)),
7034 XAttributeIfNotNull("MinVersion", row, 2),
7035 XAttributeIfNotNull("MaxVersion", row, 3),
7036 XAttributeIfNotNull("MinSize", row, 4),
7037 XAttributeIfNotNull("MaxSize", row, 5),
7038 XAttributeIfNotNull("Languages", row, 8));
7039
7040 var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(1));
7041 if (null != names[0])
7042 {
7043 // it is permissable to just have a long name
7044 if (!this.BackendHelper.IsValidShortFilename(names[0], false) && null == names[1])
7045 {
7046 fileSearch.SetAttributeValue("Name", names[0]);
7047 }
7048 else
7049 {
7050 fileSearch.SetAttributeValue("ShortName", names[0]);
7051 }
7052 }
7053
7054 if (null != names[1])
7055 {
7056 fileSearch.SetAttributeValue("Name", names[1]);
7057 }
7058
7059 if (!row.IsColumnNull(6))
7060 {
7061 fileSearch.SetAttributeValue("MinDate", ConvertIntegerToDateTime(row.FieldAsInteger(6)));
7062 }
7063
7064 if (!row.IsColumnNull(7))
7065 {
7066 fileSearch.SetAttributeValue("MaxDate", ConvertIntegerToDateTime(row.FieldAsInteger(7)));
7067 }
7068
7069 this.IndexElement(row, fileSearch);
7070 }
7071 }
7072
7073 /// <summary>
7074 /// Decompile the TargetFiles_OptionalData table.
7075 /// </summary>
7076 /// <param name="table">The table to decompile.</param>
7077 private void DecompileTargetFiles_OptionalDataTable(Table table)
7078 {
7079 foreach (var row in table.Rows)
7080 {
7081 if (!this.PatchTargetFiles.TryGetValue(row.FieldAsString(0), out var xPatchTargetFile))
7082 {
7083 xPatchTargetFile = new XElement(Names.TargetFileElement,
7084 new XAttribute("Id", row.FieldAsString(1)));
7085
7086 if (this.TryGetIndexedElement("TargetImages", out var xTargetImage, row.FieldAsString(0)))
7087 {
7088 xTargetImage.Add(xPatchTargetFile);
7089 }
7090 else
7091 {
7092 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Target", row.FieldAsString(0), "TargetImages"));
7093 }
7094
7095 this.PatchTargetFiles.Add(row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), xPatchTargetFile);
7096 }
7097
7098 AddSymbolPaths(row, 2, xPatchTargetFile);
7099
7100 if (!row.IsColumnNull(3) && !row.IsColumnNull(4))
7101 {
7102 var ignoreOffsets = row.FieldAsString(3).Split(',');
7103 var ignoreLengths = row.FieldAsString(4).Split(',');
7104
7105 if (ignoreOffsets.Length == ignoreLengths.Length)
7106 {
7107 for (var i = 0; i < ignoreOffsets.Length; i++)
7108 {
7109 var xIgnoreRange = new XElement(Names.IgnoreRangeElement);
7110
7111 if (ignoreOffsets[i].StartsWith("0x", StringComparison.Ordinal))
7112 {
7113 xIgnoreRange.SetAttributeValue("Offset", Convert.ToInt32(ignoreOffsets[i].Substring(2), 16));
7114 }
7115 else
7116 {
7117 xIgnoreRange.SetAttributeValue("Offset", Convert.ToInt32(ignoreOffsets[i], CultureInfo.InvariantCulture));
7118 }
7119
7120 if (ignoreLengths[i].StartsWith("0x", StringComparison.Ordinal))
7121 {
7122 xIgnoreRange.SetAttributeValue("Length", Convert.ToInt32(ignoreLengths[i].Substring(2), 16));
7123 }
7124 else
7125 {
7126 xIgnoreRange.SetAttributeValue("Length", Convert.ToInt32(ignoreLengths[i], CultureInfo.InvariantCulture));
7127 }
7128
7129 xPatchTargetFile.Add(xIgnoreRange);
7130 }
7131 }
7132 else
7133 {
7134 // TODO: warn
7135 }
7136 }
7137 else if (!row.IsColumnNull(3) || !row.IsColumnNull(4))
7138 {
7139 // TODO: warn about mismatch between columns
7140 }
7141
7142 // the RetainOffsets column is handled in FinalizeFamilyFileRangesTable
7143 }
7144 }
7145
7146 /// <summary>
7147 /// Decompile the TargetImages table.
7148 /// </summary>
7149 /// <param name="table">The table to decompile.</param>
7150 private void DecompileTargetImagesTable(Table table)
7151 {
7152 foreach (var row in table.Rows)
7153 {
7154 var xTargetImage = new XElement(Names.TargetImageElement,
7155 new XAttribute("Id", row.FieldAsString(0)),
7156 new XAttribute("SourceFile", row.FieldAsString(1)),
7157 new XAttribute("Order", row.FieldAsInteger(4)),
7158 XAttributeIfNotNull("Validation", row, 5));
7159
7160 AddSymbolPaths(row, 2, xTargetImage);
7161
7162 if (0 != row.FieldAsInteger(6))
7163 {
7164 xTargetImage.SetAttributeValue("IgnoreMissingFiles", "yes");
7165 }
7166
7167 this.AddChildToParent("UpgradedImages", xTargetImage, row, 3);
7168 this.IndexElement(row, xTargetImage);
7169 }
7170 }
7171
7172 /// <summary>
7173 /// Decompile the TextStyle table.
7174 /// </summary>
7175 /// <param name="table">The table to decompile.</param>
7176 private void DecompileTextStyleTable(Table table)
7177 {
7178 foreach (var row in table.Rows)
7179 {
7180 var xTextStyle = new XElement(Names.TextStyleElement,
7181 new XAttribute("Id", row.FieldAsString(0)),
7182 new XAttribute("FaceName", row.FieldAsString(1)),
7183 new XAttribute("Size", row.FieldAsString(2)));
7184
7185 if (!row.IsColumnNull(3))
7186 {
7187 var color = row.FieldAsInteger(3);
7188
7189 xTextStyle.SetAttributeValue("Red", color & 0xFF);
7190 xTextStyle.SetAttributeValue("Green", (color & 0xFF00) >> 8);
7191 xTextStyle.SetAttributeValue("Blue", (color & 0xFF0000) >> 16);
7192 }
7193
7194 if (!row.IsColumnNull(4))
7195 {
7196 var styleBits = row.FieldAsInteger(4);
7197
7198 if (WindowsInstallerConstants.MsidbTextStyleStyleBitsBold == (styleBits & WindowsInstallerConstants.MsidbTextStyleStyleBitsBold))
7199 {
7200 xTextStyle.SetAttributeValue("Bold", "yes");
7201 }
7202
7203 if (WindowsInstallerConstants.MsidbTextStyleStyleBitsItalic == (styleBits & WindowsInstallerConstants.MsidbTextStyleStyleBitsItalic))
7204 {
7205 xTextStyle.SetAttributeValue("Italic", "yes");
7206 }
7207
7208 if (WindowsInstallerConstants.MsidbTextStyleStyleBitsUnderline == (styleBits & WindowsInstallerConstants.MsidbTextStyleStyleBitsUnderline))
7209 {
7210 xTextStyle.SetAttributeValue("Underline", "yes");
7211 }
7212
7213 if (WindowsInstallerConstants.MsidbTextStyleStyleBitsStrike == (styleBits & WindowsInstallerConstants.MsidbTextStyleStyleBitsStrike))
7214 {
7215 xTextStyle.SetAttributeValue("Strike", "yes");
7216 }
7217 }
7218
7219 this.UIElement.Add(xTextStyle);
7220 }
7221 }
7222
7223 /// <summary>
7224 /// Decompile the TypeLib table.
7225 /// </summary>
7226 /// <param name="table">The table to decompile.</param>
7227 private void DecompileTypeLibTable(Table table)
7228 {
7229 foreach (var row in table.Rows)
7230 {
7231 var id = row.FieldAsString(0);
7232 var xTypeLib = new XElement(Names.TypeLibElement,
7233 new XAttribute("Advertise", "yes"),
7234 new XAttribute("Id", id),
7235 new XAttribute("Language", row.FieldAsInteger(1)),
7236 XAttributeIfNotNull("Description", row, 4),
7237 XAttributeIfNotNull("HelpDirectory", row, 5));
7238
7239 if (!row.IsColumnNull(3))
7240 {
7241 var version = row.FieldAsInteger(3);
7242
7243 if (65536 == version)
7244 {
7245 this.Messaging.Write(WarningMessages.PossiblyIncorrectTypelibVersion(row.SourceLineNumbers, id));
7246 }
7247
7248 xTypeLib.SetAttributeValue("MajorVersion", (version & 0xFFFF00) >> 8);
7249 xTypeLib.SetAttributeValue("MinorVersion", version & 0xFF);
7250 }
7251
7252 if (!row.IsColumnNull(7))
7253 {
7254 xTypeLib.SetAttributeValue("Cost", row.FieldAsInteger(7));
7255 }
7256
7257 // nested under the appropriate File element in FinalizeFileTable
7258 this.IndexElement(row, xTypeLib);
7259 }
7260 }
7261
7262 /// <summary>
7263 /// Decompile the Upgrade table.
7264 /// </summary>
7265 /// <param name="table">The table to decompile.</param>
7266 private void DecompileUpgradeTable(Table table)
7267 {
7268 var xUpgrades = new Dictionary<string, XElement>();
7269
7270 foreach (UpgradeRow upgradeRow in table.Rows)
7271 {
7272 if (WixUpgradeConstants.UpgradeDetectedProperty == upgradeRow.ActionProperty || WixUpgradeConstants.DowngradeDetectedProperty == upgradeRow.ActionProperty)
7273 {
7274 continue; // MajorUpgrade rows processed in FinalizeUpgradeTable
7275 }
7276
7277 if (!xUpgrades.TryGetValue(upgradeRow.UpgradeCode, out var xUpgrade))
7278 {
7279 xUpgrade = new XElement(Names.UpgradeElement,
7280 new XAttribute("Id", upgradeRow.UpgradeCode));
7281
7282 this.RootElement.Add(xUpgrade);
7283 xUpgrades.Add(upgradeRow.UpgradeCode, xUpgrade);
7284 }
7285
7286 var xUpgradeVersion = new XElement(Names.UpgradeVersionElement,
7287 new XAttribute("Id", upgradeRow.UpgradeCode),
7288 new XAttribute("Property", upgradeRow.ActionProperty));
7289
7290 if (null != upgradeRow.VersionMin)
7291 {
7292 xUpgradeVersion.SetAttributeValue("Minimum", upgradeRow.VersionMin);
7293 }
7294
7295 if (null != upgradeRow.VersionMax)
7296 {
7297 xUpgradeVersion.SetAttributeValue("Maximum", upgradeRow.VersionMax);
7298 }
7299
7300 if (null != upgradeRow.Language)
7301 {
7302 xUpgradeVersion.SetAttributeValue("Language", upgradeRow.Language);
7303 }
7304
7305 if (WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures))
7306 {
7307 xUpgradeVersion.SetAttributeValue("MigrateFeatures", "yes");
7308 }
7309
7310 if (WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect))
7311 {
7312 xUpgradeVersion.SetAttributeValue("OnlyDetect", "yes");
7313 }
7314
7315 if (WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure))
7316 {
7317 xUpgradeVersion.SetAttributeValue("IgnoreRemoveFailure", "yes");
7318 }
7319
7320 if (WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive))
7321 {
7322 xUpgradeVersion.SetAttributeValue("IncludeMinimum", "yes");
7323 }
7324
7325 if (WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive))
7326 {
7327 xUpgradeVersion.SetAttributeValue("IncludeMaximum", "yes");
7328 }
7329
7330 if (WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive == (upgradeRow.Attributes & WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive))
7331 {
7332 xUpgradeVersion.SetAttributeValue("ExcludeLanguages", "yes");
7333 }
7334
7335 if (null != upgradeRow.Remove)
7336 {
7337 xUpgradeVersion.SetAttributeValue("RemoveFeatures", upgradeRow.Remove);
7338 }
7339
7340 xUpgrade.Add(xUpgradeVersion);
7341 }
7342 }
7343
7344 /// <summary>
7345 /// Decompile the UpgradedFiles_OptionalData table.
7346 /// </summary>
7347 /// <param name="table">The table to decompile.</param>
7348 private void DecompileUpgradedFiles_OptionalDataTable(Table table)
7349 {
7350 foreach (var row in table.Rows)
7351 {
7352 var xUpgradeFile = new XElement(Names.UpgradeFileElement,
7353 new XAttribute("File", row.FieldAsString(1)),
7354 new XAttribute("Ignore", "no"));
7355
7356 AddSymbolPaths(row, 2, xUpgradeFile);
7357
7358 if (!row.IsColumnNull(3) && 1 == row.FieldAsInteger(3))
7359 {
7360 xUpgradeFile.SetAttributeValue("AllowIgnoreOnError", "yes");
7361 }
7362
7363 if (!row.IsColumnNull(4) && 0 != row.FieldAsInteger(4))
7364 {
7365 xUpgradeFile.SetAttributeValue("WholeFile", "yes");
7366 }
7367
7368 this.AddChildToParent("UpgradedImages", xUpgradeFile, row, 0);
7369 }
7370 }
7371
7372 /// <summary>
7373 /// Decompile the UpgradedFilesToIgnore table.
7374 /// </summary>
7375 /// <param name="table">The table to decompile.</param>
7376 private void DecompileUpgradedFilesToIgnoreTable(Table table)
7377 {
7378 foreach (var row in table.Rows)
7379 {
7380 if ("*" != row.FieldAsString(0))
7381 {
7382 var xUpgradeFile = new XElement(Names.UpgradeFileElement,
7383 new XAttribute("File", row.FieldAsString(1)),
7384 new XAttribute("Ignore", "yes"));
7385
7386 this.AddChildToParent("UpgradedImages", xUpgradeFile, row, 0);
7387 }
7388 else
7389 {
7390 this.Messaging.Write(WarningMessages.UnrepresentableColumnValue(row.SourceLineNumbers, table.Name, row.Fields[0].Column.Name, row[0]));
7391 }
7392 }
7393 }
7394
7395 /// <summary>
7396 /// Decompile the UpgradedImages table.
7397 /// </summary>
7398 /// <param name="table">The table to decompile.</param>
7399 private void DecompileUpgradedImagesTable(Table table)
7400 {
7401 foreach (var row in table.Rows)
7402 {
7403 var xUpgradeImage = new XElement(Names.UpgradeImageElement,
7404 new XAttribute("Id", row.FieldAsString(0)),
7405 new XAttribute("SourceFile", row.FieldAsString(1)),
7406 XAttributeIfNotNull("SourcePatch", row, 2));
7407
7408 AddSymbolPaths(row, 3, xUpgradeImage);
7409
7410 this.AddChildToParent("ImageFamilies", xUpgradeImage, row, 4);
7411 this.IndexElement(row, xUpgradeImage);
7412 }
7413 }
7414
7415 private static void AddSymbolPaths(Row row, int column, XElement xParent)
7416 {
7417 if (!row.IsColumnNull(column))
7418 {
7419 var symbolPaths = row.FieldAsString(column).Split(';');
7420
7421 foreach (var symbolPath in symbolPaths)
7422 {
7423 var xSymbolPath = new XElement(Names.SymbolPathElement,
7424 new XAttribute("Path", symbolPath));
7425
7426 xParent.Add(xSymbolPath);
7427 }
7428 }
7429 }
7430
7431 /// <summary>
7432 /// Decompile the UIText table.
7433 /// </summary>
7434 /// <param name="table">The table to decompile.</param>
7435 private void DecompileUITextTable(Table table)
7436 {
7437 foreach (var row in table.Rows)
7438 {
7439 var xUiText = new XElement(Names.UITextElement,
7440 new XAttribute("Id", row.FieldAsString(0)),
7441 new XAttribute("Value", row.FieldAsString(1)));
7442
7443 this.UIElement.Add(xUiText);
7444 }
7445 }
7446
7447 /// <summary>
7448 /// Decompile the Verb table.
7449 /// </summary>
7450 /// <param name="table">The table to decompile.</param>
7451 private void DecompileVerbTable(Table table)
7452 {
7453 foreach (var row in table.Rows)
7454 {
7455 var verb = new XElement(Names.VerbElement,
7456 new XAttribute("Id", row.FieldAsString(1)),
7457 XAttributeIfNotNull("Sequence", row, 2),
7458 XAttributeIfNotNull("Command", row, 3),
7459 XAttributeIfNotNull("Argument", row, 4));
7460
7461 this.IndexElement(row, verb);
7462 }
7463 }
7464
7465 /// <summary>
7466 /// Gets the RegistryRootType from an integer representation of the root.
7467 /// </summary>
7468 /// <param name="sourceLineNumbers">The source line information for the root.</param>
7469 /// <param name="tableName">The name of the table containing the field.</param>
7470 /// <param name="field">The field containing the root value.</param>
7471 /// <param name="registryRootType">The strongly-typed representation of the root.</param>
7472 /// <returns>true if the value could be converted; false otherwise.</returns>
7473 private bool GetRegistryRootType(SourceLineNumber sourceLineNumbers, string tableName, Field field, out string registryRootType)
7474 {
7475 switch (Convert.ToInt32(field.Data))
7476 {
7477 case (-1):
7478 registryRootType = "HKMU";
7479 return true;
7480 case WindowsInstallerConstants.MsidbRegistryRootClassesRoot:
7481 registryRootType = "HKCR";
7482 return true;
7483 case WindowsInstallerConstants.MsidbRegistryRootCurrentUser:
7484 registryRootType = "HKCU";
7485 return true;
7486 case WindowsInstallerConstants.MsidbRegistryRootLocalMachine:
7487 registryRootType = "HKLM";
7488 return true;
7489 case WindowsInstallerConstants.MsidbRegistryRootUsers:
7490 registryRootType = "HKU";
7491 return true;
7492 default:
7493 this.Messaging.Write(WarningMessages.IllegalColumnValue(sourceLineNumbers, tableName, field.Column.Name, field.Data));
7494 registryRootType = null; // assign anything to satisfy the out parameter
7495 return false;
7496 }
7497 }
7498
7499 /// <summary>
7500 /// Set the primary feature for a component.
7501 /// </summary>
7502 /// <param name="row">The row which specifies a primary feature.</param>
7503 /// <param name="featureColumnIndex">The index of the column contaning the feature identifier.</param>
7504 /// <param name="componentColumnIndex">The index of the column containing the component identifier.</param>
7505 private void SetPrimaryFeature(Row row, int featureColumnIndex, int componentColumnIndex)
7506 {
7507 // only products contain primary features
7508 if (OutputType.Product == this.OutputType)
7509 {
7510 var featureField = row.Fields[featureColumnIndex];
7511 var componentField = row.Fields[componentColumnIndex];
7512
7513 if (this.TryGetIndexedElement("FeatureComponents", out var xComponentRef, Convert.ToString(featureField.Data), Convert.ToString(componentField.Data)))
7514 {
7515 xComponentRef.SetAttributeValue("Primary", "yes");
7516 }
7517 else
7518 {
7519 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"));
7520 }
7521 }
7522 }
7523
7524 /// <summary>
7525 /// Checks the InstallExecuteSequence table to determine where RemoveExistingProducts is scheduled and removes it.
7526 /// </summary>
7527 /// <param name="tables">The collection of all tables.</param>
7528 private static string DetermineMajorUpgradeScheduling(TableIndexedCollection tables)
7529 {
7530 var sequenceRemoveExistingProducts = 0;
7531 var sequenceInstallValidate = 0;
7532 var sequenceInstallInitialize = 0;
7533 var sequenceInstallFinalize = 0;
7534 var sequenceInstallExecute = 0;
7535 var sequenceInstallExecuteAgain = 0;
7536
7537 var installExecuteSequenceTable = tables["InstallExecuteSequence"];
7538 if (null != installExecuteSequenceTable)
7539 {
7540 var removeExistingProductsRow = -1;
7541 for (var i = 0; i < installExecuteSequenceTable.Rows.Count; i++)
7542 {
7543 var row = installExecuteSequenceTable.Rows[i];
7544 var action = row.FieldAsString(0);
7545 var sequence = row.FieldAsInteger(2);
7546
7547 switch (action)
7548 {
7549 case "RemoveExistingProducts":
7550 sequenceRemoveExistingProducts = sequence;
7551 removeExistingProductsRow = i;
7552 break;
7553 case "InstallValidate":
7554 sequenceInstallValidate = sequence;
7555 break;
7556 case "InstallInitialize":
7557 sequenceInstallInitialize = sequence;
7558 break;
7559 case "InstallExecute":
7560 sequenceInstallExecute = sequence;
7561 break;
7562 case "InstallExecuteAgain":
7563 sequenceInstallExecuteAgain = sequence;
7564 break;
7565 case "InstallFinalize":
7566 sequenceInstallFinalize = sequence;
7567 break;
7568 }
7569 }
7570
7571 installExecuteSequenceTable.Rows.RemoveAt(removeExistingProductsRow);
7572 }
7573
7574 if (0 != sequenceInstallValidate && sequenceInstallValidate < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallInitialize)
7575 {
7576 return "afterInstallValidate";
7577 }
7578 else if (0 != sequenceInstallInitialize && sequenceInstallInitialize < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallExecute)
7579 {
7580 return "afterInstallInitialize";
7581 }
7582 else if (0 != sequenceInstallExecute && sequenceInstallExecute < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallExecuteAgain)
7583 {
7584 return "afterInstallExecute";
7585 }
7586 else if (0 != sequenceInstallExecuteAgain && sequenceInstallExecuteAgain < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallFinalize)
7587 {
7588 return "afterInstallExecuteAgain";
7589 }
7590 else
7591 {
7592 return "afterInstallFinalize";
7593 }
7594 }
7595 }
7596}
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 @@
1namespace WixToolset.Core.WindowsInstaller.Decompile
2{
3 using System.Xml.Linq;
4
5 internal static class Names
6 {
7 public static readonly XNamespace WxsNamespace = "http://wixtoolset.org/schemas/v4/wxs";
8
9 public static readonly XName WixElement = WxsNamespace + "Wix";
10
11 public static readonly XName PackageElement = WxsNamespace + "Package";
12 public static readonly XName ModuleElement = WxsNamespace + "Module";
13 public static readonly XName PatchCreationElement = WxsNamespace + "PatchCreation";
14
15 public static readonly XName SummaryInformationElement = WxsNamespace + "SummaryInformation";
16
17 public static readonly XName CustomElement = WxsNamespace + "Custom";
18
19 public static readonly XName AdminExecuteSequenceElement = WxsNamespace + "AdminExecuteSequence";
20 public static readonly XName AdminUISequenceElement = WxsNamespace + "AdminUISequence";
21 public static readonly XName AdvertiseExecuteSequenceElement = WxsNamespace + "AdvertiseExecuteSequence";
22 public static readonly XName InstallExecuteSequenceElement = WxsNamespace + "InstallExecuteSequence";
23 public static readonly XName InstallUISequenceElement = WxsNamespace + "InstallUISequence";
24
25 public static readonly XName AppSearchElement = WxsNamespace + "AppSearch";
26
27 public static readonly XName PropertyElement = WxsNamespace + "Property";
28
29 public static readonly XName ProtectRangeElement = WxsNamespace + "ProtectRange";
30 public static readonly XName ProtectFileElement = WxsNamespace + "ProtectFile";
31
32 public static readonly XName FileElement = WxsNamespace + "File";
33
34 public static readonly XName EnsureTableElement = WxsNamespace + "EnsureTable";
35 public static readonly XName PatchInformationElement = WxsNamespace + "PatchInformation";
36
37 public static readonly XName ProgressTextElement = WxsNamespace + "ProgressText";
38 public static readonly XName UIElement = WxsNamespace + "UI";
39
40 public static readonly XName AppIdElement = WxsNamespace + "AppId";
41
42 public static readonly XName ControlElement = WxsNamespace + "Control";
43
44 public static readonly XName BillboardElement = WxsNamespace + "Billboard";
45 public static readonly XName BillboardActionElement = WxsNamespace + "BillboardAction";
46
47 public static readonly XName BinaryElement = WxsNamespace + "Binary";
48
49 public static readonly XName ClassElement = WxsNamespace + "Class";
50
51 public static readonly XName FileTypeMaskElement = WxsNamespace + "FileTypeMask";
52
53 public static readonly XName ComboBoxElement = WxsNamespace + "ComboBox";
54
55 public static readonly XName ListItemElement = WxsNamespace + "ListItem";
56
57 public static readonly XName ConditionElement = WxsNamespace + "Condition";
58 public static readonly XName PublishElement = WxsNamespace + "Publish";
59 public static readonly XName CustomTableElement = WxsNamespace + "CustomTable";
60 public static readonly XName ColumnElement = WxsNamespace + "Column";
61 public static readonly XName RowElement = WxsNamespace + "Row";
62 public static readonly XName DataElement = WxsNamespace + "Data";
63 public static readonly XName CreateFolderElement = WxsNamespace + "CreateFolder";
64
65 public static readonly XName CustomActionElement = WxsNamespace + "CustomAction";
66
67 public static readonly XName ComponentSearchElement = WxsNamespace + "ComponentSearch";
68 public static readonly XName ComponentElement = WxsNamespace + "Component";
69
70 public static readonly XName LevelElement = WxsNamespace + "Level";
71 public static readonly XName DialogElement = WxsNamespace + "Dialog";
72 public static readonly XName StandardDirectoryElement = WxsNamespace + "StandardDirectory";
73 public static readonly XName DirectoryElement = WxsNamespace + "Directory";
74 public static readonly XName DirectorySearchElement = WxsNamespace + "DirectorySearch";
75 public static readonly XName CopyFileElement = WxsNamespace + "CopyFile";
76 public static readonly XName EnvironmentElement = WxsNamespace + "Environment";
77 public static readonly XName ErrorElement = WxsNamespace + "Error";
78 public static readonly XName SubscribeElement = WxsNamespace + "Subscribe";
79 public static readonly XName ExtensionElement = WxsNamespace + "Extension";
80 public static readonly XName ExternalFileElement = WxsNamespace + "ExternalFile";
81 public static readonly XName SymbolPathElement = WxsNamespace + "SymbolPath";
82 public static readonly XName IgnoreRangeElement = WxsNamespace + "IgnoreRange";
83
84 public static readonly XName FeatureElement = WxsNamespace + "Feature";
85 public static readonly XName ComponentRefElement = WxsNamespace + "ComponentRef";
86 public static readonly XName SFPFileElement = WxsNamespace + "SFPFile";
87 public static readonly XName IconElement = WxsNamespace + "Icon";
88 public static readonly XName FamilyElement = WxsNamespace + "Family";
89 public static readonly XName IniFileElement = WxsNamespace + "IniFile";
90 public static readonly XName IniFileSearchElement = WxsNamespace + "IniFileSearch";
91 public static readonly XName IsolateComponentElement = WxsNamespace + "IsolateComponent";
92 public static readonly XName LaunchElement = WxsNamespace + "Launch";
93 public static readonly XName ListBoxElement = WxsNamespace + "ListBox";
94 public static readonly XName ListViewElement = WxsNamespace + "ListView";
95 public static readonly XName PermissionElement = WxsNamespace + "Permission";
96 public static readonly XName MediaElement = WxsNamespace + "Media";
97 public static readonly XName MIMEElement = WxsNamespace + "MIME";
98 public static readonly XName ConfigurationElement = WxsNamespace + "Configuration";
99 public static readonly XName DependencyElement = WxsNamespace + "Dependency";
100 public static readonly XName ExclusionElement = WxsNamespace + "Exclusion";
101 public static readonly XName IgnoreTableElement = WxsNamespace + "IgnoreTable";
102 public static readonly XName SubstitutionElement = WxsNamespace + "Substitution";
103 public static readonly XName DigitalCertificateElement = WxsNamespace + "DigitalCertificate";
104 public static readonly XName DigitalSignatureElement = WxsNamespace + "DigitalSignature";
105 public static readonly XName EmbeddedChainerElement = WxsNamespace + "EmbeddedChainer";
106 public static readonly XName EmbeddedUIElement = WxsNamespace + "EmbeddedUI";
107 public static readonly XName EmbeddedUIResourceElement = WxsNamespace + "EmbeddedUIResource";
108 public static readonly XName PermissionExElement = WxsNamespace + "PermissionEx";
109 public static readonly XName PackageCertificatesElement = WxsNamespace + "PackageCertificates";
110 public static readonly XName PatchCertificatesElement = WxsNamespace + "PatchCertificates";
111 public static readonly XName ShortcutPropertyElement = WxsNamespace + "ShortcutProperty";
112 public static readonly XName ODBCDataSourceElement = WxsNamespace + "ODBCDataSource";
113 public static readonly XName ODBCDriverElement = WxsNamespace + "ODBCDriver";
114 public static readonly XName ODBCTranslatorElement = WxsNamespace + "ODBCTranslator";
115 public static readonly XName PatchMetadataElement = WxsNamespace + "PatchMetadata";
116 public static readonly XName OptimizeCustomActionsElement = WxsNamespace + "OptimizeCustomActions";
117 public static readonly XName CustomPropertyElement = WxsNamespace + "CustomProperty";
118 public static readonly XName PatchSequenceElement = WxsNamespace + "PatchSequence";
119 public static readonly XName ProgIdElement = WxsNamespace + "ProgId";
120 public static readonly XName ReplacePatchElement = WxsNamespace + "ReplacePatch";
121 public static readonly XName TargetProductCodeElement = WxsNamespace + "TargetProductCode";
122 public static readonly XName PatchPropertyElement = WxsNamespace + "PatchProperty";
123 public static readonly XName CategoryElement = WxsNamespace + "Category";
124 public static readonly XName RadioButtonElement = WxsNamespace + "RadioButton";
125 public static readonly XName RadioButtonGroupElement = WxsNamespace + "RadioButtonGroup";
126 public static readonly XName RegistryKeyElement = WxsNamespace + "RegistryKey";
127 public static readonly XName RegistryValueElement = WxsNamespace + "RegistryValue";
128 public static readonly XName MultiStringElement = WxsNamespace + "MultiString";
129 public static readonly XName RegistrySearchElement = WxsNamespace + "RegistrySearch";
130 public static readonly XName RemoveFolderElement = WxsNamespace + "RemoveFolder";
131 public static readonly XName RemoveFileElement = WxsNamespace + "RemoveFile";
132 public static readonly XName RemoveRegistryKeyElement = WxsNamespace + "RemoveRegistryKey";
133 public static readonly XName RemoveRegistryValueElement = WxsNamespace + "RemoveRegistryValue";
134 public static readonly XName ReserveCostElement = WxsNamespace + "ReserveCost";
135 public static readonly XName ServiceControlElement = WxsNamespace + "ServiceControl";
136 public static readonly XName ServiceArgumentElement = WxsNamespace + "ServiceArgument";
137 public static readonly XName ServiceInstallElement = WxsNamespace + "ServiceInstall";
138 public static readonly XName ServiceDependencyElement = WxsNamespace + "ServiceDependency";
139 public static readonly XName SFPCatalogElement = WxsNamespace + "SFPCatalog";
140 public static readonly XName ShortcutElement = WxsNamespace + "Shortcut";
141 public static readonly XName FileSearchElement = WxsNamespace + "FileSearch";
142 public static readonly XName TargetFileElement = WxsNamespace + "TargetFile";
143 public static readonly XName TargetImageElement = WxsNamespace + "TargetImage";
144 public static readonly XName TextStyleElement = WxsNamespace + "TextStyle";
145 public static readonly XName TypeLibElement = WxsNamespace + "TypeLib";
146 public static readonly XName UpgradeElement = WxsNamespace + "Upgrade";
147 public static readonly XName UpgradeVersionElement = WxsNamespace + "UpgradeVersion";
148 public static readonly XName UpgradeFileElement = WxsNamespace + "UpgradeFile";
149 public static readonly XName UpgradeImageElement = WxsNamespace + "UpgradeImage";
150 public static readonly XName UITextElement = WxsNamespace + "UIText";
151 public static readonly XName VerbElement = WxsNamespace + "Verb";
152 public static readonly XName ComplianceCheckElement = WxsNamespace + "ComplianceCheck";
153 public static readonly XName FileSearchRefElement = WxsNamespace + "FileSearchRef";
154 public static readonly XName ComplianceDriveElement = WxsNamespace + "ComplianceDrive";
155 public static readonly XName DirectorySearchRefElement = WxsNamespace + "DirectorySearchRef";
156 public static readonly XName RegistrySearchRefElement = WxsNamespace + "RegistrySearchRef";
157 public static readonly XName MajorUpgradeElement = WxsNamespace + "MajorUpgrade";
158 //public static readonly XName Element = WxsNamespace + "";
159 }
160}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#if DELETE
4
5namespace WixToolset.Core.WindowsInstaller
6{
7 using System;
8 using System.Collections;
9 using System.Collections.Generic;
10 using System.Globalization;
11 using WixToolset.Core.WindowsInstaller.Msi;
12 using WixToolset.Data;
13 using WixToolset.Data.Symbols;
14 using WixToolset.Data.WindowsInstaller;
15 using WixToolset.Data.WindowsInstaller.Rows;
16 using WixToolset.Extensibility;
17 using WixToolset.Extensibility.Services;
18
19 /// <summary>
20 /// Creates a transform by diffing two outputs.
21 /// </summary>
22 public sealed class Differ
23 {
24 private readonly List<IInspectorExtension> inspectorExtensions;
25 private bool showPedanticMessages;
26 private bool suppressKeepingSpecialRows;
27 private bool preserveUnchangedRows;
28 private const char sectionDelimiter = '/';
29 private readonly IMessaging messaging;
30 private SummaryInformationStreams transformSummaryInfo;
31
32 /// <summary>
33 /// Instantiates a new Differ class.
34 /// </summary>
35 public Differ(IMessaging messaging)
36 {
37 this.inspectorExtensions = new List<IInspectorExtension>();
38 this.messaging = messaging;
39 }
40
41 /// <summary>
42 /// Gets or sets the option to show pedantic messages.
43 /// </summary>
44 /// <value>The option to show pedantic messages.</value>
45 public bool ShowPedanticMessages
46 {
47 get { return this.showPedanticMessages; }
48 set { this.showPedanticMessages = value; }
49 }
50
51 /// <summary>
52 /// Gets or sets the option to suppress keeping special rows.
53 /// </summary>
54 /// <value>The option to suppress keeping special rows.</value>
55 public bool SuppressKeepingSpecialRows
56 {
57 get { return this.suppressKeepingSpecialRows; }
58 set { this.suppressKeepingSpecialRows = value; }
59 }
60
61 /// <summary>
62 /// Gets or sets the flag to determine if all rows, even unchanged ones will be persisted in the output.
63 /// </summary>
64 /// <value>The option to keep all rows including unchanged rows.</value>
65 public bool PreserveUnchangedRows
66 {
67 get { return this.preserveUnchangedRows; }
68 set { this.preserveUnchangedRows = value; }
69 }
70
71 /// <summary>
72 /// Adds an extension.
73 /// </summary>
74 /// <param name="extension">The extension to add.</param>
75 public void AddExtension(IInspectorExtension extension)
76 {
77 this.inspectorExtensions.Add(extension);
78 }
79
80 /// <summary>
81 /// Creates a transform by diffing two outputs.
82 /// </summary>
83 /// <param name="targetOutput">The target output.</param>
84 /// <param name="updatedOutput">The updated output.</param>
85 /// <returns>The transform.</returns>
86 public WindowsInstallerData Diff(WindowsInstallerData targetOutput, WindowsInstallerData updatedOutput)
87 {
88 return this.Diff(targetOutput, updatedOutput, 0);
89 }
90
91 /// <summary>
92 /// Creates a transform by diffing two outputs.
93 /// </summary>
94 /// <param name="targetOutput">The target output.</param>
95 /// <param name="updatedOutput">The updated output.</param>
96 /// <param name="validationFlags"></param>
97 /// <returns>The transform.</returns>
98 public WindowsInstallerData Diff(WindowsInstallerData targetOutput, WindowsInstallerData updatedOutput, TransformFlags validationFlags)
99 {
100 WindowsInstallerData transform = new WindowsInstallerData(null);
101 transform.Type = OutputType.Transform;
102 transform.Codepage = updatedOutput.Codepage;
103 this.transformSummaryInfo = new SummaryInformationStreams();
104
105 // compare the codepages
106 if (targetOutput.Codepage != updatedOutput.Codepage && 0 == (TransformFlags.ErrorChangeCodePage & validationFlags))
107 {
108 this.messaging.Write(ErrorMessages.OutputCodepageMismatch(targetOutput.SourceLineNumbers, targetOutput.Codepage, updatedOutput.Codepage));
109 if (null != updatedOutput.SourceLineNumbers)
110 {
111 this.messaging.Write(ErrorMessages.OutputCodepageMismatch2(updatedOutput.SourceLineNumbers));
112 }
113 }
114
115 // compare the output types
116 if (targetOutput.Type != updatedOutput.Type)
117 {
118 throw new WixException(ErrorMessages.OutputTypeMismatch(targetOutput.SourceLineNumbers, targetOutput.Type.ToString(), updatedOutput.Type.ToString()));
119 }
120
121 // compare the contents of the tables
122 foreach (Table targetTable in targetOutput.Tables)
123 {
124 Table updatedTable = updatedOutput.Tables[targetTable.Name];
125 TableOperation operation = TableOperation.None;
126
127 List<Row> rows = this.CompareTables(targetOutput, targetTable, updatedTable, out operation);
128
129 if (TableOperation.Drop == operation)
130 {
131 Table droppedTable = transform.EnsureTable(targetTable.Definition);
132 droppedTable.Operation = TableOperation.Drop;
133 }
134 else if (TableOperation.None == operation)
135 {
136 Table modified = transform.EnsureTable(updatedTable.Definition);
137 rows.ForEach(r => modified.Rows.Add(r));
138 }
139 }
140
141 // added tables
142 foreach (Table updatedTable in updatedOutput.Tables)
143 {
144 if (null == targetOutput.Tables[updatedTable.Name])
145 {
146 Table addedTable = transform.EnsureTable(updatedTable.Definition);
147 addedTable.Operation = TableOperation.Add;
148
149 foreach (Row updatedRow in updatedTable.Rows)
150 {
151 updatedRow.Operation = RowOperation.Add;
152 updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId;
153 addedTable.Rows.Add(updatedRow);
154 }
155 }
156 }
157
158 // set summary information properties
159 if (!this.suppressKeepingSpecialRows)
160 {
161 Table summaryInfoTable = transform.Tables["_SummaryInformation"];
162 this.UpdateTransformSummaryInformationTable(summaryInfoTable, validationFlags);
163 }
164
165 return transform;
166 }
167
168 /// <summary>
169 /// Add a row to the <paramref name="index"/> using the primary key.
170 /// </summary>
171 /// <param name="index">The indexed rows.</param>
172 /// <param name="row">The row to index.</param>
173 private void AddIndexedRow(IDictionary index, Row row)
174 {
175 string primaryKey = row.GetPrimaryKey('/');
176 if (null != primaryKey)
177 {
178 // Overriding WixActionRows have a primary key defined and take precedence in the index.
179 if (row is WixActionRow)
180 {
181 WixActionRow currentRow = (WixActionRow)row;
182 if (index.Contains(primaryKey))
183 {
184 // If the current row is not overridable, see if the indexed row is.
185 if (!currentRow.Overridable)
186 {
187 WixActionRow indexedRow = index[primaryKey] as WixActionRow;
188 if (null != indexedRow && indexedRow.Overridable)
189 {
190 // The indexed key is overridable and should be replaced
191 // (not removed and re-added which results in two Array.Copy
192 // operations for SortedList, or may be re-hashing in other
193 // implementations of IDictionary).
194 index[primaryKey] = currentRow;
195 }
196 }
197
198 // If we got this far, the row does not need to be indexed.
199 return;
200 }
201 }
202
203 // Nothing else should be added more than once.
204 if (!index.Contains(primaryKey))
205 {
206 index.Add(primaryKey, row);
207 }
208 else if (this.showPedanticMessages)
209 {
210 this.messaging.Write(ErrorMessages.DuplicatePrimaryKey(row.SourceLineNumbers, primaryKey, row.Table.Name));
211 }
212 }
213 else // use the string representation of the row as its primary key (it may not be unique)
214 {
215 // this is provided for compatibility with unreal tables with no primary key
216 // all real tables must specify at least one column as the primary key
217 primaryKey = row.ToString();
218 index[primaryKey] = row;
219 }
220 }
221
222 private Row CompareRows(Table targetTable, Row targetRow, Row updatedRow, out RowOperation operation, out bool keepRow)
223 {
224 Row comparedRow = null;
225 keepRow = false;
226 operation = RowOperation.None;
227
228 if (null == targetRow ^ null == updatedRow)
229 {
230 if (null == targetRow)
231 {
232 operation = updatedRow.Operation = RowOperation.Add;
233 comparedRow = updatedRow;
234 }
235 else if (null == updatedRow)
236 {
237 operation = targetRow.Operation = RowOperation.Delete;
238 targetRow.SectionId = targetRow.SectionId + sectionDelimiter;
239 comparedRow = targetRow;
240 keepRow = true;
241 }
242 }
243 else // possibly modified
244 {
245 updatedRow.Operation = RowOperation.None;
246 if (!this.suppressKeepingSpecialRows && "_SummaryInformation" == targetTable.Name)
247 {
248 // ignore rows that shouldn't be in a transform
249 if (Enum.IsDefined(typeof(SummaryInformation.Transform), (int)updatedRow[0]))
250 {
251 updatedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId;
252 comparedRow = updatedRow;
253 keepRow = true;
254 operation = RowOperation.Modify;
255 }
256 }
257 else
258 {
259 if (this.preserveUnchangedRows)
260 {
261 keepRow = true;
262 }
263
264 for (int i = 0; i < updatedRow.Fields.Length; i++)
265 {
266 ColumnDefinition columnDefinition = updatedRow.Fields[i].Column;
267
268 if (!columnDefinition.PrimaryKey)
269 {
270 bool modified = false;
271
272 if (i >= targetRow.Fields.Length)
273 {
274 columnDefinition.Added = true;
275 modified = true;
276 }
277 else if (ColumnType.Number == columnDefinition.Type && !columnDefinition.IsLocalizable)
278 {
279 if (null == targetRow[i] ^ null == updatedRow[i])
280 {
281 modified = true;
282 }
283 else if (null != targetRow[i] && null != updatedRow[i])
284 {
285 modified = ((int)targetRow[i] != (int)updatedRow[i]);
286 }
287 }
288 else if (ColumnType.Preserved == columnDefinition.Type)
289 {
290 updatedRow.Fields[i].PreviousData = (string)targetRow.Fields[i].Data;
291
292 // keep rows containing preserved fields so the historical data is available to the binder
293 keepRow = !this.suppressKeepingSpecialRows;
294 }
295 else if (ColumnType.Object == columnDefinition.Type)
296 {
297 ObjectField targetObjectField = (ObjectField)targetRow.Fields[i];
298 ObjectField updatedObjectField = (ObjectField)updatedRow.Fields[i];
299
300 updatedObjectField.PreviousEmbeddedFileIndex = targetObjectField.EmbeddedFileIndex;
301 updatedObjectField.PreviousBaseUri = targetObjectField.BaseUri;
302
303 // always keep a copy of the previous data even if they are identical
304 // This makes diff.wixmst clean and easier to control patch logic
305 updatedObjectField.PreviousData = (string)targetObjectField.Data;
306
307 // always remember the unresolved data for target build
308 updatedObjectField.UnresolvedPreviousData = (string)targetObjectField.UnresolvedData;
309
310 // keep rows containing object fields so the files can be compared in the binder
311 keepRow = !this.suppressKeepingSpecialRows;
312 }
313 else
314 {
315 modified = ((string)targetRow[i] != (string)updatedRow[i]);
316 }
317
318 if (modified)
319 {
320 if (null != updatedRow.Fields[i].PreviousData)
321 {
322 updatedRow.Fields[i].PreviousData = targetRow.Fields[i].Data.ToString();
323 }
324
325 updatedRow.Fields[i].Modified = true;
326 operation = updatedRow.Operation = RowOperation.Modify;
327 keepRow = true;
328 }
329 }
330 }
331
332 if (keepRow)
333 {
334 comparedRow = updatedRow;
335 comparedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId;
336 }
337 }
338 }
339
340 return comparedRow;
341 }
342
343 private List<Row> CompareTables(WindowsInstallerData targetOutput, Table targetTable, Table updatedTable, out TableOperation operation)
344 {
345 List<Row> rows = new List<Row>();
346 operation = TableOperation.None;
347
348 // dropped tables
349 if (null == updatedTable ^ null == targetTable)
350 {
351 if (null == targetTable)
352 {
353 operation = TableOperation.Add;
354 rows.AddRange(updatedTable.Rows);
355 }
356 else if (null == updatedTable)
357 {
358 operation = TableOperation.Drop;
359 }
360 }
361 else // possibly modified tables
362 {
363 SortedList updatedPrimaryKeys = new SortedList();
364 SortedList targetPrimaryKeys = new SortedList();
365
366 // compare the table definitions
367 if (0 != targetTable.Definition.CompareTo(updatedTable.Definition))
368 {
369 // continue to the next table; may be more mismatches
370 this.messaging.Write(ErrorMessages.DatabaseSchemaMismatch(targetOutput.SourceLineNumbers, targetTable.Name));
371 }
372 else
373 {
374 this.IndexPrimaryKeys(targetTable, targetPrimaryKeys, updatedTable, updatedPrimaryKeys);
375
376 // diff the target and updated rows
377 foreach (DictionaryEntry targetPrimaryKeyEntry in targetPrimaryKeys)
378 {
379 string targetPrimaryKey = (string)targetPrimaryKeyEntry.Key;
380 bool keepRow = false;
381 RowOperation rowOperation = RowOperation.None;
382
383 Row compared = this.CompareRows(targetTable, targetPrimaryKeyEntry.Value as Row, updatedPrimaryKeys[targetPrimaryKey] as Row, out rowOperation, out keepRow);
384
385 if (keepRow)
386 {
387 rows.Add(compared);
388 }
389 }
390
391 // find the inserted rows
392 foreach (DictionaryEntry updatedPrimaryKeyEntry in updatedPrimaryKeys)
393 {
394 string updatedPrimaryKey = (string)updatedPrimaryKeyEntry.Key;
395
396 if (!targetPrimaryKeys.Contains(updatedPrimaryKey))
397 {
398 Row updatedRow = (Row)updatedPrimaryKeyEntry.Value;
399
400 updatedRow.Operation = RowOperation.Add;
401 updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId;
402 rows.Add(updatedRow);
403 }
404 }
405 }
406 }
407
408 return rows;
409 }
410
411 private void IndexPrimaryKeys(Table targetTable, SortedList targetPrimaryKeys, Table updatedTable, SortedList updatedPrimaryKeys)
412 {
413 // index the target rows
414 foreach (Row row in targetTable.Rows)
415 {
416 this.AddIndexedRow(targetPrimaryKeys, row);
417
418 if ("Property" == targetTable.Name)
419 {
420 if ("ProductCode" == (string)row[0])
421 {
422 this.transformSummaryInfo.TargetProductCode = (string)row[1];
423 if ("*" == this.transformSummaryInfo.TargetProductCode)
424 {
425 this.messaging.Write(ErrorMessages.ProductCodeInvalidForTransform(row.SourceLineNumbers));
426 }
427 }
428 else if ("ProductVersion" == (string)row[0])
429 {
430 this.transformSummaryInfo.TargetProductVersion = (string)row[1];
431 }
432 else if ("UpgradeCode" == (string)row[0])
433 {
434 this.transformSummaryInfo.TargetUpgradeCode = (string)row[1];
435 }
436 }
437 else if ("_SummaryInformation" == targetTable.Name)
438 {
439 if (1 == (int)row[0]) // PID_CODEPAGE
440 {
441 this.transformSummaryInfo.TargetSummaryInfoCodepage = (string)row[1];
442 }
443 else if (7 == (int)row[0]) // PID_TEMPLATE
444 {
445 this.transformSummaryInfo.TargetPlatformAndLanguage = (string)row[1];
446 }
447 else if (14 == (int)row[0]) // PID_PAGECOUNT
448 {
449 this.transformSummaryInfo.TargetMinimumVersion = (string)row[1];
450 }
451 }
452 }
453
454 // index the updated rows
455 foreach (Row row in updatedTable.Rows)
456 {
457 this.AddIndexedRow(updatedPrimaryKeys, row);
458
459 if ("Property" == updatedTable.Name)
460 {
461 if ("ProductCode" == (string)row[0])
462 {
463 this.transformSummaryInfo.UpdatedProductCode = (string)row[1];
464 if ("*" == this.transformSummaryInfo.UpdatedProductCode)
465 {
466 this.messaging.Write(ErrorMessages.ProductCodeInvalidForTransform(row.SourceLineNumbers));
467 }
468 }
469 else if ("ProductVersion" == (string)row[0])
470 {
471 this.transformSummaryInfo.UpdatedProductVersion = (string)row[1];
472 }
473 }
474 else if ("_SummaryInformation" == updatedTable.Name)
475 {
476 if (1 == (int)row[0]) // PID_CODEPAGE
477 {
478 this.transformSummaryInfo.UpdatedSummaryInfoCodepage = (string)row[1];
479 }
480 else if (7 == (int)row[0]) // PID_TEMPLATE
481 {
482 this.transformSummaryInfo.UpdatedPlatformAndLanguage = (string)row[1];
483 }
484 else if (14 == (int)row[0]) // PID_PAGECOUNT
485 {
486 this.transformSummaryInfo.UpdatedMinimumVersion = (string)row[1];
487 }
488 }
489 }
490 }
491
492 private void UpdateTransformSummaryInformationTable(Table summaryInfoTable, TransformFlags validationFlags)
493 {
494 // calculate the minimum version of MSI required to process the transform
495 int targetMin;
496 int updatedMin;
497 int minimumVersion = 100;
498
499 if (Int32.TryParse(this.transformSummaryInfo.TargetMinimumVersion, out targetMin) && Int32.TryParse(this.transformSummaryInfo.UpdatedMinimumVersion, out updatedMin))
500 {
501 minimumVersion = Math.Max(targetMin, updatedMin);
502 }
503
504 Hashtable summaryRows = new Hashtable(summaryInfoTable.Rows.Count);
505 foreach (Row row in summaryInfoTable.Rows)
506 {
507 summaryRows[row[0]] = row;
508
509 if ((int)SummaryInformation.Transform.CodePage == (int)row[0])
510 {
511 row.Fields[1].Data = this.transformSummaryInfo.UpdatedSummaryInfoCodepage;
512 row.Fields[1].PreviousData = this.transformSummaryInfo.TargetSummaryInfoCodepage;
513 }
514 else if ((int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0])
515 {
516 row[1] = this.transformSummaryInfo.TargetPlatformAndLanguage;
517 }
518 else if ((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == (int)row[0])
519 {
520 row[1] = this.transformSummaryInfo.UpdatedPlatformAndLanguage;
521 }
522 else if ((int)SummaryInformation.Transform.ProductCodes == (int)row[0])
523 {
524 row[1] = String.Concat(this.transformSummaryInfo.TargetProductCode, this.transformSummaryInfo.TargetProductVersion, ';', this.transformSummaryInfo.UpdatedProductCode, this.transformSummaryInfo.UpdatedProductVersion, ';', this.transformSummaryInfo.TargetUpgradeCode);
525 }
526 else if ((int)SummaryInformation.Transform.InstallerRequirement == (int)row[0])
527 {
528 row[1] = minimumVersion.ToString(CultureInfo.InvariantCulture);
529 }
530 else if ((int)SummaryInformation.Transform.Security == (int)row[0])
531 {
532 row[1] = "4";
533 }
534 }
535
536 if (!summaryRows.Contains((int)SummaryInformation.Transform.TargetPlatformAndLanguage))
537 {
538 Row summaryRow = summaryInfoTable.CreateRow(null);
539 summaryRow[0] = (int)SummaryInformation.Transform.TargetPlatformAndLanguage;
540 summaryRow[1] = this.transformSummaryInfo.TargetPlatformAndLanguage;
541 }
542
543 if (!summaryRows.Contains((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage))
544 {
545 Row summaryRow = summaryInfoTable.CreateRow(null);
546 summaryRow[0] = (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage;
547 summaryRow[1] = this.transformSummaryInfo.UpdatedPlatformAndLanguage;
548 }
549
550 if (!summaryRows.Contains((int)SummaryInformation.Transform.ValidationFlags))
551 {
552 Row summaryRow = summaryInfoTable.CreateRow(null);
553 summaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags;
554 summaryRow[1] = ((int)validationFlags).ToString(CultureInfo.InvariantCulture);
555 }
556
557 if (!summaryRows.Contains((int)SummaryInformation.Transform.InstallerRequirement))
558 {
559 Row summaryRow = summaryInfoTable.CreateRow(null);
560 summaryRow[0] = (int)SummaryInformation.Transform.InstallerRequirement;
561 summaryRow[1] = minimumVersion.ToString(CultureInfo.InvariantCulture);
562 }
563
564 if (!summaryRows.Contains((int)SummaryInformation.Transform.Security))
565 {
566 Row summaryRow = summaryInfoTable.CreateRow(null);
567 summaryRow[0] = (int)SummaryInformation.Transform.Security;
568 summaryRow[1] = "4";
569 }
570 }
571
572 private class SummaryInformationStreams
573 {
574 public string TargetSummaryInfoCodepage
575 { get; set; }
576
577 public string TargetPlatformAndLanguage
578 { get; set; }
579
580 public string TargetProductCode
581 { get; set; }
582
583 public string TargetProductVersion
584 { get; set; }
585
586 public string TargetUpgradeCode
587 { get; set; }
588
589 public string TargetMinimumVersion
590 { get; set; }
591
592 public string UpdatedSummaryInfoCodepage
593 { get; set; }
594
595 public string UpdatedPlatformAndLanguage
596 { get; set; }
597
598 public string UpdatedProductCode
599 { get; set; }
600
601 public string UpdatedProductVersion
602 { get; set; }
603
604 public string UpdatedMinimumVersion
605 { get; set; }
606 }
607 }
608}
609
610#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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.ExtensibilityServices
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using WixToolset.Data;
9 using WixToolset.Data.Symbols;
10 using WixToolset.Data.WindowsInstaller;
11 using WixToolset.Data.WindowsInstaller.Rows;
12 using WixToolset.Extensibility.Data;
13 using WixToolset.Extensibility.Services;
14
15 internal class WindowsInstallerBackendHelper : IWindowsInstallerBackendHelper
16 {
17 private readonly IBackendHelper backendHelper;
18
19 public WindowsInstallerBackendHelper(IServiceProvider serviceProvider)
20 {
21 this.backendHelper = serviceProvider.GetService<IBackendHelper>();
22 }
23
24 #region IBackendHelper interfaces
25
26 public IFileFacade CreateFileFacade(FileSymbol file, AssemblySymbol assembly) => this.backendHelper.CreateFileFacade(file, assembly);
27
28 public IFileFacade CreateFileFacade(FileRow fileRow) => this.backendHelper.CreateFileFacade(fileRow);
29
30 public IFileFacade CreateFileFacadeFromMergeModule(FileSymbol fileSymbol) => this.backendHelper.CreateFileFacadeFromMergeModule(fileSymbol);
31
32 public IFileTransfer CreateFileTransfer(string source, string destination, bool move, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.CreateFileTransfer(source, destination, move, sourceLineNumbers);
33
34 public string CreateGuid() => this.backendHelper.CreateGuid();
35
36 public string CreateGuid(Guid namespaceGuid, string value) => this.backendHelper.CreateGuid(namespaceGuid, value);
37
38 public IResolvedDirectory CreateResolvedDirectory(string directoryParent, string name) => this.backendHelper.CreateResolvedDirectory(directoryParent, name);
39
40 public IReadOnlyList<ITrackedFile> ExtractEmbeddedFiles(IEnumerable<IExpectedExtractFile> embeddedFiles) => this.backendHelper.ExtractEmbeddedFiles(embeddedFiles);
41
42 public string GenerateIdentifier(string prefix, params string[] args) => this.backendHelper.GenerateIdentifier(prefix, args);
43
44 public string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath) => this.backendHelper.GetCanonicalRelativePath(sourceLineNumbers, elementName, attributeName, relativePath);
45
46 public int GetValidCodePage(string value, bool allowNoChange, bool onlyAnsi = false, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.GetValidCodePage(value, allowNoChange, onlyAnsi, sourceLineNumbers);
47
48 public string GetMsiFileName(string value, bool source, bool longName) => this.backendHelper.GetMsiFileName(value, source, longName);
49
50 public bool IsValidBinderVariable(string variable) => this.backendHelper.IsValidBinderVariable(variable);
51
52 public bool IsValidFourPartVersion(string version) => this.backendHelper.IsValidFourPartVersion(version);
53
54 public bool IsValidIdentifier(string id) => this.backendHelper.IsValidIdentifier(id);
55
56 public bool IsValidLongFilename(string filename, bool allowWildcards, bool allowRelative) => this.backendHelper.IsValidLongFilename(filename, allowWildcards, allowRelative);
57
58 public bool IsValidShortFilename(string filename, bool allowWildcards) => this.backendHelper.IsValidShortFilename(filename, allowWildcards);
59
60 public void ResolveDelayedFields(IEnumerable<IDelayedField> delayedFields, Dictionary<string, string> variableCache) => this.backendHelper.ResolveDelayedFields(delayedFields, variableCache);
61
62 public string[] SplitMsiFileName(string value) => this.backendHelper.SplitMsiFileName(value);
63
64 public ITrackedFile TrackFile(string path, TrackedFileType type, SourceLineNumber sourceLineNumbers = null) => this.backendHelper.TrackFile(path, type, sourceLineNumbers);
65
66 #endregion
67
68 #region IWindowsInstallerBackendHelper interfaces
69
70 public Row CreateRow(IntermediateSection section, IntermediateSymbol symbol, WindowsInstallerData data, TableDefinition tableDefinition)
71 {
72 var table = data.EnsureTable(tableDefinition);
73
74 var row = table.CreateRow(symbol.SourceLineNumbers);
75 row.SectionId = section.Id;
76
77 return row;
78 }
79
80 public bool TryAddSymbolToMatchingTableDefinitions(IntermediateSection section, IntermediateSymbol symbol, WindowsInstallerData data, TableDefinitionCollection tableDefinitions)
81 {
82 var tableDefinition = tableDefinitions.FirstOrDefault(t => t.SymbolDefinition?.Name == symbol.Definition.Name);
83 if (tableDefinition == null)
84 {
85 return false;
86 }
87
88 var row = this.CreateRow(section, symbol, data, tableDefinition);
89 var rowOffset = 0;
90
91 if (tableDefinition.SymbolIdIsPrimaryKey)
92 {
93 row[0] = symbol.Id.Id;
94 rowOffset = 1;
95 }
96
97 for (var i = 0; i < symbol.Fields.Length; ++i)
98 {
99 if (i < tableDefinition.Columns.Length)
100 {
101 var column = tableDefinition.Columns[i + rowOffset];
102
103 switch (column.Type)
104 {
105 case ColumnType.Number:
106 row[i + rowOffset] = column.Nullable ? symbol.AsNullableNumber(i) : symbol.AsNumber(i);
107 break;
108
109 default:
110 row[i + rowOffset] = symbol.AsString(i);
111 break;
112 }
113 }
114 }
115
116 return true;
117 }
118
119 #endregion
120 }
121}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Inscribe
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.IO;
9 using System.Runtime.InteropServices;
10 using System.Security.Cryptography.X509Certificates;
11 using WixToolset.Core.Native.Msi;
12 using WixToolset.Core.WindowsInstaller.Bind;
13 using WixToolset.Data;
14 using WixToolset.Data.WindowsInstaller;
15 using WixToolset.Extensibility.Data;
16 using WixToolset.Extensibility.Services;
17
18 internal class InscribeMsiPackageCommand
19 {
20 public InscribeMsiPackageCommand(IInscribeContext context)
21 {
22 this.Context = context;
23 this.Messaging = context.ServiceProvider.GetService<IMessaging>();
24 this.WindowsInstallerBackendHelper = context.ServiceProvider.GetService<IWindowsInstallerBackendHelper>();
25 this.TableDefinitions = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All);
26 }
27
28 private IInscribeContext Context { get; }
29
30 private IMessaging Messaging { get; }
31
32 private IWindowsInstallerBackendHelper WindowsInstallerBackendHelper { get; }
33
34 private TableDefinitionCollection TableDefinitions { get; }
35
36 public bool Execute()
37 {
38 // 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
39 var foundUnsignedExternals = false;
40 var shouldCommit = false;
41
42 var attributes = File.GetAttributes(this.Context.InputFilePath);
43 if (FileAttributes.ReadOnly == (attributes & FileAttributes.ReadOnly))
44 {
45 this.Messaging.Write(ErrorMessages.ReadOnlyOutputFile(this.Context.InputFilePath));
46 return shouldCommit;
47 }
48
49 using (var database = new Database(this.Context.InputFilePath, OpenDatabase.Transact))
50 {
51 // Just use the English codepage, because the tables we're importing only have binary streams / MSI identifiers / other non-localizable content
52 var codepage = 1252;
53
54 // list of certificates for this database (hash/identifier)
55 var certificates = new Dictionary<string, string>();
56
57 // Reset the in-memory tables for this new database
58 var digitalSignatureTable = new Table(this.TableDefinitions["MsiDigitalSignature"]);
59 var digitalCertificateTable = new Table(this.TableDefinitions["MsiDigitalCertificate"]);
60
61 // If any digital signature records exist that are not of the media type, preserve them
62 if (database.TableExists("MsiDigitalSignature"))
63 {
64 using (var digitalSignatureView = database.OpenExecuteView("SELECT `Table`, `SignObject`, `DigitalCertificate_`, `Hash` FROM `MsiDigitalSignature` WHERE `Table` <> 'Media'"))
65 {
66 foreach (var digitalSignatureRecord in digitalSignatureView.Records)
67 {
68 Row digitalSignatureRow = null;
69 digitalSignatureRow = digitalSignatureTable.CreateRow(null);
70
71 var table = digitalSignatureRecord.GetString(0);
72 var signObject = digitalSignatureRecord.GetString(1);
73
74 digitalSignatureRow[0] = table;
75 digitalSignatureRow[1] = signObject;
76 digitalSignatureRow[2] = digitalSignatureRecord.GetString(2);
77
78 if (false == digitalSignatureRecord.IsNull(3))
79 {
80 // Export to a file, because the MSI API's require us to provide a file path on disk
81 var hashPath = Path.Combine(this.Context.IntermediateFolder, "MsiDigitalSignature");
82 var hashFileName = String.Concat(table, ".", signObject, ".bin");
83
84 Directory.CreateDirectory(hashPath);
85 hashPath = Path.Combine(hashPath, hashFileName);
86
87 using (var fs = File.Create(hashPath))
88 {
89 int bytesRead;
90 var buffer = new byte[1024 * 4];
91
92 while (0 != (bytesRead = digitalSignatureRecord.GetStream(3, buffer, buffer.Length)))
93 {
94 fs.Write(buffer, 0, bytesRead);
95 }
96 }
97
98 digitalSignatureRow[3] = hashFileName;
99 }
100 }
101 }
102 }
103
104 // If any digital certificates exist, extract and preserve them
105 if (database.TableExists("MsiDigitalCertificate"))
106 {
107 using (var digitalCertificateView = database.OpenExecuteView("SELECT * FROM `MsiDigitalCertificate`"))
108 {
109 foreach (var digitalCertificateRecord in digitalCertificateView.Records)
110 {
111 var certificateId = digitalCertificateRecord.GetString(1); // get the identifier of the certificate
112
113 // Export to a file, because the MSI API's require us to provide a file path on disk
114 var certPath = Path.Combine(this.Context.IntermediateFolder, "MsiDigitalCertificate");
115 Directory.CreateDirectory(certPath);
116 certPath = Path.Combine(certPath, String.Concat(certificateId, ".cer"));
117
118 using (var fs = File.Create(certPath))
119 {
120 int bytesRead;
121 var buffer = new byte[1024 * 4];
122
123 while (0 != (bytesRead = digitalCertificateRecord.GetStream(2, buffer, buffer.Length)))
124 {
125 fs.Write(buffer, 0, bytesRead);
126 }
127 }
128
129 // Add it to our "add to MsiDigitalCertificate" table dictionary
130 var digitalCertificateRow = digitalCertificateTable.CreateRow(null);
131 digitalCertificateRow[0] = certificateId;
132
133 // Now set the file path on disk where this binary stream will be picked up at import time
134 digitalCertificateRow[1] = String.Concat(certificateId, ".cer");
135
136 // Load the cert to get it's thumbprint
137 var cert = X509Certificate.CreateFromCertFile(certPath);
138 var cert2 = new X509Certificate2(cert);
139
140 certificates.Add(cert2.Thumbprint, certificateId);
141 }
142 }
143 }
144
145 using (var mediaView = database.OpenExecuteView("SELECT * FROM `Media`"))
146 {
147 foreach (var mediaRecord in mediaView.Records)
148 {
149 X509Certificate2 cert2 = null;
150 Row digitalSignatureRow = null;
151
152 var cabName = mediaRecord.GetString(4); // get the name of the cab
153 // If there is no cabinet or it's an internal cab, skip it.
154 if (String.IsNullOrEmpty(cabName) || cabName.StartsWith("#", StringComparison.Ordinal))
155 {
156 continue;
157 }
158
159 var cabId = mediaRecord.GetString(1); // get the ID of the cab
160 var cabPath = Path.Combine(Path.GetDirectoryName(this.Context.InputFilePath), cabName);
161
162 // If the cabs aren't there, throw an error but continue to catch the other errors
163 if (!File.Exists(cabPath))
164 {
165 this.Messaging.Write(ErrorMessages.WixFileNotFound(cabPath));
166 continue;
167 }
168
169 try
170 {
171 // Get the certificate from the cab
172 var signedFileCert = X509Certificate.CreateFromSignedFile(cabPath);
173 cert2 = new X509Certificate2(signedFileCert);
174 }
175 catch (System.Security.Cryptography.CryptographicException e)
176 {
177 var HResult = unchecked((uint)Marshal.GetHRForException(e));
178
179 // If the file has no cert, continue, but flag that we found at least one so we can later give a warning
180 if (0x80092009 == HResult) // CRYPT_E_NO_MATCH
181 {
182 foundUnsignedExternals = true;
183 continue;
184 }
185
186 // todo: exactly which HRESULT corresponds to this issue?
187 // If it's one of these exact platforms, warn the user that it may be due to their OS.
188 if ((5 == Environment.OSVersion.Version.Major && 2 == Environment.OSVersion.Version.Minor) || // W2K3
189 (5 == Environment.OSVersion.Version.Major && 1 == Environment.OSVersion.Version.Minor)) // XP
190 {
191 this.Messaging.Write(ErrorMessages.UnableToGetAuthenticodeCertOfFileDownlevelOS(cabPath, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", HResult)));
192 }
193 else // otherwise, generic error
194 {
195 this.Messaging.Write(ErrorMessages.UnableToGetAuthenticodeCertOfFile(cabPath, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", HResult)));
196 }
197 }
198
199 // If we haven't added this cert to the MsiDigitalCertificate table, set it up to be added
200 if (!certificates.ContainsKey(cert2.Thumbprint))
201 {
202 // generate a stable identifier
203 var certificateGeneratedId = this.WindowsInstallerBackendHelper.GenerateIdentifier("cer", cert2.Thumbprint);
204
205 // Add it to our "add to MsiDigitalCertificate" table dictionary
206 var digitalCertificateRow = digitalCertificateTable.CreateRow(null);
207 digitalCertificateRow[0] = certificateGeneratedId;
208
209 // Export to a file, because the MSI API's require us to provide a file path on disk
210 var certPath = Path.Combine(this.Context.IntermediateFolder, "MsiDigitalCertificate");
211 Directory.CreateDirectory(certPath);
212 certPath = Path.Combine(certPath, String.Concat(cert2.Thumbprint, ".cer"));
213 File.Delete(certPath);
214
215 using (var writer = new BinaryWriter(File.Open(certPath, FileMode.Create)))
216 {
217 writer.Write(cert2.RawData);
218 writer.Close();
219 }
220
221 // Now set the file path on disk where this binary stream will be picked up at import time
222 digitalCertificateRow[1] = String.Concat(cert2.Thumbprint, ".cer");
223
224 certificates.Add(cert2.Thumbprint, certificateGeneratedId);
225 }
226
227 digitalSignatureRow = digitalSignatureTable.CreateRow(null);
228
229 digitalSignatureRow[0] = "Media";
230 digitalSignatureRow[1] = cabId;
231 digitalSignatureRow[2] = certificates[cert2.Thumbprint];
232 }
233 }
234
235 if (digitalCertificateTable.Rows.Count > 0)
236 {
237 var command = new CreateIdtFileCommand(this.Messaging, digitalCertificateTable, codepage, this.Context.IntermediateFolder, true);
238 command.Execute();
239
240 database.Import(command.IdtPath);
241 shouldCommit = true;
242 }
243
244 if (digitalSignatureTable.Rows.Count > 0)
245 {
246 var command = new CreateIdtFileCommand(this.Messaging, digitalSignatureTable, codepage, this.Context.IntermediateFolder, true);
247 command.Execute();
248
249 database.Import(command.IdtPath);
250 shouldCommit = true;
251 }
252
253 // TODO: if we created the table(s), then we should add the _Validation records for them.
254
255 certificates = null;
256
257 // If we did find external cabs but not all of them were signed, give a warning
258 if (foundUnsignedExternals)
259 {
260 this.Messaging.Write(WarningMessages.ExternalCabsAreNotSigned(this.Context.InputFilePath));
261 }
262
263 if (shouldCommit)
264 {
265 database.Commit();
266 }
267 }
268
269 return shouldCommit;
270 }
271 }
272}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset
4{
5 using System;
6 using System.CodeDom.Compiler;
7 using System.Collections;
8 using System.Collections.Generic;
9 using System.Collections.Specialized;
10 using System.Globalization;
11 using System.IO;
12 using System.Text;
13 using System.Text.RegularExpressions;
14 using WixToolset.Data;
15
16 /// <summary>
17 /// Converts a wixout representation of an MSM database into a ComponentGroup the form of WiX source.
18 /// </summary>
19 public sealed class Melter
20 {
21#if TODO_MELT
22 private MelterCore core;
23 private Decompiler decompiler;
24
25 private Wix.ComponentGroup componentGroup;
26 private Wix.DirectoryRef primaryDirectoryRef;
27 private Wix.Fragment fragment;
28
29 private string id;
30 private string moduleId;
31 private const string nullGuid = "{00000000-0000-0000-0000-000000000000}";
32
33 public string Id
34 {
35 get { return this.id; }
36 set { this.id = value; }
37 }
38
39 public Decompiler Decompiler
40 {
41 get { return this.decompiler; }
42 set { this.decompiler = value; }
43 }
44
45 /// <summary>
46 /// Creates a new melter object.
47 /// </summary>
48 /// <param name="decompiler">The decompiler to use during the melting process.</param>
49 /// <param name="id">The Id to use for the ComponentGroup, DirectoryRef, and WixVariables. If null, defaults to the Module's Id</param>
50 public Melter(Decompiler decompiler, string id)
51 {
52 this.core = new MelterCore();
53
54 this.componentGroup = new Wix.ComponentGroup();
55 this.fragment = new Wix.Fragment();
56 this.primaryDirectoryRef = new Wix.DirectoryRef();
57
58 this.decompiler = decompiler;
59 this.id = id;
60
61 if (null == this.decompiler)
62 {
63 this.core.OnMessage(WixErrors.ExpectedDecompiler("The melting process"));
64 }
65 }
66
67 /// <summary>
68 /// Converts a Module wixout into a ComponentGroup.
69 /// </summary>
70 /// <param name="wixout">The output object representing the unbound merge module to melt.</param>
71 /// <returns>The converted Module as a ComponentGroup.</returns>
72 public Wix.Wix Melt(Output wixout)
73 {
74 this.moduleId = GetModuleId(wixout);
75
76 // Assign the default componentGroupId if none was specified
77 if (null == this.id)
78 {
79 this.id = this.moduleId;
80 }
81
82 this.componentGroup.Id = this.id;
83 this.primaryDirectoryRef.Id = this.id;
84
85 PreDecompile(wixout);
86
87 wixout.Type = OutputType.Product;
88 this.decompiler.TreatProductAsModule = true;
89 Wix.Wix wix = this.decompiler.Decompile(wixout);
90
91 if (null == wix)
92 {
93 return wix;
94 }
95
96 ConvertModule(wix);
97
98 return wix;
99 }
100
101 /// <summary>
102 /// Converts a Module to a ComponentGroup and adds all of its relevant elements to the main fragment.
103 /// </summary>
104 /// <param name="wix">The output object representing an unbound merge module.</param>
105 private void ConvertModule(Wix.Wix wix)
106 {
107 Wix.Product product = Melter.GetProduct(wix);
108
109 List<string> customActionsRemoved = new List<string>();
110 Dictionary<Wix.Custom, Wix.InstallExecuteSequence> customsToRemove = new Dictionary<Wix.Custom, Wix.InstallExecuteSequence>();
111
112 foreach (Wix.ISchemaElement child in product.Children)
113 {
114 Wix.Directory childDir = child as Wix.Directory;
115 if (null != childDir)
116 {
117 bool isTargetDir = this.WalkDirectory(childDir);
118 if (isTargetDir)
119 {
120 continue;
121 }
122 }
123 else
124 {
125 Wix.Dependency childDep = child as Wix.Dependency;
126 if (null != childDep)
127 {
128 this.AddPropertyRef(childDep.RequiredId);
129 continue;
130 }
131 else if (child is Wix.Package)
132 {
133 continue;
134 }
135 else if (child is Wix.CustomAction)
136 {
137 Wix.CustomAction customAction = child as Wix.CustomAction;
138 string directoryId;
139 if (StartsWithStandardDirectoryId(customAction.Id, out directoryId) && customAction.Property == customAction.Id)
140 {
141 customActionsRemoved.Add(customAction.Id);
142 continue;
143 }
144 }
145 else if (child is Wix.InstallExecuteSequence)
146 {
147 Wix.InstallExecuteSequence installExecuteSequence = child as Wix.InstallExecuteSequence;
148
149 foreach (Wix.ISchemaElement sequenceChild in installExecuteSequence.Children)
150 {
151 Wix.Custom custom = sequenceChild as Wix.Custom;
152 string directoryId;
153 if (custom != null && StartsWithStandardDirectoryId(custom.Action, out directoryId))
154 {
155 customsToRemove.Add(custom, installExecuteSequence);
156 }
157 }
158 }
159 }
160
161 this.fragment.AddChild(child);
162 }
163
164 // For any customaction that we removed, also remove the scheduling of that action.
165 foreach (Wix.Custom custom in customsToRemove.Keys)
166 {
167 if (customActionsRemoved.Contains(custom.Action))
168 {
169 ((Wix.InstallExecuteSequence)customsToRemove[custom]).RemoveChild(custom);
170 }
171 }
172
173 AddProperty(this.moduleId, this.id);
174
175 wix.RemoveChild(product);
176 wix.AddChild(this.fragment);
177
178 this.fragment.AddChild(this.componentGroup);
179 this.fragment.AddChild(this.primaryDirectoryRef);
180 }
181
182 /// <summary>
183 /// Gets the module from the Wix object.
184 /// </summary>
185 /// <param name="wix">The Wix object.</param>
186 /// <returns>The Module in the Wix object, null if no Module was found</returns>
187 private static Wix.Product GetProduct(Wix.Wix wix)
188 {
189 foreach (Wix.ISchemaElement element in wix.Children)
190 {
191 Wix.Product productElement = element as Wix.Product;
192 if (null != productElement)
193 {
194 return productElement;
195 }
196 }
197 return null;
198 }
199
200 /// <summary>
201 /// Adds a PropertyRef to the main Fragment.
202 /// </summary>
203 /// <param name="propertyRefId">Id of the PropertyRef.</param>
204 private void AddPropertyRef(string propertyRefId)
205 {
206 Wix.PropertyRef propertyRef = new Wix.PropertyRef();
207 propertyRef.Id = propertyRefId;
208 this.fragment.AddChild(propertyRef);
209 }
210
211 /// <summary>
212 /// Adds a Property to the main Fragment.
213 /// </summary>
214 /// <param name="propertyId">Id of the Property.</param>
215 /// <param name="value">Value of the Property.</param>
216 private void AddProperty(string propertyId, string value)
217 {
218 Wix.Property property = new Wix.Property();
219 property.Id = propertyId;
220 property.Value = value;
221 this.fragment.AddChild(property);
222 }
223
224 /// <summary>
225 /// Walks a directory structure obtaining Component Id's and Standard Directory Id's.
226 /// </summary>
227 /// <param name="directory">The Directory to walk.</param>
228 /// <returns>true if the directory is TARGETDIR.</returns>
229 private bool WalkDirectory(Wix.Directory directory)
230 {
231 bool isTargetDir = false;
232 if ("TARGETDIR" == directory.Id)
233 {
234 isTargetDir = true;
235 }
236
237 string standardDirectoryId = null;
238 if (Melter.StartsWithStandardDirectoryId(directory.Id, out standardDirectoryId) && !isTargetDir)
239 {
240 this.AddSetPropertyCustomAction(directory.Id, String.Format(CultureInfo.InvariantCulture, "[{0}]", standardDirectoryId));
241 }
242
243 foreach (Wix.ISchemaElement child in directory.Children)
244 {
245 Wix.Directory childDir = child as Wix.Directory;
246 if (null != childDir)
247 {
248 if (isTargetDir)
249 {
250 this.primaryDirectoryRef.AddChild(child);
251 }
252 this.WalkDirectory(childDir);
253 }
254 else
255 {
256 Wix.Component childComponent = child as Wix.Component;
257 if (null != childComponent)
258 {
259 if (isTargetDir)
260 {
261 this.primaryDirectoryRef.AddChild(child);
262 }
263 this.AddComponentRef(childComponent);
264 }
265 }
266 }
267
268 return isTargetDir;
269 }
270
271 /// <summary>
272 /// Gets the module Id out of the Output object.
273 /// </summary>
274 /// <param name="wixout">The output object.</param>
275 /// <returns>The module Id from the Output object.</returns>
276 private string GetModuleId(Output wixout)
277 {
278 // get the moduleId from the wixout
279 Table moduleSignatureTable = wixout.Tables["ModuleSignature"];
280 if (null == moduleSignatureTable || 0 >= moduleSignatureTable.Rows.Count)
281 {
282 this.core.OnMessage(WixErrors.ExpectedTableInMergeModule("ModuleSignature"));
283 }
284 return moduleSignatureTable.Rows[0].Fields[0].Data.ToString();
285 }
286
287 /// <summary>
288 /// Determines if the directory Id starts with a standard directory id.
289 /// </summary>
290 /// <param name="directoryId">The directory id.</param>
291 /// <param name="standardDirectoryId">The standard directory id.</param>
292 /// <returns>true if the directory starts with a standard directory id.</returns>
293 private static bool StartsWithStandardDirectoryId(string directoryId, out string standardDirectoryId)
294 {
295 standardDirectoryId = null;
296 foreach (string id in WindowsInstallerStandard.GetStandardDirectories())
297 {
298 if (directoryId.StartsWith(id, StringComparison.Ordinal))
299 {
300 standardDirectoryId = id;
301 return true;
302 }
303 }
304 return false;
305 }
306
307 /// <summary>
308 /// Adds a ComponentRef to the main ComponentGroup.
309 /// </summary>
310 /// <param name="component">The component to add.</param>
311 private void AddComponentRef(Wix.Component component)
312 {
313 Wix.ComponentRef componentRef = new Wix.ComponentRef();
314 componentRef.Id = component.Id;
315 this.componentGroup.AddChild(componentRef);
316 }
317
318 /// <summary>
319 /// Adds a SetProperty CA for a Directory.
320 /// </summary>
321 /// <param name="propertyId">The Id of the Property to set.</param>
322 /// <param name="value">The value to set the Property to.</param>
323 private void AddSetPropertyCustomAction(string propertyId, string value)
324 {
325 // Add the action
326 Wix.CustomAction customAction = new Wix.CustomAction();
327 customAction.Id = propertyId;
328 customAction.Property = propertyId;
329 customAction.Value = value;
330 this.fragment.AddChild(customAction);
331
332 // Schedule the action
333 Wix.InstallExecuteSequence installExecuteSequence = new Wix.InstallExecuteSequence();
334 Wix.Custom custom = new Wix.Custom();
335 custom.Action = customAction.Id;
336 custom.Before = "CostInitialize";
337 installExecuteSequence.AddChild(custom);
338 this.fragment.AddChild(installExecuteSequence);
339 }
340
341 /// <summary>
342 /// Does any operations to the wixout that would need to be done before decompiling.
343 /// </summary>
344 /// <param name="wixout">The output object representing the unbound merge module.</param>
345 private void PreDecompile(Output wixout)
346 {
347 string wixVariable = String.Format(CultureInfo.InvariantCulture, "!(wix.{0}", this.id);
348
349 foreach (Table table in wixout.Tables)
350 {
351 // Determine if the table has a feature foreign key
352 bool hasFeatureForeignKey = false;
353 foreach (ColumnDefinition columnDef in table.Definition.Columns)
354 {
355 if (null != columnDef.KeyTable)
356 {
357 string[] keyTables = columnDef.KeyTable.Split(';');
358 foreach (string keyTable in keyTables)
359 {
360 if ("Feature" == keyTable)
361 {
362 hasFeatureForeignKey = true;
363 break;
364 }
365 }
366 }
367 }
368
369 // If this table has no foreign keys to the feature table, skip it.
370 if (!hasFeatureForeignKey)
371 {
372 continue;
373 }
374
375 // Go through all the rows and replace the null guid with the wix variable
376 // for columns that are foreign keys into the feature table.
377 foreach (Row row in table.Rows)
378 {
379 foreach (Field field in row.Fields)
380 {
381 if (null != field.Column.KeyTable)
382 {
383 string[] keyTables = field.Column.KeyTable.Split(';');
384 foreach (string keyTable in keyTables)
385 {
386 if ("Feature" == keyTable)
387 {
388 field.Data = field.Data.ToString().Replace(nullGuid, wixVariable);
389 break;
390 }
391 }
392 }
393 }
394 }
395 }
396 }
397#endif
398 }
399}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset
4{
5 using WixToolset.Data;
6
7 /// <summary>
8 /// Melts a Module Wix document into a ComponentGroup representation.
9 /// </summary>
10 public sealed class MelterCore
11 {
12#if TODO_MELT
13 /// <summary>
14 /// Gets whether the melter core encountered an error while processing.
15 /// </summary>
16 /// <value>Flag if core encountered an error during processing.</value>
17 public bool EncounteredError
18 {
19 get { return Messaging.Instance.EncounteredError; }
20 }
21
22 /// <summary>
23 /// Sends a message to the message delegate if there is one.
24 /// </summary>
25 /// <param name="mea">Message event arguments.</param>
26 public void OnMessage(MessageEventArgs e)
27 {
28 Messaging.Instance.OnMessage(e);
29 }
30#endif
31 }
32}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller
4{
5 using WixToolset.Core.WindowsInstaller.Bind;
6 using WixToolset.Core.WindowsInstaller.Decompile;
7 using WixToolset.Core.WindowsInstaller.Inscribe;
8 using WixToolset.Core.WindowsInstaller.Unbind;
9 using WixToolset.Data;
10 using WixToolset.Extensibility;
11 using WixToolset.Extensibility.Data;
12 using WixToolset.Extensibility.Services;
13
14 internal class MsiBackend : IBackend
15 {
16 public IBindResult Bind(IBindContext context)
17 {
18 var extensionManager = context.ServiceProvider.GetService<IExtensionManager>();
19
20 var backendExtensions = extensionManager.GetServices<IWindowsInstallerBackendBinderExtension>();
21
22 foreach (var extension in backendExtensions)
23 {
24 extension.PreBackendBind(context);
25 }
26
27 IBindResult result = null;
28 var dispose = true;
29 try
30 {
31 var command = new BindDatabaseCommand(context, backendExtensions, "darice.cub");
32 result = command.Execute();
33
34 foreach (var extension in backendExtensions)
35 {
36 extension.PostBackendBind(result);
37 }
38
39 dispose = false;
40 return result;
41 }
42 finally
43 {
44 if (dispose)
45 {
46 result?.Dispose();
47 }
48 }
49 }
50
51 public IDecompileResult Decompile(IDecompileContext context)
52 {
53 var extensionManager = context.ServiceProvider.GetService<IExtensionManager>();
54
55 var backendExtensions = extensionManager.GetServices<IWindowsInstallerBackendDecompilerExtension>();
56
57 foreach (var extension in backendExtensions)
58 {
59 extension.PreBackendDecompile(context);
60 }
61
62 var command = new DecompileMsiOrMsmCommand(context, backendExtensions);
63 var result = command.Execute();
64
65 foreach (var extension in backendExtensions)
66 {
67 extension.PostBackendDecompile(result);
68 }
69
70 return result;
71 }
72
73 public bool Inscribe(IInscribeContext context)
74 {
75 var command = new InscribeMsiPackageCommand(context);
76 return command.Execute();
77 }
78
79 public Intermediate Unbind(IUnbindContext context)
80 {
81 var command = new UnbindMsiOrMsmCommand(context);
82 return command.Execute();
83 }
84 }
85}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller
4{
5 using WixToolset.Core.WindowsInstaller.Bind;
6 using WixToolset.Core.WindowsInstaller.Decompile;
7 using WixToolset.Core.WindowsInstaller.Unbind;
8 using WixToolset.Data;
9 using WixToolset.Extensibility;
10 using WixToolset.Extensibility.Data;
11 using WixToolset.Extensibility.Services;
12
13 internal class MsmBackend : IBackend
14 {
15 public IBindResult Bind(IBindContext context)
16 {
17 var extensionManager = context.ServiceProvider.GetService<IExtensionManager>();
18
19 var backendExtensions = extensionManager.GetServices<IWindowsInstallerBackendBinderExtension>();
20
21 foreach (var extension in backendExtensions)
22 {
23 extension.PreBackendBind(context);
24 }
25
26 IBindResult result = null;
27 try
28 {
29 var command = new BindDatabaseCommand(context, backendExtensions, "mergemod.cub");
30 result = command.Execute();
31
32 foreach (var extension in backendExtensions)
33 {
34 extension.PostBackendBind(result);
35 }
36
37 return result;
38 }
39 catch
40 {
41 result?.Dispose();
42 throw;
43 }
44 }
45
46 public IDecompileResult Decompile(IDecompileContext context)
47 {
48 var extensionManager = context.ServiceProvider.GetService<IExtensionManager>();
49
50 var backendExtensions = extensionManager.GetServices<IWindowsInstallerBackendDecompilerExtension>();
51
52 foreach (var extension in backendExtensions)
53 {
54 extension.PreBackendDecompile(context);
55 }
56
57 var command = new DecompileMsiOrMsmCommand(context, backendExtensions);
58 var result = command.Execute();
59
60 foreach (var extension in backendExtensions)
61 {
62 extension.PostBackendDecompile(result);
63 }
64
65 return result;
66 }
67
68 public bool Inscribe(IInscribeContext context) => false;
69
70 public Intermediate Unbind(IUnbindContext context)
71 {
72 var command = new UnbindMsiOrMsmCommand(context);
73 return command.Execute();
74 }
75 }
76}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 using WixToolset.Core.WindowsInstaller.Bind;
10 using WixToolset.Core.Native.Msi;
11 using WixToolset.Core.WindowsInstaller.Unbind;
12 using WixToolset.Data;
13 using WixToolset.Data.Symbols;
14 using WixToolset.Data.WindowsInstaller;
15 using WixToolset.Extensibility;
16 using WixToolset.Extensibility.Data;
17 using WixToolset.Extensibility.Services;
18
19 internal class MspBackend : IBackend
20 {
21 public IBindResult Bind(IBindContext context)
22 {
23 var messaging = context.ServiceProvider.GetService<IMessaging>();
24
25 var backendHelper = context.ServiceProvider.GetService<IBackendHelper>();
26
27 var extensionManager = context.ServiceProvider.GetService<IExtensionManager>();
28
29 var backendExtensions = extensionManager.GetServices<IWindowsInstallerBackendBinderExtension>();
30
31 foreach (var extension in backendExtensions)
32 {
33 extension.PreBackendBind(context);
34 }
35
36 // Create transforms named in patch transforms.
37 IEnumerable<PatchTransform> patchTransforms;
38 {
39 var command = new CreatePatchTransformsCommand(messaging, backendHelper, context.IntermediateRepresentation, context.IntermediateFolder);
40 patchTransforms = command.Execute();
41 }
42
43 // Enhance the intermediate by attaching the created patch transforms.
44 IEnumerable<SubStorage> subStorages;
45 {
46 var command = new AttachPatchTransformsCommand(messaging, backendHelper, context.IntermediateRepresentation, patchTransforms);
47 subStorages = command.Execute();
48 }
49
50 // Create WindowsInstallerData with patch metdata and transforms as sub-storages
51 // Create MSP from WindowsInstallerData
52 IBindResult result = null;
53 try
54 {
55 var command = new BindDatabaseCommand(context, backendExtensions, subStorages, null);
56 result = command.Execute();
57
58 foreach (var extension in backendExtensions)
59 {
60 extension.PostBackendBind(result);
61 }
62
63 return result;
64 }
65 catch
66 {
67 result?.Dispose();
68 throw;
69 }
70 }
71
72 public IDecompileResult Decompile(IDecompileContext context) => throw new NotImplementedException();
73
74 public bool Inscribe(IInscribeContext context) => throw new NotImplementedException();
75
76 public Intermediate Unbind(IUnbindContext context)
77 {
78#if TODO_PATCHING
79 Output patch;
80
81 // patch files are essentially database files (use a special flag to let the API know its a patch file)
82 try
83 {
84 using (Database database = new Database(context.InputFilePath, OpenDatabase.ReadOnly | OpenDatabase.OpenPatchFile))
85 {
86 var unbindCommand = new UnbindDatabaseCommand(context.Messaging, database, context.InputFilePath, OutputType.Patch, context.ExportBasePath, context.IntermediateFolder, context.IsAdminImage, context.SuppressDemodularization, skipSummaryInfo: false);
87 patch = unbindCommand.Execute();
88 }
89 }
90 catch (Win32Exception e)
91 {
92 if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED
93 {
94 throw new WixException(WixErrors.OpenDatabaseFailed(context.InputFilePath));
95 }
96
97 throw;
98 }
99
100 // retrieve the transforms (they are in substorages)
101 using (Storage storage = Storage.Open(context.InputFilePath, StorageMode.Read | StorageMode.ShareDenyWrite))
102 {
103 Table summaryInformationTable = patch.Tables["_SummaryInformation"];
104 foreach (Row row in summaryInformationTable.Rows)
105 {
106 if (8 == (int)row[0]) // PID_LASTAUTHOR
107 {
108 string value = (string)row[1];
109
110 foreach (string decoratedSubStorageName in value.Split(';'))
111 {
112 string subStorageName = decoratedSubStorageName.Substring(1);
113 string transformFile = Path.Combine(context.IntermediateFolder, String.Concat("Transform", Path.DirectorySeparatorChar, subStorageName, ".mst"));
114
115 // ensure the parent directory exists
116 Directory.CreateDirectory(Path.GetDirectoryName(transformFile));
117
118 // copy the substorage to a new storage for the transform file
119 using (Storage subStorage = storage.OpenStorage(subStorageName))
120 {
121 using (Storage transformStorage = Storage.CreateDocFile(transformFile, StorageMode.ReadWrite | StorageMode.ShareExclusive | StorageMode.Create))
122 {
123 subStorage.CopyTo(transformStorage);
124 }
125 }
126
127 // unbind the transform
128 var unbindCommand= new UnbindTransformCommand(context.Messaging, transformFile, (null == context.ExportBasePath ? null : Path.Combine(context.ExportBasePath, subStorageName)), context.IntermediateFolder);
129 var transform = unbindCommand.Execute();
130
131 patch.SubStorages.Add(new SubStorage(subStorageName, transform));
132 }
133
134 break;
135 }
136 }
137 }
138
139 // extract the files from the cabinets
140 // TODO: use per-transform export paths for support of multi-product patches
141 if (null != context.ExportBasePath && !context.SuppressExtractCabinets)
142 {
143 using (Database database = new Database(context.InputFilePath, OpenDatabase.ReadOnly | OpenDatabase.OpenPatchFile))
144 {
145 foreach (SubStorage subStorage in patch.SubStorages)
146 {
147 // only patch transforms should carry files
148 if (subStorage.Name.StartsWith("#", StringComparison.Ordinal))
149 {
150 var extractCommand = new ExtractCabinetsCommand(subStorage.Data, database, context.InputFilePath, context.ExportBasePath, context.IntermediateFolder);
151 extractCommand.Execute();
152 }
153 }
154 }
155 }
156
157 return patch;
158#endif
159 throw new NotImplementedException();
160 }
161 }
162}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller
4{
5 using System;
6 using WixToolset.Core.WindowsInstaller.Unbind;
7 using WixToolset.Data;
8 using WixToolset.Extensibility;
9 using WixToolset.Extensibility.Data;
10
11 internal class MstBackend : IBackend
12 {
13 public IBindResult Bind(IBindContext context)
14 {
15#if TODO_PATCHING
16 var command = new BindTransformCommand();
17 command.Extensions = context.Extensions;
18 command.TempFilesLocation = context.IntermediateFolder;
19 command.Transform = context.IntermediateRepresentation;
20 command.OutputPath = context.OutputPath;
21 command.Execute();
22
23 return new BindResult(Array.Empty<FileTransfer>(), Array.Empty<string>());
24#endif
25 throw new NotImplementedException();
26 }
27
28 public IDecompileResult Decompile(IDecompileContext context)
29 {
30 throw new NotImplementedException();
31 }
32
33 public bool Inscribe(IInscribeContext context)
34 {
35 throw new NotImplementedException();
36 }
37
38 public Intermediate Unbind(IUnbindContext context)
39 {
40 var command = new UnbindMsiOrMsmCommand(context);
41 return command.Execute();
42 }
43 }
44} \ 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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller
4{
5 using System;
6 using System.Collections.Generic;
7 using WixToolset.Data.WindowsInstaller;
8
9 /// <summary>
10 /// A dictionary of rows. Unlike the RowIndexedList this
11 /// will throw when multiple rows with the same key are added.
12 /// </summary>
13 internal sealed class RowDictionary<T> : Dictionary<string, T> where T : Row
14 {
15 /// <summary>
16 /// Creates an empty <see cref="RowDictionary{T}"/>.
17 /// </summary>
18 public RowDictionary()
19 : base(StringComparer.InvariantCulture)
20 {
21 }
22
23 /// <summary>
24 /// Creates and populates a <see cref="RowDictionary{T}"/> with the rows from the given <see cref="Table"/>.
25 /// </summary>
26 /// <param name="table">The table to index.</param>
27 /// <remarks>
28 /// Rows added to the index are not automatically added to the given <paramref name="table"/>.
29 /// </remarks>
30 public RowDictionary(Table table)
31 : this()
32 {
33 if (null != table)
34 {
35 foreach (T row in table.Rows)
36 {
37 this.Add(row);
38 }
39 }
40 }
41
42 /// <summary>
43 /// Adds a row to the dictionary using the row key.
44 /// </summary>
45 /// <param name="row">Row to add to the dictionary.</param>
46 public void Add(T row)
47 {
48 this.Add(row.GetKey(), row);
49 }
50
51 /// <summary>
52 /// Gets the row by integer key.
53 /// </summary>
54 /// <param name="key">Integer key to look up.</param>
55 /// <returns>Row or null if key is not found.</returns>
56 public T Get(int key)
57 {
58 return this.Get(key.ToString());
59 }
60
61 /// <summary>
62 /// Gets the row by string key.
63 /// </summary>
64 /// <param name="key">String key to look up.</param>
65 /// <returns>Row or null if key is not found.</returns>
66 public T Get(string key)
67 {
68 return this.TryGetValue(key, out var result) ? result : null;
69 }
70 }
71}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Unbind
4{
5 using System;
6 using System.Collections;
7 using System.Collections.Generic;
8 using System.Globalization;
9 using System.IO;
10 using System.Linq;
11 using WixToolset.Core.Native;
12 using WixToolset.Core.Native.Msi;
13 using WixToolset.Data;
14 using WixToolset.Data.WindowsInstaller;
15 using WixToolset.Data.WindowsInstaller.Rows;
16
17 internal class ExtractCabinetsCommand
18 {
19 public ExtractCabinetsCommand(WindowsInstallerData output, Database database, string inputFilePath, string exportBasePath, string intermediateFolder, bool treatOutputAsModule = false)
20 {
21 this.Output = output;
22 this.Database = database;
23 this.InputFilePath = inputFilePath;
24 this.ExportBasePath = exportBasePath;
25 this.IntermediateFolder = intermediateFolder;
26 this.TreatOutputAsModule = treatOutputAsModule;
27 }
28
29 public string[] ExtractedFiles { get; private set; }
30
31 private WindowsInstallerData Output { get; }
32
33 private Database Database { get; }
34
35 private string InputFilePath { get; }
36
37 private string ExportBasePath { get; }
38
39 private string IntermediateFolder { get; }
40
41 public bool TreatOutputAsModule { get; }
42
43 public void Execute()
44 {
45 var databaseBasePath = Path.GetDirectoryName(this.InputFilePath);
46 var cabinetFiles = new List<string>();
47 var embeddedCabinets = new SortedList();
48
49 // index all of the cabinet files
50 if (OutputType.Module == this.Output.Type || this.TreatOutputAsModule)
51 {
52 embeddedCabinets.Add(0, "MergeModule.CABinet");
53 }
54 else if (null != this.Output.Tables["Media"])
55 {
56 foreach (MediaRow mediaRow in this.Output.Tables["Media"].Rows)
57 {
58 if (null != mediaRow.Cabinet)
59 {
60 if (OutputType.Product == this.Output.Type ||
61 (OutputType.Transform == this.Output.Type && RowOperation.Add == mediaRow.Operation))
62 {
63 if (mediaRow.Cabinet.StartsWith("#", StringComparison.Ordinal))
64 {
65 embeddedCabinets.Add(mediaRow.DiskId, mediaRow.Cabinet.Substring(1));
66 }
67 else
68 {
69 cabinetFiles.Add(Path.Combine(databaseBasePath, mediaRow.Cabinet));
70 }
71 }
72 }
73 }
74 }
75
76 // extract the embedded cabinet files from the database
77 if (0 < embeddedCabinets.Count)
78 {
79 using (var streamsView = this.Database.OpenView("SELECT `Data` FROM `_Streams` WHERE `Name` = ?"))
80 {
81 foreach (int diskId in embeddedCabinets.Keys)
82 {
83 using (var record = new Record(1))
84 {
85 record.SetString(1, (string)embeddedCabinets[diskId]);
86 streamsView.Execute(record);
87 }
88
89 using (var record = streamsView.Fetch())
90 {
91 if (null != record)
92 {
93 // since the cabinets are stored in case-sensitive streams inside the msi, but the file system is not (typically) case-sensitive,
94 // embedded cabinets must be extracted to a canonical file name (like their diskid) to ensure extraction will always work
95 var cabinetFile = Path.Combine(this.IntermediateFolder, String.Concat("Media", Path.DirectorySeparatorChar, diskId.ToString(CultureInfo.InvariantCulture), ".cab"));
96
97 // ensure the parent directory exists
98 Directory.CreateDirectory(Path.GetDirectoryName(cabinetFile));
99
100 using (var fs = File.Create(cabinetFile))
101 {
102 int bytesRead;
103 var buffer = new byte[512];
104
105 while (0 != (bytesRead = record.GetStream(1, buffer, buffer.Length)))
106 {
107 fs.Write(buffer, 0, bytesRead);
108 }
109 }
110
111 cabinetFiles.Add(cabinetFile);
112 }
113 else
114 {
115 // TODO: warning about missing embedded cabinet
116 }
117 }
118 }
119 }
120 }
121
122 // extract the cabinet files
123 if (0 < cabinetFiles.Count)
124 {
125 // ensure the directory exists or extraction will fail
126 Directory.CreateDirectory(this.ExportBasePath);
127
128 foreach (var cabinetFile in cabinetFiles)
129 {
130 try
131 {
132 var cabinet = new Cabinet(cabinetFile);
133 this.ExtractedFiles = cabinet.Extract(this.ExportBasePath).ToArray();
134 }
135 catch (FileNotFoundException)
136 {
137 throw new WixException(ErrorMessages.FileNotFound(new SourceLineNumber(this.InputFilePath), cabinetFile));
138 }
139 }
140 }
141 else
142 {
143 this.ExtractedFiles = new string[0];
144 }
145 }
146 }
147}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Unbind
4{
5 using System;
6 using System.Collections;
7 using System.Collections.Generic;
8 using System.Globalization;
9 using System.IO;
10 using System.Text.RegularExpressions;
11 using WixToolset.Core.Native.Msi;
12 using WixToolset.Data;
13 using WixToolset.Data.WindowsInstaller;
14 using WixToolset.Extensibility.Services;
15
16 internal class UnbindDatabaseCommand
17 {
18 private List<string> exportedFiles;
19
20 public UnbindDatabaseCommand(IMessaging messaging, IBackendHelper backendHelper, Database database, string databasePath, OutputType outputType, string exportBasePath, string intermediateFolder, bool isAdminImage, bool suppressDemodularization, bool skipSummaryInfo)
21 {
22 this.Messaging = messaging;
23 this.BackendHelper = backendHelper;
24 this.Database = database;
25 this.DatabasePath = databasePath;
26 this.OutputType = outputType;
27 this.ExportBasePath = exportBasePath;
28 this.IntermediateFolder = intermediateFolder;
29 this.IsAdminImage = isAdminImage;
30 this.SuppressDemodularization = suppressDemodularization;
31 this.SkipSummaryInfo = skipSummaryInfo;
32
33 this.TableDefinitions = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All);
34 }
35
36 public IMessaging Messaging { get; }
37
38 public IBackendHelper BackendHelper { get; }
39
40 public Database Database { get; }
41
42 public string DatabasePath { get; }
43
44 public OutputType OutputType { get; }
45
46 public string ExportBasePath { get; }
47
48 public string IntermediateFolder { get; }
49
50 public bool IsAdminImage { get; }
51
52 public bool SuppressDemodularization { get; }
53
54 public bool SkipSummaryInfo { get; }
55
56 public TableDefinitionCollection TableDefinitions { get; }
57
58 public IEnumerable<string> ExportedFiles => this.exportedFiles;
59
60 private int SectionCount { get; set; }
61
62 public WindowsInstallerData Execute()
63 {
64 this.exportedFiles = new List<string>();
65
66 string modularizationGuid = null;
67 var output = new WindowsInstallerData(new SourceLineNumber(this.DatabasePath));
68 View validationView = null;
69
70 // set the output type
71 output.Type = this.OutputType;
72
73 Directory.CreateDirectory(this.IntermediateFolder);
74
75 // get the codepage
76 this.Database.Export("_ForceCodepage", this.IntermediateFolder, "_ForceCodepage.idt");
77 using (var sr = File.OpenText(Path.Combine(this.IntermediateFolder, "_ForceCodepage.idt")))
78 {
79 string line;
80
81 while (null != (line = sr.ReadLine()))
82 {
83 var data = line.Split('\t');
84
85 if (2 == data.Length)
86 {
87 output.Codepage = Convert.ToInt32(data[0], CultureInfo.InvariantCulture);
88 }
89 }
90 }
91
92 // get the summary information table if it exists; it won't if unbinding a transform
93 if (!this.SkipSummaryInfo)
94 {
95 using (var summaryInformation = new SummaryInformation(this.Database))
96 {
97 var table = new Table(this.TableDefinitions["_SummaryInformation"]);
98
99 for (var i = 1; 19 >= i; i++)
100 {
101 var value = summaryInformation.GetProperty(i);
102
103 if (0 < value.Length)
104 {
105 var row = table.CreateRow(output.SourceLineNumbers);
106 row[0] = i;
107 row[1] = value;
108 }
109 }
110
111 output.Tables.Add(table);
112 }
113 }
114
115 try
116 {
117 // open a view on the validation table if it exists
118 if (this.Database.TableExists("_Validation"))
119 {
120 validationView = this.Database.OpenView("SELECT * FROM `_Validation` WHERE `Table` = ? AND `Column` = ?");
121 }
122
123 // get the normal tables
124 using (var tablesView = this.Database.OpenExecuteView("SELECT * FROM _Tables"))
125 {
126 foreach (var tableRecord in tablesView.Records)
127 {
128 var tableName = tableRecord.GetString(1);
129
130 using (var tableView = this.Database.OpenExecuteView(String.Format(CultureInfo.InvariantCulture, "SELECT * FROM `{0}`", tableName)))
131 {
132 var tableDefinition = this.GetTableDefinition(tableName, tableView, validationView);
133 var table = new Table(tableDefinition);
134
135 foreach (var rowRecord in tableView.Records)
136 {
137 var recordCount = rowRecord.GetFieldCount();
138 var row = table.CreateRow(output.SourceLineNumbers);
139
140 for (var i = 0; recordCount > i && row.Fields.Length > i; i++)
141 {
142 if (rowRecord.IsNull(i + 1))
143 {
144 if (!row.Fields[i].Column.Nullable)
145 {
146 // TODO: display an error for a null value in a non-nullable field OR
147 // display a warning and put an empty string in the value to let the compiler handle it
148 // (the second option is risky because the later code may make certain assumptions about
149 // the contents of a row value)
150 }
151 }
152 else
153 {
154 switch (row.Fields[i].Column.Type)
155 {
156 case ColumnType.Number:
157 var success = false;
158 var intValue = rowRecord.GetInteger(i + 1);
159 if (row.Fields[i].Column.IsLocalizable)
160 {
161 success = row.BestEffortSetField(i, Convert.ToString(intValue, CultureInfo.InvariantCulture));
162 }
163 else
164 {
165 success = row.BestEffortSetField(i, intValue);
166 }
167
168 if (!success)
169 {
170 this.Messaging.Write(WarningMessages.BadColumnDataIgnored(row.SourceLineNumbers, Convert.ToString(intValue, CultureInfo.InvariantCulture), tableName, row.Fields[i].Column.Name));
171 }
172 break;
173 case ColumnType.Object:
174 var sourceFile = "FILE NOT EXPORTED, USE THE dark.exe -x OPTION TO EXPORT BINARIES";
175
176 if (null != this.ExportBasePath)
177 {
178 var relativeSourceFile = Path.Combine(tableName, row.GetPrimaryKey('.'));
179 sourceFile = Path.Combine(this.ExportBasePath, relativeSourceFile);
180
181 // ensure the parent directory exists
182 System.IO.Directory.CreateDirectory(Path.Combine(this.ExportBasePath, tableName));
183
184 using (var fs = System.IO.File.Create(sourceFile))
185 {
186 int bytesRead;
187 var buffer = new byte[512];
188
189 while (0 != (bytesRead = rowRecord.GetStream(i + 1, buffer, buffer.Length)))
190 {
191 fs.Write(buffer, 0, bytesRead);
192 }
193 }
194
195 this.exportedFiles.Add(sourceFile);
196 }
197
198 row[i] = sourceFile;
199 break;
200 default:
201 var value = rowRecord.GetString(i + 1);
202
203 switch (row.Fields[i].Column.Category)
204 {
205 case ColumnCategory.Guid:
206 value = value.ToUpper(CultureInfo.InvariantCulture);
207 break;
208 }
209
210 // de-modularize
211 if (!this.SuppressDemodularization && OutputType.Module == output.Type && ColumnModularizeType.None != row.Fields[i].Column.ModularizeType)
212 {
213 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}");
214
215 if (null == modularizationGuid)
216 {
217 var match = modularization.Match(value);
218 if (match.Success)
219 {
220 modularizationGuid = String.Concat('{', match.Value.Substring(1).Replace('_', '-'), '}');
221 }
222 }
223
224 value = modularization.Replace(value, String.Empty);
225 }
226
227 // escape "$(" for the preprocessor
228 value = value.Replace("$(", "$$(");
229
230 // escape things that look like wix variables
231 // TODO: Evaluate this requirement.
232 //var matches = Common.WixVariableRegex.Matches(value);
233 //for (var j = matches.Count - 1; 0 <= j; j--)
234 //{
235 // value = value.Insert(matches[j].Index, "!");
236 //}
237
238 row[i] = value;
239 break;
240 }
241 }
242 }
243 }
244
245 output.Tables.Add(table);
246 }
247 }
248 }
249 }
250 finally
251 {
252 if (null != validationView)
253 {
254 validationView.Close();
255 }
256 }
257
258 // set the modularization guid as the PackageCode
259 if (null != modularizationGuid)
260 {
261 var table = output.Tables["_SummaryInformation"];
262
263 foreach (var row in table.Rows)
264 {
265 if (9 == (int)row[0]) // PID_REVNUMBER
266 {
267 row[1] = modularizationGuid;
268 }
269 }
270 }
271
272 if (this.IsAdminImage)
273 {
274 this.GenerateWixFileTable(this.DatabasePath, output);
275 this.GenerateSectionIds(output);
276 }
277
278 return output;
279 }
280
281 private TableDefinition GetTableDefinition(string tableName, View tableView, View validationView)
282 {
283 // Use our table definitions whenever possible since they will be used when compiling the source code anyway.
284 // This also allows us to take advantage of WiX concepts like localizable columns which current code assumes.
285 if (this.TableDefinitions.Contains(tableName))
286 {
287 return this.TableDefinitions[tableName];
288 }
289
290 ColumnDefinition[] columns;
291 using (Record columnNameRecord = tableView.GetColumnNames(),
292 columnTypeRecord = tableView.GetColumnTypes())
293 {
294 // index the primary keys
295 var tablePrimaryKeys = new HashSet<string>();
296 using (var primaryKeysRecord = this.Database.PrimaryKeys(tableName))
297 {
298 var primaryKeysFieldCount = primaryKeysRecord.GetFieldCount();
299
300 for (var i = 1; i <= primaryKeysFieldCount; i++)
301 {
302 tablePrimaryKeys.Add(primaryKeysRecord.GetString(i));
303 }
304 }
305
306 var columnCount = columnNameRecord.GetFieldCount();
307 columns = new ColumnDefinition[columnCount];
308 for (var i = 1; i <= columnCount; i++)
309 {
310 var columnName = columnNameRecord.GetString(i);
311 var idtType = columnTypeRecord.GetString(i);
312
313 ColumnType columnType;
314 int length;
315 bool nullable;
316
317 var columnCategory = ColumnCategory.Unknown;
318 var columnModularizeType = ColumnModularizeType.None;
319 var primary = tablePrimaryKeys.Contains(columnName);
320 int? minValue = null;
321 int? maxValue = null;
322 string keyTable = null;
323 int? keyColumn = null;
324 string category = null;
325 string set = null;
326 string description = null;
327
328 // get the column type, length, and whether its nullable
329 switch (Char.ToLower(idtType[0], CultureInfo.InvariantCulture))
330 {
331 case 'i':
332 columnType = ColumnType.Number;
333 break;
334 case 'l':
335 columnType = ColumnType.Localized;
336 break;
337 case 's':
338 columnType = ColumnType.String;
339 break;
340 case 'v':
341 columnType = ColumnType.Object;
342 break;
343 default:
344 // TODO: error
345 columnType = ColumnType.Unknown;
346 break;
347 }
348 length = Convert.ToInt32(idtType.Substring(1), CultureInfo.InvariantCulture);
349 nullable = Char.IsUpper(idtType[0]);
350
351 // try to get validation information
352 if (null != validationView)
353 {
354 using (var validationRecord = new Record(2))
355 {
356 validationRecord.SetString(1, tableName);
357 validationRecord.SetString(2, columnName);
358
359 validationView.Execute(validationRecord);
360 }
361
362 using (var validationRecord = validationView.Fetch())
363 {
364 if (null != validationRecord)
365 {
366 var validationNullable = validationRecord.GetString(3);
367 minValue = validationRecord.IsNull(4) ? null : (int?)validationRecord.GetInteger(4);
368 maxValue = validationRecord.IsNull(5) ? null : (int?)validationRecord.GetInteger(5);
369 keyTable = validationRecord.IsNull(6) ? null : validationRecord.GetString(6);
370 keyColumn = validationRecord.IsNull(7) ? null : (int?)validationRecord.GetInteger(7);
371 category = validationRecord.IsNull(8) ? null : validationRecord.GetString(8);
372 set = validationRecord.IsNull(9) ? null : validationRecord.GetString(9);
373 description = validationRecord.IsNull(10) ? null : validationRecord.GetString(10);
374
375 // check the validation nullable value against the column definition
376 if (null == validationNullable)
377 {
378 // TODO: warn for illegal validation nullable column
379 }
380 else if ((nullable && "Y" != validationNullable) || (!nullable && "N" != validationNullable))
381 {
382 // TODO: warn for mismatch between column definition and validation nullable
383 }
384
385 // convert category to ColumnCategory
386 if (null != category)
387 {
388 try
389 {
390 columnCategory = (ColumnCategory)Enum.Parse(typeof(ColumnCategory), category, true);
391 }
392 catch (ArgumentException)
393 {
394 columnCategory = ColumnCategory.Unknown;
395 }
396 }
397 }
398 else
399 {
400 // TODO: warn about no validation information
401 }
402 }
403 }
404
405 // guess the modularization type
406 if ("Icon" == keyTable && 1 == keyColumn)
407 {
408 columnModularizeType = ColumnModularizeType.Icon;
409 }
410 else if ("Condition" == columnName)
411 {
412 columnModularizeType = ColumnModularizeType.Condition;
413 }
414 else if (ColumnCategory.Formatted == columnCategory || ColumnCategory.FormattedSDDLText == columnCategory)
415 {
416 columnModularizeType = ColumnModularizeType.Property;
417 }
418 else if (ColumnCategory.Identifier == columnCategory)
419 {
420 columnModularizeType = ColumnModularizeType.Column;
421 }
422
423 columns[i - 1] = new ColumnDefinition(columnName, columnType, length, primary, nullable, columnCategory, minValue, maxValue, keyTable, keyColumn, set, description, columnModularizeType, (ColumnType.Localized == columnType), true);
424 }
425 }
426
427 return new TableDefinition(tableName, null, columns, false);
428 }
429
430 /// <summary>
431 /// Generates the WixFile table based on a path to an admin image msi and an Output.
432 /// </summary>
433 /// <param name="databaseFile">The path to the msi database file in an admin image.</param>
434 /// <param name="output">The Output that represents the msi database.</param>
435 private void GenerateWixFileTable(string databaseFile, WindowsInstallerData output)
436 {
437 throw new NotImplementedException();
438#if TODO_FIX_UNBINDING_FILES
439 var adminRootPath = Path.GetDirectoryName(databaseFile);
440
441 var componentDirectoryIndex = new Hashtable();
442 var componentTable = output.Tables["Component"];
443 foreach (var row in componentTable.Rows)
444 {
445 componentDirectoryIndex.Add(row[0], row[2]);
446 }
447
448 // Index full source paths for all directories
449 var directoryDirectoryParentIndex = new Hashtable();
450 var directoryFullPathIndex = new Hashtable();
451 var directorySourceNameIndex = new Hashtable();
452 var directoryTable = output.Tables["Directory"];
453 foreach (var row in directoryTable.Rows)
454 {
455 directoryDirectoryParentIndex.Add(row[0], row[1]);
456 if (null == row[1])
457 {
458 directoryFullPathIndex.Add(row[0], adminRootPath);
459 }
460 else
461 {
462 directorySourceNameIndex.Add(row[0], GetAdminSourceName((string)row[2]));
463 }
464 }
465
466 foreach (DictionaryEntry directoryEntry in directoryDirectoryParentIndex)
467 {
468 if (!directoryFullPathIndex.ContainsKey(directoryEntry.Key))
469 {
470 this.GetAdminFullPath((string)directoryEntry.Key, directoryDirectoryParentIndex, directorySourceNameIndex, directoryFullPathIndex);
471 }
472 }
473
474 var fileTable = output.Tables["File"];
475 var wixFileTable = output.EnsureTable(this.TableDefinitions["WixFile"]);
476 foreach (var row in fileTable.Rows)
477 {
478 var wixFileRow = new WixFileRow(null, this.TableDefinitions["WixFile"]);
479 wixFileRow.File = (string)row[0];
480 wixFileRow.Directory = (string)componentDirectoryIndex[(string)row[1]];
481 wixFileRow.Source = Path.Combine((string)directoryFullPathIndex[wixFileRow.Directory], GetAdminSourceName((string)row[2]));
482
483 if (!File.Exists(wixFileRow.Source))
484 {
485 throw new WixException(ErrorMessages.WixFileNotFound(wixFileRow.Source));
486 }
487
488 wixFileTable.Rows.Add(wixFileRow);
489 }
490#endif
491 }
492
493 /// <summary>
494 /// 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.
495 /// </summary>
496 /// <param name="directory">The directory identifier.</param>
497 /// <param name="directoryDirectoryParentIndex">The Hashtable containing all the directory to directory parent mapping.</param>
498 /// <param name="directorySourceNameIndex">The Hashtable containing all the directory to source name mapping.</param>
499 /// <param name="directoryFullPathIndex">The Hashtable containing a mapping between all of the directories and their previously calculated full paths.</param>
500 /// <returns>The full path to the directory.</returns>
501 private string GetAdminFullPath(string directory, Hashtable directoryDirectoryParentIndex, Hashtable directorySourceNameIndex, Hashtable directoryFullPathIndex)
502 {
503 var parent = (string)directoryDirectoryParentIndex[directory];
504 var sourceName = (string)directorySourceNameIndex[directory];
505
506 string parentFullPath;
507 if (directoryFullPathIndex.ContainsKey(parent))
508 {
509 parentFullPath = (string)directoryFullPathIndex[parent];
510 }
511 else
512 {
513 parentFullPath = this.GetAdminFullPath(parent, directoryDirectoryParentIndex, directorySourceNameIndex, directoryFullPathIndex);
514 }
515
516 if (null == sourceName)
517 {
518 sourceName = String.Empty;
519 }
520
521 var fullPath = Path.Combine(parentFullPath, sourceName);
522 directoryFullPathIndex.Add(directory, fullPath);
523
524 return fullPath;
525 }
526
527 /// <summary>
528 /// Get the source name in an admin image.
529 /// </summary>
530 /// <param name="value">The Filename value.</param>
531 /// <returns>The source name of the directory in an admin image.</returns>
532 private string GetAdminSourceName(string value)
533 {
534 string name = null;
535 string[] names;
536 string shortname = null;
537 string shortsourcename = null;
538 string sourcename = null;
539
540 names = this.BackendHelper.SplitMsiFileName(value);
541
542 if (null != names[0] && "." != names[0])
543 {
544 if (null != names[1])
545 {
546 shortname = names[0];
547 }
548 else
549 {
550 name = names[0];
551 }
552 }
553
554 if (null != names[1])
555 {
556 name = names[1];
557 }
558
559 if (null != names[2])
560 {
561 if (null != names[3])
562 {
563 shortsourcename = names[2];
564 }
565 else
566 {
567 sourcename = names[2];
568 }
569 }
570
571 if (null != names[3])
572 {
573 sourcename = names[3];
574 }
575
576 if (null != sourcename)
577 {
578 return sourcename;
579 }
580 else if (null != shortsourcename)
581 {
582 return shortsourcename;
583 }
584 else if (null != name)
585 {
586 return name;
587 }
588 else
589 {
590 return shortname;
591 }
592 }
593
594 /// <summary>
595 /// Creates section ids on rows which form logical groupings of resources.
596 /// </summary>
597 /// <param name="output">The Output that represents the msi database.</param>
598 private void GenerateSectionIds(WindowsInstallerData output)
599 {
600 // First assign and index section ids for the tables that are in their own sections.
601 this.AssignSectionIdsToTable(output.Tables["Binary"], 0);
602 var componentSectionIdIndex = this.AssignSectionIdsToTable(output.Tables["Component"], 0);
603 var customActionSectionIdIndex = this.AssignSectionIdsToTable(output.Tables["CustomAction"], 0);
604 this.AssignSectionIdsToTable(output.Tables["Directory"], 0);
605 var featureSectionIdIndex = this.AssignSectionIdsToTable(output.Tables["Feature"], 0);
606 this.AssignSectionIdsToTable(output.Tables["Icon"], 0);
607 var digitalCertificateSectionIdIndex = this.AssignSectionIdsToTable(output.Tables["MsiDigitalCertificate"], 0);
608 this.AssignSectionIdsToTable(output.Tables["Property"], 0);
609
610 // Now handle all the tables that rely on the first set of indexes but also produce their own indexes. Order matters here.
611 var fileSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["File"], componentSectionIdIndex, 1, 0);
612 var appIdSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["Class"], componentSectionIdIndex, 2, 5);
613 var odbcDataSourceSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["ODBCDataSource"], componentSectionIdIndex, 1, 0);
614 var odbcDriverSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["ODBCDriver"], componentSectionIdIndex, 1, 0);
615 var registrySectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["Registry"], componentSectionIdIndex, 5, 0);
616 var serviceInstallSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["ServiceInstall"], componentSectionIdIndex, 11, 0);
617
618 // Now handle all the tables which only rely on previous indexes and order does not matter.
619 foreach (var table in output.Tables)
620 {
621 switch (table.Name)
622 {
623 case "WixFile":
624 case "MsiFileHash":
625 ConnectTableToSection(table, fileSectionIdIndex, 0);
626 break;
627 case "MsiAssembly":
628 case "MsiAssemblyName":
629 ConnectTableToSection(table, componentSectionIdIndex, 0);
630 break;
631 case "MsiPackageCertificate":
632 case "MsiPatchCertificate":
633 ConnectTableToSection(table, digitalCertificateSectionIdIndex, 1);
634 break;
635 case "CreateFolder":
636 case "FeatureComponents":
637 case "MoveFile":
638 case "ReserveCost":
639 case "ODBCTranslator":
640 ConnectTableToSection(table, componentSectionIdIndex, 1);
641 break;
642 case "TypeLib":
643 ConnectTableToSection(table, componentSectionIdIndex, 2);
644 break;
645 case "Shortcut":
646 case "Environment":
647 ConnectTableToSection(table, componentSectionIdIndex, 3);
648 break;
649 case "RemoveRegistry":
650 ConnectTableToSection(table, componentSectionIdIndex, 4);
651 break;
652 case "ServiceControl":
653 ConnectTableToSection(table, componentSectionIdIndex, 5);
654 break;
655 case "IniFile":
656 case "RemoveIniFile":
657 ConnectTableToSection(table, componentSectionIdIndex, 7);
658 break;
659 case "AppId":
660 ConnectTableToSection(table, appIdSectionIdIndex, 0);
661 break;
662 case "Condition":
663 ConnectTableToSection(table, featureSectionIdIndex, 0);
664 break;
665 case "ODBCSourceAttribute":
666 ConnectTableToSection(table, odbcDataSourceSectionIdIndex, 0);
667 break;
668 case "ODBCAttribute":
669 ConnectTableToSection(table, odbcDriverSectionIdIndex, 0);
670 break;
671 case "AdminExecuteSequence":
672 case "AdminUISequence":
673 case "AdvtExecuteSequence":
674 case "AdvtUISequence":
675 case "InstallExecuteSequence":
676 case "InstallUISequence":
677 ConnectTableToSection(table, customActionSectionIdIndex, 0);
678 break;
679 case "LockPermissions":
680 case "MsiLockPermissions":
681 foreach (var row in table.Rows)
682 {
683 var lockObject = (string)row[0];
684 var tableName = (string)row[1];
685 switch (tableName)
686 {
687 case "File":
688 row.SectionId = (string)fileSectionIdIndex[lockObject];
689 break;
690 case "Registry":
691 row.SectionId = (string)registrySectionIdIndex[lockObject];
692 break;
693 case "ServiceInstall":
694 row.SectionId = (string)serviceInstallSectionIdIndex[lockObject];
695 break;
696 }
697 }
698 break;
699 }
700 }
701
702 // Now pass the output to each unbinder extension to allow them to analyze the output and determine thier proper section ids.
703 //foreach (IUnbinderExtension extension in this.unbinderExtensions)
704 //{
705 // extension.GenerateSectionIds(output);
706 //}
707 }
708
709 /// <summary>
710 /// Creates new section ids on all the rows in a table.
711 /// </summary>
712 /// <param name="table">The table to add sections to.</param>
713 /// <param name="rowPrimaryKeyIndex">The index of the column which is used by other tables to reference this table.</param>
714 /// <returns>A Hashtable containing the tables key for each row paired with its assigned section id.</returns>
715 private Hashtable AssignSectionIdsToTable(Table table, int rowPrimaryKeyIndex)
716 {
717 var hashtable = new Hashtable();
718 if (null != table)
719 {
720 foreach (var row in table.Rows)
721 {
722 row.SectionId = this.GetNewSectionId();
723 hashtable.Add(row[rowPrimaryKeyIndex], row.SectionId);
724 }
725 }
726 return hashtable;
727 }
728
729 /// <summary>
730 /// Connects a table's rows to an already sectioned table.
731 /// </summary>
732 /// <param name="table">The table containing rows that need to be connected to sections.</param>
733 /// <param name="sectionIdIndex">A hashtable containing keys to map table to its section.</param>
734 /// <param name="rowIndex">The index of the column which is used as the foreign key in to the sectionIdIndex.</param>
735 private static void ConnectTableToSection(Table table, Hashtable sectionIdIndex, int rowIndex)
736 {
737 if (null != table)
738 {
739 foreach (var row in table.Rows)
740 {
741 if (sectionIdIndex.ContainsKey(row[rowIndex]))
742 {
743 row.SectionId = (string)sectionIdIndex[row[rowIndex]];
744 }
745 }
746 }
747 }
748
749 /// <summary>
750 /// Connects a table's rows to an already sectioned table and produces an index for other tables to connect to it.
751 /// </summary>
752 /// <param name="table">The table containing rows that need to be connected to sections.</param>
753 /// <param name="sectionIdIndex">A hashtable containing keys to map table to its section.</param>
754 /// <param name="rowIndex">The index of the column which is used as the foreign key in to the sectionIdIndex.</param>
755 /// <param name="rowPrimaryKeyIndex">The index of the column which is used by other tables to reference this table.</param>
756 /// <returns>A Hashtable containing the tables key for each row paired with its assigned section id.</returns>
757 private static Hashtable ConnectTableToSectionAndIndex(Table table, Hashtable sectionIdIndex, int rowIndex, int rowPrimaryKeyIndex)
758 {
759 var newHashTable = new Hashtable();
760 if (null != table)
761 {
762 foreach (var row in table.Rows)
763 {
764 if (!sectionIdIndex.ContainsKey(row[rowIndex]))
765 {
766 continue;
767 }
768
769 row.SectionId = (string)sectionIdIndex[row[rowIndex]];
770 if (null != row[rowPrimaryKeyIndex])
771 {
772 newHashTable.Add(row[rowPrimaryKeyIndex], row.SectionId);
773 }
774 }
775 }
776 return newHashTable;
777 }
778
779 /// <summary>
780 /// Creates a new section identifier to be used when adding a section to an output.
781 /// </summary>
782 /// <returns>A string representing a new section id.</returns>
783 private string GetNewSectionId()
784 {
785 this.SectionCount++;
786 return "wix.section." + this.SectionCount.ToString(CultureInfo.InvariantCulture);
787 }
788 }
789}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Unbind
4{
5 using System;
6 using System.ComponentModel;
7 using WixToolset.Data;
8 using WixToolset.Extensibility.Data;
9 using WixToolset.Core.Native.Msi;
10
11 internal class UnbindMsiOrMsmCommand
12 {
13 public UnbindMsiOrMsmCommand(IUnbindContext context)
14 {
15 this.Context = context;
16 }
17
18 public IUnbindContext Context { get; }
19
20 public Intermediate Execute()
21 {
22#if TODO_PATCHING
23 Output output;
24
25 try
26 {
27 using (Database database = new Database(this.Context.InputFilePath, OpenDatabase.ReadOnly))
28 {
29 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);
30 output = unbindCommand.Execute();
31
32 // extract the files from the cabinets
33 if (!String.IsNullOrEmpty(this.Context.ExportBasePath) && !this.Context.SuppressExtractCabinets)
34 {
35 var extractCommand = new ExtractCabinetsCommand(output, database, this.Context.InputFilePath, this.Context.ExportBasePath, this.Context.IntermediateFolder);
36 extractCommand.Execute();
37 }
38 }
39 }
40 catch (Win32Exception e)
41 {
42 if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED
43 {
44 throw new WixException(WixErrors.OpenDatabaseFailed(this.Context.InputFilePath));
45 }
46
47 throw;
48 }
49
50 return output;
51#endif
52 throw new NotImplementedException();
53 }
54 }
55}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Unbind
4{
5 using System;
6 using System.Collections;
7 using System.Collections.Generic;
8 using System.ComponentModel;
9 using System.Globalization;
10 using System.IO;
11 using System.Linq;
12 using WixToolset.Core.Native.Msi;
13 using WixToolset.Core.WindowsInstaller.Bind;
14 using WixToolset.Data;
15 using WixToolset.Data.WindowsInstaller;
16 using WixToolset.Extensibility.Services;
17
18 internal class UnbindTransformCommand
19 {
20 public UnbindTransformCommand(IMessaging messaging, IBackendHelper backendHelper, string transformFile, string exportBasePath, string intermediateFolder)
21 {
22 this.Messaging = messaging;
23 this.BackendHelper = backendHelper;
24 this.TransformFile = transformFile;
25 this.ExportBasePath = exportBasePath;
26 this.IntermediateFolder = intermediateFolder;
27
28 this.TableDefinitions = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All);
29 }
30
31 private IMessaging Messaging { get; }
32
33 private IBackendHelper BackendHelper { get; }
34
35 private string TransformFile { get; }
36
37 private string ExportBasePath { get; }
38
39 private string IntermediateFolder { get; }
40
41 private TableDefinitionCollection TableDefinitions { get; }
42
43 private string EmptyFile { get; set; }
44
45 public WindowsInstallerData Execute()
46 {
47 var transform = new WindowsInstallerData(new SourceLineNumber(this.TransformFile));
48 transform.Type = OutputType.Transform;
49
50 // get the summary information table
51 using (var summaryInformation = new SummaryInformation(this.TransformFile))
52 {
53 var table = transform.EnsureTable(this.TableDefinitions["_SummaryInformation"]);
54
55 for (var i = 1; 19 >= i; i++)
56 {
57 var value = summaryInformation.GetProperty(i);
58
59 if (0 < value.Length)
60 {
61 var row = table.CreateRow(transform.SourceLineNumbers);
62 row[0] = i;
63 row[1] = value;
64 }
65 }
66 }
67
68 // create a schema msi which hopefully matches the table schemas in the transform
69 var schemaOutput = new WindowsInstallerData(null);
70 var msiDatabaseFile = Path.Combine(this.IntermediateFolder, "schema.msi");
71 foreach (var tableDefinition in this.TableDefinitions)
72 {
73 // skip unreal tables and the Patch table
74 if (!tableDefinition.Unreal && "Patch" != tableDefinition.Name)
75 {
76 schemaOutput.EnsureTable(tableDefinition);
77 }
78 }
79
80 var addedRows = new Dictionary<string, Row>();
81 Table transformViewTable;
82
83 // Bind the schema msi.
84 this.GenerateDatabase(schemaOutput, msiDatabaseFile);
85
86 // apply the transform to the database and retrieve the modifications
87 using (var msiDatabase = new Database(msiDatabaseFile, OpenDatabase.Transact))
88 {
89 // apply the transform with the ViewTransform option to collect all the modifications
90 msiDatabase.ApplyTransform(this.TransformFile, TransformErrorConditions.All | TransformErrorConditions.ViewTransform);
91
92 // unbind the database
93 var unbindCommand = new UnbindDatabaseCommand(this.Messaging, this.BackendHelper, msiDatabase, msiDatabaseFile, OutputType.Product, this.ExportBasePath, this.IntermediateFolder, false, false, skipSummaryInfo: true);
94 var transformViewOutput = unbindCommand.Execute();
95
96 // index the added and possibly modified rows (added rows may also appears as modified rows)
97 transformViewTable = transformViewOutput.Tables["_TransformView"];
98 var modifiedRows = new Hashtable();
99 foreach (var row in transformViewTable.Rows)
100 {
101 var tableName = (string)row[0];
102 var columnName = (string)row[1];
103 var primaryKeys = (string)row[2];
104
105 if ("INSERT" == columnName)
106 {
107 var index = String.Concat(tableName, ':', primaryKeys);
108
109 addedRows.Add(index, null);
110 }
111 else if ("CREATE" != columnName && "DELETE" != columnName && "DROP" != columnName && null != primaryKeys) // modified row
112 {
113 var index = String.Concat(tableName, ':', primaryKeys);
114
115 modifiedRows[index] = row;
116 }
117 }
118
119 // create placeholder rows for modified rows to make the transform insert the updated values when its applied
120 foreach (Row row in modifiedRows.Values)
121 {
122 var tableName = (string)row[0];
123 var columnName = (string)row[1];
124 var primaryKeys = (string)row[2];
125
126 var index = String.Concat(tableName, ':', primaryKeys);
127
128 // ignore information for added rows
129 if (!addedRows.ContainsKey(index))
130 {
131 var table = schemaOutput.Tables[tableName];
132 this.CreateRow(table, primaryKeys, true);
133 }
134 }
135 }
136
137 // Re-bind the schema output with the placeholder rows.
138 this.GenerateDatabase(schemaOutput, msiDatabaseFile);
139
140 // apply the transform to the database and retrieve the modifications
141 using (var msiDatabase = new Database(msiDatabaseFile, OpenDatabase.Transact))
142 {
143 try
144 {
145 // apply the transform
146 msiDatabase.ApplyTransform(this.TransformFile, TransformErrorConditions.All);
147
148 // commit the database to guard against weird errors with streams
149 msiDatabase.Commit();
150 }
151 catch (Win32Exception ex)
152 {
153 if (0x65B == ex.NativeErrorCode)
154 {
155 // this commonly happens when the transform was built
156 // against a database schema different from the internal
157 // table definitions
158 throw new WixException(ErrorMessages.TransformSchemaMismatch());
159 }
160 }
161
162 // unbind the database
163 var unbindCommand = new UnbindDatabaseCommand(this.Messaging, this.BackendHelper, msiDatabase, msiDatabaseFile, OutputType.Product, this.ExportBasePath, this.IntermediateFolder, false, false, skipSummaryInfo: true);
164 var output = unbindCommand.Execute();
165
166 // index all the rows to easily find modified rows
167 var rows = new Dictionary<string, Row>();
168 foreach (var table in output.Tables)
169 {
170 foreach (var row in table.Rows)
171 {
172 rows.Add(String.Concat(table.Name, ':', row.GetPrimaryKey('\t', " ")), row);
173 }
174 }
175
176 // process the _TransformView rows into transform rows
177 foreach (var row in transformViewTable.Rows)
178 {
179 var tableName = (string)row[0];
180 var columnName = (string)row[1];
181 var primaryKeys = (string)row[2];
182
183 var table = transform.EnsureTable(this.TableDefinitions[tableName]);
184
185 if ("CREATE" == columnName) // added table
186 {
187 table.Operation = TableOperation.Add;
188 }
189 else if ("DELETE" == columnName) // deleted row
190 {
191 var deletedRow = this.CreateRow(table, primaryKeys, false);
192 deletedRow.Operation = RowOperation.Delete;
193 }
194 else if ("DROP" == columnName) // dropped table
195 {
196 table.Operation = TableOperation.Drop;
197 }
198 else if ("INSERT" == columnName) // added row
199 {
200 var index = String.Concat(tableName, ':', primaryKeys);
201 var addedRow = rows[index];
202 addedRow.Operation = RowOperation.Add;
203 table.Rows.Add(addedRow);
204 }
205 else if (null != primaryKeys) // modified row
206 {
207 var index = String.Concat(tableName, ':', primaryKeys);
208
209 // the _TransformView table includes information for added rows
210 // that looks like modified rows so it sometimes needs to be ignored
211 if (!addedRows.ContainsKey(index))
212 {
213 var modifiedRow = rows[index];
214
215 // mark the field as modified
216 var indexOfModifiedValue = -1;
217 for (var i = 0; i < modifiedRow.TableDefinition.Columns.Length; ++i)
218 {
219 if (columnName.Equals(modifiedRow.TableDefinition.Columns[i].Name, StringComparison.Ordinal))
220 {
221 indexOfModifiedValue = i;
222 break;
223 }
224 }
225 modifiedRow.Fields[indexOfModifiedValue].Modified = true;
226
227 // move the modified row into the transform the first time its encountered
228 if (RowOperation.None == modifiedRow.Operation)
229 {
230 modifiedRow.Operation = RowOperation.Modify;
231 table.Rows.Add(modifiedRow);
232 }
233 }
234 }
235 else // added column
236 {
237 var column = table.Definition.Columns.Single(c => c.Name.Equals(columnName, StringComparison.Ordinal));
238 column.Added = true;
239 }
240 }
241 }
242
243 return transform;
244 }
245
246 /// <summary>
247 /// Create a deleted or modified row.
248 /// </summary>
249 /// <param name="table">The table containing the row.</param>
250 /// <param name="primaryKeys">The primary keys of the row.</param>
251 /// <param name="setRequiredFields">Option to set all required fields with placeholder values.</param>
252 /// <returns>The new row.</returns>
253 private Row CreateRow(Table table, string primaryKeys, bool setRequiredFields)
254 {
255 var row = table.CreateRow(null);
256
257 var primaryKeyParts = primaryKeys.Split('\t');
258 var primaryKeyPartIndex = 0;
259
260 for (var i = 0; i < table.Definition.Columns.Length; i++)
261 {
262 var columnDefinition = table.Definition.Columns[i];
263
264 if (columnDefinition.PrimaryKey)
265 {
266 if (ColumnType.Number == columnDefinition.Type && !columnDefinition.IsLocalizable)
267 {
268 row[i] = Convert.ToInt32(primaryKeyParts[primaryKeyPartIndex++], CultureInfo.InvariantCulture);
269 }
270 else
271 {
272 row[i] = primaryKeyParts[primaryKeyPartIndex++];
273 }
274 }
275 else if (setRequiredFields)
276 {
277 if (ColumnType.Number == columnDefinition.Type && !columnDefinition.IsLocalizable)
278 {
279 row[i] = 1;
280 }
281 else if (ColumnType.Object == columnDefinition.Type)
282 {
283 if (null == this.EmptyFile)
284 {
285 this.EmptyFile = Path.Combine(this.IntermediateFolder, ".empty");
286 using (var fileStream = File.Create(this.EmptyFile))
287 {
288 }
289 }
290
291 row[i] = this.EmptyFile;
292 }
293 else
294 {
295 row[i] = "1";
296 }
297 }
298 }
299
300 return row;
301 }
302
303 private void GenerateDatabase(WindowsInstallerData output, string databaseFile)
304 {
305 var command = new GenerateDatabaseCommand(this.Messaging, null, null, output, databaseFile, this.TableDefinitions, this.IntermediateFolder, keepAddedColumns: true, suppressAddingValidationRows: true, useSubdirectory: false);
306 command.Execute();
307 }
308 }
309}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller
4{
5 using WixToolset.Data;
6
7 internal static class WindowsInstallerBackendErrors
8 {
9 //public static Message ReplaceThisWithTheFirstError(SourceLineNumber sourceLineNumbers)
10 //{
11 // return Message(sourceLineNumbers, Ids.ReplaceThisWithTheFirstError, "format string", arg1, arg2);
12 //}
13
14 private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args)
15 {
16 return new Message(sourceLineNumber, MessageLevel.Error, (int)id, format, args);
17 }
18
19 public enum Ids
20 {
21 // ReplaceThisWithTheFirstError = 7500,
22 } // last available is 7999. 8000 is BurnBackendErrors.
23 }
24}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller
4{
5 using System;
6 using System.IO;
7 using WixToolset.Extensibility;
8
9 internal class WindowsInstallerBackendFactory : IBackendFactory
10 {
11 public bool TryCreateBackend(string outputType, string outputFile, out IBackend backend)
12 {
13 if (String.IsNullOrEmpty(outputType))
14 {
15 outputType = Path.GetExtension(outputFile);
16 }
17
18 switch (outputType?.ToLowerInvariant())
19 {
20 case "module":
21 case ".msm":
22 backend = new MsmBackend();
23 return true;
24
25 case "msipackage":
26 case "package":
27 case "product":
28 case ".msi":
29 backend = new MsiBackend();
30 return true;
31
32 case "patch":
33 case ".msp":
34 backend = new MspBackend();
35 return true;
36
37 //case "patchcreation":
38 //case ".pcp":
39 // return new PatchCreationBackend();
40
41 case "transform":
42 case ".mst":
43 backend = new MstBackend();
44 return true;
45 }
46
47 backend = null;
48 return false;
49 }
50 }
51}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller
4{
5 using WixToolset.Data;
6
7 internal static class WindowsInstallerBackendWarnings
8 {
9 //public static Message ReplaceThisWithTheFirstWarning(SourceLineNumber sourceLineNumbers)
10 //{
11 // return Message(sourceLineNumbers, Ids.ReplaceThisWithTheFirstWarning, "format string", arg1, arg2);
12 //}
13
14 private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args)
15 {
16 return new Message(sourceLineNumber, MessageLevel.Warning, (int)id, format, args);
17 }
18
19 public enum Ids
20 {
21 // ReplaceThisWithTheFirstWarning = 7100,
22 } // last available is 7499. 7500 is WindowsInstallerBackendErrors.
23 }
24}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller
4{
5 using System;
6 using WixToolset.Extensibility;
7
8 internal class WindowsInstallerExtensionFactory : IExtensionFactory
9 {
10 public bool TryCreateExtension(Type extensionType, out object extension)
11 {
12 extension = null;
13
14 if (extensionType == typeof(IBackendFactory))
15 {
16 extension = new WindowsInstallerBackendFactory();
17 }
18
19 return extension != null;
20 }
21 }
22}
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project Sdk="Microsoft.NET.Sdk">
5 <PropertyGroup>
6 <TargetFrameworks>netstandard2.0</TargetFrameworks>
7 <TargetFrameworks Condition=" '$(Configuration)'=='Release' ">$(TargetFrameworks);net461;net472</TargetFrameworks>
8 <Description>Core Windows Installer</Description>
9 <Title>WiX Toolset Core Windows Installer</Title>
10 <DebugType>embedded</DebugType>
11 <PublishRepositoryUrl>true</PublishRepositoryUrl>
12 <CreateDocumentationFile>true</CreateDocumentationFile>
13 </PropertyGroup>
14
15 <ItemGroup>
16 <PackageReference Include="WixToolset.Core.Native" Version="4.0.*" />
17 <PackageReference Include="WixToolset.Data" Version="4.0.*" />
18 <PackageReference Include="WixToolset.Extensibility" Version="4.0.*" />
19 </ItemGroup>
20
21 <ItemGroup>
22 <PackageReference Include="System.Reflection.Metadata" Version="1.6.0" />
23 <PackageReference Include="System.Text.Encoding.CodePages" Version="4.6.0" />
24 </ItemGroup>
25
26 <ItemGroup>
27 <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
28 <PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" PrivateAssets="all" />
29 </ItemGroup>
30</Project>
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller
4{
5 using System;
6 using System.Collections.Generic;
7 using WixToolset.Core.WindowsInstaller.ExtensibilityServices;
8 using WixToolset.Extensibility.Services;
9
10 /// <summary>
11 /// Extensions methods for adding WindowsInstaller services.
12 /// </summary>
13 public static class WixToolsetCoreServiceProviderExtensions
14 {
15 /// <summary>
16 /// Adds WindowsInstaller services.
17 /// </summary>
18 /// <param name="coreProvider"></param>
19 /// <returns></returns>
20 public static IWixToolsetCoreServiceProvider AddWindowsInstallerBackend(this IWixToolsetCoreServiceProvider coreProvider)
21 {
22 AddServices(coreProvider);
23
24 var extensionManager = coreProvider.GetService<IExtensionManager>();
25 extensionManager.Add(typeof(WindowsInstallerExtensionFactory).Assembly);
26
27 return coreProvider;
28 }
29
30 private static void AddServices(IWixToolsetCoreServiceProvider coreProvider)
31 {
32 // Singletons.
33 coreProvider.AddService((provider, singletons) => AddSingleton<IWindowsInstallerBackendHelper>(singletons, new WindowsInstallerBackendHelper(provider)));
34 }
35
36 private static T AddSingleton<T>(Dictionary<Type, object> singletons, T service) where T : class
37 {
38 singletons.Add(typeof(T), service);
39 return service;
40 }
41 }
42}
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 @@
1Microsoft Visual Studio Solution File, Format Version 12.00
2# Visual Studio 15
3VisualStudioVersion = 15.0.27004.2009
4MinimumVisualStudioVersion = 15.0.26124.0
5Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Core", "src\WixToolset.Core\WixToolset.Core.csproj", "{0B524850-5B9A-472B-85CC-D25920A1DFE1}"
6EndProject
7Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Core.WindowsInstaller", "src\WixToolset.Core.WindowsInstaller\WixToolset.Core.WindowsInstaller.csproj", "{5617F2A7-46A0-4D07-B9E0-E982D15641E4}"
8EndProject
9Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Core.Burn", "src\WixToolset.Core.Burn\WixToolset.Core.Burn.csproj", "{BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}"
10EndProject
11Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Core.ExtensionCache", "src\WixToolset.Core.ExtensionCache\WixToolset.Core.ExtensionCache.csproj", "{A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}"
12EndProject
13Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test", "Test", "{1284331E-BC6C-426D-AAAF-140C0174F875}"
14EndProject
15Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example.Extension", "src\test\Example.Extension\Example.Extension.csproj", "{C66C2503-C671-4230-8B48-1D93A8532A28}"
16EndProject
17Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolsetTest.CoreIntegration", "src\test\WixToolsetTest.CoreIntegration\WixToolsetTest.CoreIntegration.csproj", "{E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}"
18EndProject
19Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolsetTest.Core.Burn", "src\test\WixToolsetTest.Core.Burn\WixToolsetTest.Core.Burn.csproj", "{DF63F589-028E-45A1-A212-948FACF1FDCD}"
20EndProject
21Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Core.TestPackage", "src\WixToolset.Core.TestPackage\WixToolset.Core.TestPackage.csproj", "{853716DB-C02C-41BD-91BC-79CDC0C17D10}"
22EndProject
23Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CompileCoreTestExtensionWixlib", "src\test\CompileCoreTestExtensionWixlib\CompileCoreTestExtensionWixlib.csproj", "{23FC60D7-B101-42F8-9786-DB7A9CD964A2}"
24EndProject
25Global
26 GlobalSection(SolutionConfigurationPlatforms) = preSolution
27 Debug|Any CPU = Debug|Any CPU
28 Debug|x64 = Debug|x64
29 Debug|x86 = Debug|x86
30 Release|Any CPU = Release|Any CPU
31 Release|x64 = Release|x64
32 Release|x86 = Release|x86
33 EndGlobalSection
34 GlobalSection(ProjectConfigurationPlatforms) = postSolution
35 {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
36 {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Debug|Any CPU.Build.0 = Debug|Any CPU
37 {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Debug|x64.ActiveCfg = Debug|Any CPU
38 {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Debug|x64.Build.0 = Debug|Any CPU
39 {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Debug|x86.ActiveCfg = Debug|Any CPU
40 {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Debug|x86.Build.0 = Debug|Any CPU
41 {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Release|Any CPU.ActiveCfg = Release|Any CPU
42 {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Release|Any CPU.Build.0 = Release|Any CPU
43 {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Release|x64.ActiveCfg = Release|Any CPU
44 {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Release|x64.Build.0 = Release|Any CPU
45 {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Release|x86.ActiveCfg = Release|Any CPU
46 {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Release|x86.Build.0 = Release|Any CPU
47 {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
48 {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Debug|Any CPU.Build.0 = Debug|Any CPU
49 {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Debug|x64.ActiveCfg = Debug|Any CPU
50 {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Debug|x64.Build.0 = Debug|Any CPU
51 {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Debug|x86.ActiveCfg = Debug|Any CPU
52 {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Debug|x86.Build.0 = Debug|Any CPU
53 {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Release|Any CPU.ActiveCfg = Release|Any CPU
54 {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Release|Any CPU.Build.0 = Release|Any CPU
55 {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Release|x64.ActiveCfg = Release|Any CPU
56 {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Release|x64.Build.0 = Release|Any CPU
57 {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Release|x86.ActiveCfg = Release|Any CPU
58 {5617F2A7-46A0-4D07-B9E0-E982D15641E4}.Release|x86.Build.0 = Release|Any CPU
59 {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
60 {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Debug|Any CPU.Build.0 = Debug|Any CPU
61 {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Debug|x64.ActiveCfg = Debug|Any CPU
62 {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Debug|x64.Build.0 = Debug|Any CPU
63 {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Debug|x86.ActiveCfg = Debug|Any CPU
64 {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Debug|x86.Build.0 = Debug|Any CPU
65 {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Release|Any CPU.ActiveCfg = Release|Any CPU
66 {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Release|Any CPU.Build.0 = Release|Any CPU
67 {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Release|x64.ActiveCfg = Release|Any CPU
68 {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Release|x64.Build.0 = Release|Any CPU
69 {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Release|x86.ActiveCfg = Release|Any CPU
70 {BC19D30D-C1B6-46DF-95B3-8EDF688E0FEC}.Release|x86.Build.0 = Release|Any CPU
71 {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
72 {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Debug|Any CPU.Build.0 = Debug|Any CPU
73 {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Debug|x64.ActiveCfg = Debug|Any CPU
74 {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Debug|x64.Build.0 = Debug|Any CPU
75 {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Debug|x86.ActiveCfg = Debug|Any CPU
76 {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Debug|x86.Build.0 = Debug|Any CPU
77 {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Release|Any CPU.ActiveCfg = Release|Any CPU
78 {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Release|Any CPU.Build.0 = Release|Any CPU
79 {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Release|x64.ActiveCfg = Release|Any CPU
80 {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Release|x64.Build.0 = Release|Any CPU
81 {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Release|x86.ActiveCfg = Release|Any CPU
82 {A1F0DF11-87FB-4BBC-B53B-83F2CE66604A}.Release|x86.Build.0 = Release|Any CPU
83 {C66C2503-C671-4230-8B48-1D93A8532A28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
84 {C66C2503-C671-4230-8B48-1D93A8532A28}.Debug|Any CPU.Build.0 = Debug|Any CPU
85 {C66C2503-C671-4230-8B48-1D93A8532A28}.Debug|x64.ActiveCfg = Debug|Any CPU
86 {C66C2503-C671-4230-8B48-1D93A8532A28}.Debug|x64.Build.0 = Debug|Any CPU
87 {C66C2503-C671-4230-8B48-1D93A8532A28}.Debug|x86.ActiveCfg = Debug|Any CPU
88 {C66C2503-C671-4230-8B48-1D93A8532A28}.Debug|x86.Build.0 = Debug|Any CPU
89 {C66C2503-C671-4230-8B48-1D93A8532A28}.Release|Any CPU.ActiveCfg = Release|Any CPU
90 {C66C2503-C671-4230-8B48-1D93A8532A28}.Release|Any CPU.Build.0 = Release|Any CPU
91 {C66C2503-C671-4230-8B48-1D93A8532A28}.Release|x64.ActiveCfg = Release|Any CPU
92 {C66C2503-C671-4230-8B48-1D93A8532A28}.Release|x64.Build.0 = Release|Any CPU
93 {C66C2503-C671-4230-8B48-1D93A8532A28}.Release|x86.ActiveCfg = Release|Any CPU
94 {C66C2503-C671-4230-8B48-1D93A8532A28}.Release|x86.Build.0 = Release|Any CPU
95 {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
96 {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Debug|Any CPU.Build.0 = Debug|Any CPU
97 {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Debug|x64.ActiveCfg = Debug|Any CPU
98 {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Debug|x64.Build.0 = Debug|Any CPU
99 {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Debug|x86.ActiveCfg = Debug|Any CPU
100 {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Debug|x86.Build.0 = Debug|Any CPU
101 {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Release|Any CPU.ActiveCfg = Release|Any CPU
102 {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Release|Any CPU.Build.0 = Release|Any CPU
103 {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Release|x64.ActiveCfg = Release|Any CPU
104 {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Release|x64.Build.0 = Release|Any CPU
105 {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Release|x86.ActiveCfg = Release|Any CPU
106 {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B}.Release|x86.Build.0 = Release|Any CPU
107 {DF63F589-028E-45A1-A212-948FACF1FDCD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
108 {DF63F589-028E-45A1-A212-948FACF1FDCD}.Debug|Any CPU.Build.0 = Debug|Any CPU
109 {DF63F589-028E-45A1-A212-948FACF1FDCD}.Debug|x64.ActiveCfg = Debug|Any CPU
110 {DF63F589-028E-45A1-A212-948FACF1FDCD}.Debug|x64.Build.0 = Debug|Any CPU
111 {DF63F589-028E-45A1-A212-948FACF1FDCD}.Debug|x86.ActiveCfg = Debug|Any CPU
112 {DF63F589-028E-45A1-A212-948FACF1FDCD}.Debug|x86.Build.0 = Debug|Any CPU
113 {DF63F589-028E-45A1-A212-948FACF1FDCD}.Release|Any CPU.ActiveCfg = Release|Any CPU
114 {DF63F589-028E-45A1-A212-948FACF1FDCD}.Release|Any CPU.Build.0 = Release|Any CPU
115 {DF63F589-028E-45A1-A212-948FACF1FDCD}.Release|x64.ActiveCfg = Release|Any CPU
116 {DF63F589-028E-45A1-A212-948FACF1FDCD}.Release|x64.Build.0 = Release|Any CPU
117 {DF63F589-028E-45A1-A212-948FACF1FDCD}.Release|x86.ActiveCfg = Release|Any CPU
118 {DF63F589-028E-45A1-A212-948FACF1FDCD}.Release|x86.Build.0 = Release|Any CPU
119 {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
120 {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Debug|Any CPU.Build.0 = Debug|Any CPU
121 {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Debug|x64.ActiveCfg = Debug|Any CPU
122 {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Debug|x64.Build.0 = Debug|Any CPU
123 {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Debug|x86.ActiveCfg = Debug|Any CPU
124 {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Debug|x86.Build.0 = Debug|Any CPU
125 {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Release|Any CPU.ActiveCfg = Release|Any CPU
126 {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Release|Any CPU.Build.0 = Release|Any CPU
127 {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Release|x64.ActiveCfg = Release|Any CPU
128 {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Release|x64.Build.0 = Release|Any CPU
129 {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Release|x86.ActiveCfg = Release|Any CPU
130 {853716DB-C02C-41BD-91BC-79CDC0C17D10}.Release|x86.Build.0 = Release|Any CPU
131 {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
132 {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
133 {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|x64.ActiveCfg = Debug|Any CPU
134 {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|x64.Build.0 = Debug|Any CPU
135 {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|x86.ActiveCfg = Debug|Any CPU
136 {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Debug|x86.Build.0 = Debug|Any CPU
137 {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
138 {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|Any CPU.Build.0 = Release|Any CPU
139 {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|x64.ActiveCfg = Release|Any CPU
140 {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|x64.Build.0 = Release|Any CPU
141 {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|x86.ActiveCfg = Release|Any CPU
142 {23FC60D7-B101-42F8-9786-DB7A9CD964A2}.Release|x86.Build.0 = Release|Any CPU
143 EndGlobalSection
144 GlobalSection(SolutionProperties) = preSolution
145 HideSolutionNode = FALSE
146 EndGlobalSection
147 GlobalSection(NestedProjects) = preSolution
148 {C66C2503-C671-4230-8B48-1D93A8532A28} = {1284331E-BC6C-426D-AAAF-140C0174F875}
149 {E8A08E86-1780-4ED4-8F63-AB2B52C1C16B} = {1284331E-BC6C-426D-AAAF-140C0174F875}
150 {DF63F589-028E-45A1-A212-948FACF1FDCD} = {1284331E-BC6C-426D-AAAF-140C0174F875}
151 {23FC60D7-B101-42F8-9786-DB7A9CD964A2} = {1284331E-BC6C-426D-AAAF-140C0174F875}
152 EndGlobalSection
153 GlobalSection(ExtensibilityGlobals) = postSolution
154 SolutionGuid = {BB8820D5-723D-426D-B4A0-4D221603C5FA}
155 EndGlobalSection
156EndGlobal
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 @@
1<SolutionConfiguration>
2 <Settings>
3 <AllowParallelTestExecution>True</AllowParallelTestExecution>
4 <SolutionConfigured>True</SolutionConfigured>
5 </Settings>
6</SolutionConfiguration> \ 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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Bind
4{
5 using WixToolset.Data;
6 using WixToolset.Extensibility.Data;
7
8 /// <summary>
9 /// Holds a symbol and field that contain binder variables, which need to be resolved
10 /// later, once the files have been resolved.
11 /// </summary>
12 internal class DelayedField : IDelayedField
13 {
14 /// <summary>
15 /// Creates a delayed field.
16 /// </summary>
17 /// <param name="symbol">Symbol for the field.</param>
18 /// <param name="field">Field needing further resolution.</param>
19 public DelayedField(IntermediateSymbol symbol, IntermediateField field)
20 {
21 this.Symbol = symbol;
22 this.Field = field;
23 }
24
25 /// <summary>
26 /// The row containing the field.
27 /// </summary>
28 public IntermediateSymbol Symbol { get; }
29
30 /// <summary>
31 /// The field needing further resolving.
32 /// </summary>
33 public IntermediateField Field { get; }
34 }
35}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Bind
4{
5 using System;
6 using WixToolset.Extensibility.Data;
7
8 internal class ExpectedExtractFile : IExpectedExtractFile
9 {
10 public Uri Uri { get; set; }
11
12 public string EmbeddedFileId { get; set; }
13
14 public string OutputPath { get; set; }
15 }
16}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.IO;
9 using System.Linq;
10 using System.Security.Cryptography;
11 using System.Text;
12
13 /// <summary>
14 /// Internal helper class used to extract embedded files.
15 /// </summary>
16 internal class ExtractEmbeddedFiles
17 {
18 private readonly Dictionary<Uri, SortedList<string, string>> filesWithEmbeddedFiles = new Dictionary<Uri, SortedList<string, string>>();
19
20 public IEnumerable<Uri> Uris => this.filesWithEmbeddedFiles.Keys;
21
22 /// <summary>
23 /// 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.
24 /// </summary>
25 /// <param name="uri">Uri to file containing the embedded files.</param>
26 /// <param name="embeddedFileId">Id of the embedded file to extract.</param>
27 /// <param name="extractFolder">Folder where extracted files should be placed.</param>
28 /// <returns>The extract path for the embedded file.</returns>
29 public string AddEmbeddedFileToExtract(Uri uri, string embeddedFileId, string extractFolder)
30 {
31 // If the uri to the file that contains the embedded file does not already have embedded files
32 // being extracted, create the dictionary to track that.
33 if (!this.filesWithEmbeddedFiles.TryGetValue(uri, out var extracts))
34 {
35 extracts = new SortedList<string, string>(StringComparer.OrdinalIgnoreCase);
36 this.filesWithEmbeddedFiles.Add(uri, extracts);
37 }
38
39 // If the embedded file is not already tracked in the dictionary of extracts, add it.
40 if (!extracts.TryGetValue(embeddedFileId, out var extractPath))
41 {
42 var localFileNameWithoutExtension = Path.GetFileNameWithoutExtension(uri.LocalPath);
43 var unique = this.HashUri(uri.AbsoluteUri);
44 var extractedName = String.Format(CultureInfo.InvariantCulture, @"{0}_{1}\{2}", localFileNameWithoutExtension, unique, embeddedFileId);
45
46 extractPath = Path.GetFullPath(Path.Combine(extractFolder, extractedName));
47 extracts.Add(embeddedFileId, extractPath);
48 }
49
50 return extractPath;
51 }
52
53 public IReadOnlyList<ExpectedExtractFile> GetExpectedEmbeddedFiles()
54 {
55 var files = new List<ExpectedExtractFile>();
56
57 foreach (var uriWithExtracts in this.filesWithEmbeddedFiles)
58 {
59 foreach (var extracts in uriWithExtracts.Value)
60 {
61 files.Add(new ExpectedExtractFile
62 {
63 Uri = uriWithExtracts.Key,
64 EmbeddedFileId = extracts.Key,
65 OutputPath = extracts.Value,
66 });
67 }
68 }
69
70 return files;
71 }
72
73 public IEnumerable<ExpectedExtractFile> GetExtractFilesForUri(Uri uri)
74 {
75 if (!this.filesWithEmbeddedFiles.TryGetValue(uri, out var extracts))
76 {
77 extracts = new SortedList<string, string>(StringComparer.OrdinalIgnoreCase);
78 }
79
80 return extracts.Select(e => new ExpectedExtractFile { Uri = uri, EmbeddedFileId = e.Key, OutputPath = e.Value });
81 }
82
83 private string HashUri(string uri)
84 {
85 using (SHA1 sha1 = new SHA1CryptoServiceProvider())
86 {
87 var hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(uri));
88 return Convert.ToBase64String(hash).TrimEnd('=').Replace('+', '-').Replace('/', '_');
89 }
90 }
91 }
92}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using WixToolset.Data;
9 using WixToolset.Extensibility.Data;
10 using WixToolset.Extensibility.Services;
11
12 internal class ExtractEmbeddedFilesCommand
13 {
14 public ExtractEmbeddedFilesCommand(IBackendHelper backendHelper, IEnumerable<IExpectedExtractFile> embeddedFiles)
15 {
16 this.BackendHelper = backendHelper;
17 this.FilesWithEmbeddedFiles = embeddedFiles;
18 }
19
20 public IReadOnlyList<ITrackedFile> TrackedFiles { get; private set; }
21
22 private IBackendHelper BackendHelper { get; }
23
24 private IEnumerable<IExpectedExtractFile> FilesWithEmbeddedFiles { get; }
25
26 public void Execute()
27 {
28 var trackedFiles = new List<ITrackedFile>();
29 var group = this.FilesWithEmbeddedFiles.GroupBy(e => e.Uri);
30
31 foreach (var expectedEmbeddedFileByUri in group)
32 {
33 var baseUri = expectedEmbeddedFileByUri.Key;
34
35 using (var wixout = WixOutput.Read(baseUri))
36 {
37 var uniqueIds = new SortedSet<string>(StringComparer.OrdinalIgnoreCase);
38
39 foreach (var embeddedFile in expectedEmbeddedFileByUri)
40 {
41 if (uniqueIds.Add(embeddedFile.EmbeddedFileId))
42 {
43 wixout.ExtractEmbeddedFile(embeddedFile.EmbeddedFileId, embeddedFile.OutputPath);
44 trackedFiles.Add(this.BackendHelper.TrackFile(embeddedFile.OutputPath, TrackedFileType.Temporary));
45 }
46 }
47 }
48 }
49
50 this.TrackedFiles = trackedFiles;
51 }
52 }
53}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 using WixToolset.Data;
10 using WixToolset.Extensibility;
11 using WixToolset.Extensibility.Data;
12
13 internal class FileResolver
14 {
15 private const string BindPathOpenString = "!(bindpath.";
16
17 private FileResolver(IEnumerable<IBindPath> bindPaths)
18 {
19 this.BindPaths = (bindPaths ?? Array.Empty<IBindPath>()).ToLookup(b => b.Stage);
20 this.RebaseTarget = this.BindPaths[BindStage.Target].Any();
21 this.RebaseUpdated = this.BindPaths[BindStage.Updated].Any();
22 }
23
24 public FileResolver(IEnumerable<IBindPath> bindPaths, IEnumerable<IResolverExtension> extensions) : this(bindPaths)
25 {
26 this.ResolverExtensions = extensions ?? Array.Empty<IResolverExtension>();
27 }
28
29 public FileResolver(IEnumerable<IBindPath> bindPaths, IEnumerable<ILibrarianExtension> extensions) : this(bindPaths)
30 {
31 this.LibrarianExtensions = extensions ?? Array.Empty<ILibrarianExtension>();
32 }
33
34 private ILookup<BindStage, IBindPath> BindPaths { get; }
35
36 public bool RebaseTarget { get; }
37
38 public bool RebaseUpdated { get; }
39
40 private IEnumerable<IResolverExtension> ResolverExtensions { get; }
41
42 private IEnumerable<ILibrarianExtension> LibrarianExtensions { get; }
43
44 public string Resolve(SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, string source)
45 {
46 var checkedPaths = new List<string>();
47
48 foreach (var extension in this.LibrarianExtensions)
49 {
50 var resolved = extension.ResolveFile(sourceLineNumbers, symbolDefinition, source);
51
52 if (resolved?.CheckedPaths != null)
53 {
54 checkedPaths.AddRange(resolved.CheckedPaths);
55 }
56
57 if (!String.IsNullOrEmpty(resolved?.Path))
58 {
59 return resolved?.Path;
60 }
61 }
62
63 return this.MustResolveUsingBindPaths(source, symbolDefinition, sourceLineNumbers, BindStage.Normal, checkedPaths);
64 }
65
66 /// <summary>
67 /// Resolves the source path of a file using binder extensions.
68 /// </summary>
69 /// <param name="source">Original source value.</param>
70 /// <param name="symbolDefinition">Optional type of source file being resolved.</param>
71 /// <param name="sourceLineNumbers">Optional source line of source file being resolved.</param>
72 /// <param name="bindStage">The binding stage used to determine what collection of bind paths will be used</param>
73 /// <param name="alreadyCheckedPaths">Optional collection of paths already checked.</param>
74 /// <returns>Should return a valid path for the stream to be imported.</returns>
75 public string ResolveFile(string source, IntermediateSymbolDefinition symbolDefinition, SourceLineNumber sourceLineNumbers, BindStage bindStage, IEnumerable<string> alreadyCheckedPaths = null)
76 {
77 var checkedPaths = new List<string>();
78
79 if (alreadyCheckedPaths != null)
80 {
81 checkedPaths.AddRange(alreadyCheckedPaths);
82 }
83
84 foreach (var extension in this.ResolverExtensions)
85 {
86 var resolved = extension.ResolveFile(source, symbolDefinition, sourceLineNumbers, bindStage);
87
88 if (resolved?.CheckedPaths != null)
89 {
90 checkedPaths.AddRange(resolved.CheckedPaths);
91 }
92
93 if (!String.IsNullOrEmpty(resolved?.Path))
94 {
95 return resolved?.Path;
96 }
97 }
98
99 return this.MustResolveUsingBindPaths(source, symbolDefinition, sourceLineNumbers, bindStage, checkedPaths);
100 }
101
102 private string MustResolveUsingBindPaths(string source, IntermediateSymbolDefinition symbolDefinition, SourceLineNumber sourceLineNumbers, BindStage bindStage, List<string> checkedPaths)
103 {
104 string resolved = null;
105
106 // If the file exists, we're good to go.
107 checkedPaths.Add(source);
108 if (CheckFileExists(source))
109 {
110 resolved = source;
111 }
112 else if (Path.IsPathRooted(source)) // path is rooted so bindpaths won't help, bail since the file apparently doesn't exist.
113 {
114 resolved = null;
115 }
116 else // not a rooted path so let's try applying all the different source resolution options.
117 {
118 var bindName = String.Empty;
119 var path = source;
120 var pathWithoutSourceDir = String.Empty;
121
122 if (source.StartsWith(BindPathOpenString, StringComparison.Ordinal))
123 {
124 var closeParen = source.IndexOf(')', BindPathOpenString.Length);
125
126 if (-1 != closeParen)
127 {
128 bindName = source.Substring(BindPathOpenString.Length, closeParen - BindPathOpenString.Length);
129 path = source.Substring(BindPathOpenString.Length + bindName.Length + 1); // +1 for the closing brace.
130 path = path.TrimStart('\\'); // remove starting '\\' char so the path doesn't look rooted.
131 }
132 }
133 else if (source.StartsWith("SourceDir\\", StringComparison.Ordinal) || source.StartsWith("SourceDir/", StringComparison.Ordinal))
134 {
135 pathWithoutSourceDir = path.Substring(10);
136 }
137
138 var bindPaths = this.BindPaths[bindStage];
139
140 foreach (var bindPath in bindPaths)
141 {
142 if (String.IsNullOrEmpty(bindName))
143 {
144 if (String.IsNullOrEmpty(bindPath.Name))
145 {
146 if (!String.IsNullOrEmpty(pathWithoutSourceDir))
147 {
148 var filePath = Path.Combine(bindPath.Path, pathWithoutSourceDir);
149
150 checkedPaths.Add(filePath);
151 if (CheckFileExists(filePath))
152 {
153 resolved = filePath;
154 }
155 }
156
157 if (String.IsNullOrEmpty(resolved))
158 {
159 var filePath = Path.Combine(bindPath.Path, path);
160
161 checkedPaths.Add(filePath);
162 if (CheckFileExists(filePath))
163 {
164 resolved = filePath;
165 }
166 }
167 }
168 }
169 else if (bindName.Equals(bindPath.Name, StringComparison.OrdinalIgnoreCase))
170 {
171 var filePath = Path.Combine(bindPath.Path, path);
172
173 checkedPaths.Add(filePath);
174 if (CheckFileExists(filePath))
175 {
176 resolved = filePath;
177 }
178 }
179
180 if (!String.IsNullOrEmpty(resolved))
181 {
182 break;
183 }
184 }
185 }
186
187 if (null == resolved)
188 {
189 throw new WixException(ErrorMessages.FileNotFound(sourceLineNumbers, source, symbolDefinition.Name, checkedPaths));
190 }
191
192 return resolved;
193 }
194
195 private static bool CheckFileExists(string path)
196 {
197 try
198 {
199 return File.Exists(path);
200 }
201 catch (ArgumentException)
202 {
203 throw new WixException(ErrorMessages.IllegalCharactersInPath(path));
204 }
205 }
206 }
207}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.Text;
9 using WixToolset.Data;
10 using WixToolset.Extensibility.Data;
11 using WixToolset.Extensibility.Services;
12
13 /// <summary>
14 /// Resolves the fields which had variables that needed to be resolved after the file information
15 /// was loaded.
16 /// </summary>
17 internal class ResolveDelayedFieldsCommand
18 {
19 /// <summary>
20 /// Resolve delayed fields.
21 /// </summary>
22 /// <param name="messaging"></param>
23 /// <param name="delayedFields">The fields which had resolution delayed.</param>
24 /// <param name="variableCache">The cached variable values used when resolving delayed fields.</param>
25 public ResolveDelayedFieldsCommand(IMessaging messaging, IEnumerable<IDelayedField> delayedFields, Dictionary<string, string> variableCache)
26 {
27 this.Messaging = messaging;
28 this.DelayedFields = delayedFields;
29 this.VariableCache = variableCache;
30 }
31
32 private IMessaging Messaging { get; }
33
34 private IEnumerable<IDelayedField> DelayedFields { get;}
35
36 private IDictionary<string, string> VariableCache { get; }
37
38 public void Execute()
39 {
40 var deferredFields = new List<IDelayedField>();
41
42 foreach (var delayedField in this.DelayedFields)
43 {
44 try
45 {
46 var propertySymbol = delayedField.Symbol;
47
48 // process properties first in case they refer to other binder variables
49 if (delayedField.Symbol.Definition.Type == SymbolDefinitionType.Property)
50 {
51 var value = this.ResolveDelayedVariables(propertySymbol.SourceLineNumbers, delayedField.Field.AsString());
52
53 // update the variable cache with the new value
54 var key = String.Concat("property.", propertySymbol.Id.Id);
55 this.VariableCache[key] = value;
56
57 // update the field data
58 delayedField.Field.Set(value);
59 }
60 else
61 {
62 deferredFields.Add(delayedField);
63 }
64 }
65 catch (WixException we)
66 {
67 this.Messaging.Write(we.Error);
68 continue;
69 }
70 }
71
72 // add specialization for ProductVersion fields
73 var keyProductVersion = "property.ProductVersion";
74 if (this.VariableCache.TryGetValue(keyProductVersion, out var versionValue) && Version.TryParse(versionValue, out var productVersion))
75 {
76 // Don't add the variable if it already exists (developer defined a property with the same name).
77 var fieldKey = String.Concat(keyProductVersion, ".Major");
78 if (!this.VariableCache.ContainsKey(fieldKey))
79 {
80 this.VariableCache[fieldKey] = productVersion.Major.ToString(CultureInfo.InvariantCulture);
81 }
82
83 fieldKey = String.Concat(keyProductVersion, ".Minor");
84 if (!this.VariableCache.ContainsKey(fieldKey))
85 {
86 this.VariableCache[fieldKey] = productVersion.Minor.ToString(CultureInfo.InvariantCulture);
87 }
88
89 fieldKey = String.Concat(keyProductVersion, ".Build");
90 if (!this.VariableCache.ContainsKey(fieldKey))
91 {
92 this.VariableCache[fieldKey] = productVersion.Build.ToString(CultureInfo.InvariantCulture);
93 }
94
95 fieldKey = String.Concat(keyProductVersion, ".Revision");
96 if (!this.VariableCache.ContainsKey(fieldKey))
97 {
98 this.VariableCache[fieldKey] = productVersion.Revision.ToString(CultureInfo.InvariantCulture);
99 }
100 }
101
102 // process the remaining fields in case they refer to property binder variables
103 foreach (var delayedField in deferredFields)
104 {
105 try
106 {
107 var value = this.ResolveDelayedVariables(delayedField.Symbol.SourceLineNumbers, delayedField.Field.AsString());
108 delayedField.Field.Set(value);
109 }
110 catch (WixException we)
111 {
112 this.Messaging.Write(we.Error);
113 }
114 }
115 }
116
117 private string ResolveDelayedVariables(SourceLineNumber sourceLineNumbers, string value)
118 {
119 var start = 0;
120
121 while (Common.TryParseWixVariable(value, start, out var parsed))
122 {
123 if (parsed.Namespace == "bind")
124 {
125 var key = String.Concat(parsed.Name, ".", parsed.Scope);
126
127 if (!this.VariableCache.TryGetValue(key, out var resolvedValue))
128 {
129 resolvedValue = parsed.DefaultValue;
130 }
131
132 // insert the resolved value if it was found or display an error
133 if (null != resolvedValue)
134 {
135 if (parsed.Index == 0 && parsed.Length == value.Length)
136 {
137 value = resolvedValue;
138 }
139 else
140 {
141 var sb = new StringBuilder(value);
142 sb.Remove(parsed.Index, parsed.Length);
143 sb.Insert(parsed.Index, resolvedValue);
144 value = sb.ToString();
145 }
146
147 start = parsed.Index;
148 }
149 else
150 {
151 this.Messaging.Write(ErrorMessages.UnresolvedBindReference(sourceLineNumbers, value));
152 break;
153 }
154 }
155 else
156 {
157 start = parsed.Index + parsed.Length;
158 }
159 }
160
161 return value;
162 }
163 }
164}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using WixToolset.Data;
9 using WixToolset.Data.Symbols;
10 using WixToolset.Extensibility;
11 using WixToolset.Extensibility.Data;
12 using WixToolset.Extensibility.Services;
13
14 /// <summary>
15 /// Resolve source fields in the tables included in the output
16 /// </summary>
17 internal class ResolveFieldsCommand
18 {
19 public IMessaging Messaging { private get; set; }
20
21 public bool BuildingPatch { private get; set; }
22
23 public IVariableResolver VariableResolver { private get; set; }
24
25 public IEnumerable<IBindPath> BindPaths { private get; set; }
26
27 public IEnumerable<IResolverExtension> Extensions { private get; set; }
28
29 public ExtractEmbeddedFiles FilesWithEmbeddedFiles { private get; set; }
30
31 public string IntermediateFolder { private get; set; }
32
33 public Intermediate Intermediate { private get; set; }
34
35 public bool SupportDelayedResolution { private get; set; }
36
37 public bool AllowUnresolvedVariables { private get; set; }
38
39 public IReadOnlyCollection<DelayedField> DelayedFields { get; private set; }
40
41 public void Execute()
42 {
43 var delayedFields = this.SupportDelayedResolution ? new List<DelayedField>() : null;
44
45 var fileResolver = new FileResolver(this.BindPaths, this.Extensions);
46
47 // Build the column lookup only when needed.
48 Dictionary<string, WixCustomTableColumnSymbol> customColumnsById = null;
49
50 foreach (var sections in this.Intermediate.Sections)
51 {
52 foreach (var symbol in sections.Symbols)
53 {
54 foreach (var field in symbol.Fields)
55 {
56 if (field.IsNull())
57 {
58 continue;
59 }
60
61 var fieldType = field.Type;
62
63 // Custom table cells require an extra look up to the column definition as the
64 // cell's data type is always a string (because strings can store anything) but
65 // the column definition may be more specific.
66 if (symbol.Definition.Type == SymbolDefinitionType.WixCustomTableCell)
67 {
68 // We only care about the Data in a CustomTable cell.
69 if (field.Name != nameof(WixCustomTableCellSymbolFields.Data))
70 {
71 continue;
72 }
73
74 if (customColumnsById == null)
75 {
76 customColumnsById = this.Intermediate.Sections.SelectMany(s => s.Symbols.OfType<WixCustomTableColumnSymbol>()).ToDictionary(t => t.Id.Id);
77 }
78
79 if (customColumnsById.TryGetValue(symbol.Fields[(int)WixCustomTableCellSymbolFields.TableRef].AsString() + "/" + symbol.Fields[(int)WixCustomTableCellSymbolFields.ColumnRef].AsString(), out var customColumn))
80 {
81 fieldType = customColumn.Type;
82 }
83 }
84
85 // Check to make sure we're in a scenario where we can handle variable resolution.
86 if (null != delayedFields)
87 {
88 // resolve localization and wix variables
89 if (fieldType == IntermediateFieldType.String)
90 {
91 var original = field.AsString();
92 if (!String.IsNullOrEmpty(original))
93 {
94 var resolution = this.VariableResolver.ResolveVariables(symbol.SourceLineNumbers, original, !this.AllowUnresolvedVariables);
95 if (resolution.UpdatedValue)
96 {
97 field.Set(resolution.Value);
98 }
99
100 if (resolution.DelayedResolve)
101 {
102 delayedFields.Add(new DelayedField(symbol, field));
103 }
104 }
105 }
106 }
107
108 // Move to next symbol if we've hit an error resolving variables.
109 if (this.Messaging.EncounteredError) // TODO: make this error handling more specific to just the failure to resolve variables in this field.
110 {
111 continue;
112 }
113
114 // Resolve file paths
115 if (fieldType == IntermediateFieldType.Path)
116 {
117 this.ResolvePathField(fileResolver, symbol, field);
118
119#if TODO_PATCHING
120 if (null != objectField.PreviousData)
121 {
122 objectField.PreviousData = this.BindVariableResolver.ResolveVariables(symbol.SourceLineNumbers, objectField.PreviousData, false, out isDefault);
123
124 if (!Messaging.Instance.EncounteredError) // TODO: make this error handling more specific to just the failure to resolve variables in this field.
125 {
126 // file is compressed in a cabinet (and not modified above)
127 if (objectField.PreviousEmbeddedFileIndex.HasValue && isDefault)
128 {
129 // when loading transforms from disk, PreviousBaseUri may not have been set
130 if (null == objectField.PreviousBaseUri)
131 {
132 objectField.PreviousBaseUri = objectField.BaseUri;
133 }
134
135 string extractPath = this.FilesWithEmbeddedFiles.AddEmbeddedFileIndex(objectField.PreviousBaseUri, objectField.PreviousEmbeddedFileIndex.Value, this.IntermediateFolder);
136
137 // set the path to the file once its extracted from the cabinet
138 objectField.PreviousData = extractPath;
139 }
140 else if (null != objectField.PreviousData) // non-compressed file (or localized value)
141 {
142 try
143 {
144 if (!fileResolver.RebaseTarget && !fileResolver.RebaseUpdated)
145 {
146 // resolve the path to the file
147 objectField.PreviousData = fileResolver.ResolveFile((string)objectField.PreviousData, symbol.Definition.Name, symbol.SourceLineNumbers, BindStage.Normal);
148 }
149 else
150 {
151 if (fileResolver.RebaseTarget)
152 {
153 // if -bt is used, it come here
154 // Try to use the original unresolved source from either target build or update build
155 // If both target and updated are of old wixpdb, it behaves the same as today, no re-base logic here
156 // If target is old version and updated is new version, it uses unresolved path from updated build
157 // If both target and updated are of new versions, it uses unresolved path from target build
158 if (null != objectField.UnresolvedPreviousData || null != objectField.UnresolvedData)
159 {
160 objectField.PreviousData = objectField.UnresolvedPreviousData ?? objectField.UnresolvedData;
161 }
162 }
163
164 // resolve the path to the file
165 objectField.PreviousData = fileResolver.ResolveFile((string)objectField.PreviousData, symbol.Definition.Name, symbol.SourceLineNumbers, BindStage.Target);
166
167 }
168 }
169 catch (WixFileNotFoundException)
170 {
171 // display the error with source line information
172 Messaging.Instance.Write(WixErrors.FileNotFound(symbol.SourceLineNumbers, (string)objectField.PreviousData));
173 }
174 }
175 }
176 }
177#endif
178 }
179 }
180 }
181 }
182
183 this.DelayedFields = delayedFields;
184 }
185
186 private void ResolvePathField(FileResolver fileResolver, IntermediateSymbol symbol, IntermediateField field)
187 {
188 var fieldValue = field.AsPath();
189 var originalFieldPath = fieldValue.Path;
190
191#if TODO_PATCHING
192 // Skip file resolution if the file is to be deleted.
193 if (RowOperation.Delete == symbol.Operation)
194 {
195 continue;
196 }
197#endif
198
199 // If the file is embedded and if the previous value has a bind variable in the path
200 // which gets modified by resolving the previous value again then switch to that newly
201 // resolved path instead of using the embedded file.
202 if (fieldValue.Embed && field.PreviousValue != null)
203 {
204 var resolution = this.VariableResolver.ResolveVariables(symbol.SourceLineNumbers, field.PreviousValue.AsString(), errorOnUnknown: false);
205
206 if (resolution.UpdatedValue && !resolution.IsDefault)
207 {
208 fieldValue = new IntermediateFieldPathValue { Path = resolution.Value };
209 }
210 }
211
212 // If we're still using the embedded file.
213 if (fieldValue.Embed)
214 {
215 // Set the path to the embedded file once where it will be extracted.
216 var extractPath = this.FilesWithEmbeddedFiles.AddEmbeddedFileToExtract(fieldValue.BaseUri, fieldValue.Path, this.IntermediateFolder);
217
218 field.Set(extractPath);
219 }
220 else if (fieldValue.Path != null)
221 {
222 try
223 {
224 var resolvedPath = fieldValue.Path;
225
226 if (!this.BuildingPatch) // Normal binding for non-Patch scenario such as link (light.exe)
227 {
228#if TODO_PATCHING
229 // keep a copy of the un-resolved data for future replay. This will be saved into wixpdb file
230 if (null == objectField.UnresolvedData)
231 {
232 objectField.UnresolvedData = (string)objectField.Data;
233 }
234#endif
235 resolvedPath = fileResolver.ResolveFile(fieldValue.Path, symbol.Definition, symbol.SourceLineNumbers, BindStage.Normal);
236 }
237 else if (!fileResolver.RebaseTarget && !fileResolver.RebaseUpdated) // Normal binding for Patch Scenario (normal patch, no re-basing logic)
238 {
239 resolvedPath = fileResolver.ResolveFile(fieldValue.Path, symbol.Definition, symbol.SourceLineNumbers, BindStage.Normal);
240 }
241#if TODO_PATCHING
242 else // Re-base binding path scenario caused by pyro.exe -bt -bu
243 {
244 // by default, use the resolved Data for file lookup
245 string filePathToResolve = (string)objectField.Data;
246
247 // if -bu is used in pyro command, this condition holds true and the tool
248 // will use pre-resolved source for new wixpdb file
249 if (fileResolver.RebaseUpdated)
250 {
251 // try to use the unResolved Source if it exists.
252 // New version of wixpdb file keeps a copy of pre-resolved Source. i.e. !(bindpath.test)\foo.dll
253 // Old version of winpdb file does not contain this attribute and the value is null.
254 if (null != objectField.UnresolvedData)
255 {
256 filePathToResolve = objectField.UnresolvedData;
257 }
258 }
259
260 objectField.Data = fileResolver.ResolveFile(filePathToResolve, symbol.Definition.Name, symbol.SourceLineNumbers, BindStage.Updated);
261 }
262#endif
263
264 if (!String.Equals(originalFieldPath, resolvedPath, StringComparison.OrdinalIgnoreCase))
265 {
266 field.Set(resolvedPath);
267 }
268 }
269 catch (WixException e)
270 {
271 this.Messaging.Write(e.Error);
272 }
273 }
274 }
275 }
276}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using WixToolset.Core.Native;
9 using WixToolset.Data;
10 using WixToolset.Extensibility;
11 using WixToolset.Extensibility.Data;
12 using WixToolset.Extensibility.Services;
13
14 internal class TransferFilesCommand
15 {
16 public TransferFilesCommand(IMessaging messaging, IEnumerable<ILayoutExtension> extensions, IEnumerable<IFileTransfer> fileTransfers, bool resetAcls)
17 {
18 this.Extensions = extensions;
19 this.Messaging = messaging;
20 this.FileTransfers = fileTransfers;
21 this.ResetAcls = resetAcls;
22 }
23
24 private IMessaging Messaging { get; }
25
26 private IEnumerable<ILayoutExtension> Extensions { get; }
27
28 private IEnumerable<IFileTransfer> FileTransfers { get; }
29
30 private bool ResetAcls { get; }
31
32 public void Execute()
33 {
34 var destinationFiles = new List<string>();
35
36 foreach (var fileTransfer in this.FileTransfers)
37 {
38 // If the source and destination are identical, then there's nothing to do here
39 if (0 == String.Compare(fileTransfer.Source, fileTransfer.Destination, StringComparison.OrdinalIgnoreCase))
40 {
41 fileTransfer.Redundant = true;
42 continue;
43 }
44
45 var retry = false;
46 do
47 {
48 try
49 {
50 if (fileTransfer.Move)
51 {
52 this.Messaging.Write(VerboseMessages.MoveFile(fileTransfer.Source, fileTransfer.Destination));
53 this.MoveFile(fileTransfer.Source, fileTransfer.Destination);
54 }
55 else
56 {
57 this.Messaging.Write(VerboseMessages.CopyFile(fileTransfer.Source, fileTransfer.Destination));
58 this.CopyFile(fileTransfer.Source, fileTransfer.Destination);
59 }
60
61 retry = false;
62 destinationFiles.Add(fileTransfer.Destination);
63 }
64 catch (FileNotFoundException e)
65 {
66 throw new WixException(ErrorMessages.FileNotFound(fileTransfer.SourceLineNumbers, e.FileName));
67 }
68 catch (DirectoryNotFoundException)
69 {
70 // if we already retried, give up
71 if (retry)
72 {
73 throw;
74 }
75
76 var directory = Path.GetDirectoryName(fileTransfer.Destination);
77 this.Messaging.Write(VerboseMessages.CreateDirectory(directory));
78 Directory.CreateDirectory(directory);
79 retry = true;
80 }
81 catch (UnauthorizedAccessException)
82 {
83 // if we already retried, give up
84 if (retry)
85 {
86 throw;
87 }
88
89 if (File.Exists(fileTransfer.Destination))
90 {
91 this.Messaging.Write(VerboseMessages.RemoveDestinationFile(fileTransfer.Destination));
92
93 // try to ensure the file is not read-only
94 var attributes = File.GetAttributes(fileTransfer.Destination);
95 try
96 {
97 File.SetAttributes(fileTransfer.Destination, attributes & ~FileAttributes.ReadOnly);
98 }
99 catch (ArgumentException) // thrown for unauthorized access errors
100 {
101 throw new WixException(ErrorMessages.UnauthorizedAccess(fileTransfer.Destination));
102 }
103
104 // try to delete the file
105 try
106 {
107 File.Delete(fileTransfer.Destination);
108 }
109 catch (IOException)
110 {
111 throw new WixException(ErrorMessages.FileInUse(null, fileTransfer.Destination));
112 }
113
114 retry = true;
115 }
116 else // no idea what just happened, bail
117 {
118 throw;
119 }
120 }
121 catch (IOException)
122 {
123 // if we already retried, give up
124 if (retry)
125 {
126 throw;
127 }
128
129 if (File.Exists(fileTransfer.Destination))
130 {
131 this.Messaging.Write(VerboseMessages.RemoveDestinationFile(fileTransfer.Destination));
132
133 // ensure the file is not read-only, then delete it
134 var attributes = File.GetAttributes(fileTransfer.Destination);
135 File.SetAttributes(fileTransfer.Destination, attributes & ~FileAttributes.ReadOnly);
136 try
137 {
138 File.Delete(fileTransfer.Destination);
139 }
140 catch (IOException)
141 {
142 throw new WixException(ErrorMessages.FileInUse(null, fileTransfer.Destination));
143 }
144
145 retry = true;
146 }
147 else // no idea what just happened, bail
148 {
149 throw;
150 }
151 }
152 } while (retry);
153 }
154
155 // Finally, if directed then reset remove ACLs that may may have been picked up
156 // during the file transfer process.
157 if (this.ResetAcls && 0 < destinationFiles.Count)
158 {
159 try
160 {
161 FileSystem.ResetAcls(destinationFiles);
162 }
163 catch (Exception e)
164 {
165 this.Messaging.Write(WarningMessages.UnableToResetAcls(e.Message));
166 }
167 }
168 }
169
170 private void CopyFile(string source, string destination)
171 {
172 foreach (var extension in this.Extensions)
173 {
174 if (extension.CopyFile(source, destination))
175 {
176 return;
177 }
178 }
179
180 FileSystem.CopyFile(source, destination, allowHardlink: true);
181 }
182
183 private void MoveFile(string source, string destination)
184 {
185 foreach (var extension in this.Extensions)
186 {
187 if (extension.MoveFile(source, destination))
188 {
189 return;
190 }
191 }
192
193 FileSystem.MoveFile(source, destination);
194 }
195 }
196}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Threading;
8 using WixToolset.Data;
9 using WixToolset.Extensibility;
10 using WixToolset.Extensibility.Data;
11
12 internal class BindContext : IBindContext
13 {
14 internal BindContext(IServiceProvider serviceProvider)
15 {
16 this.ServiceProvider = serviceProvider;
17 }
18
19 public IServiceProvider ServiceProvider { get; }
20
21 public IReadOnlyCollection<BindPath> BindPaths { get; set; }
22
23 public string BurnStubPath { get; set; }
24
25 public int CabbingThreadCount { get; set; }
26
27 public string CabCachePath { get; set; }
28
29 public CompressionLevel? DefaultCompressionLevel { get; set; }
30
31 public IReadOnlyCollection<IDelayedField> DelayedFields { get; set; }
32
33 public IReadOnlyCollection<IExpectedExtractFile> ExpectedEmbeddedFiles { get; set; }
34
35 public IReadOnlyCollection<IBinderExtension> Extensions { get; set; }
36
37 public IReadOnlyCollection<IFileSystemExtension> FileSystemExtensions { get; set; }
38
39 public IReadOnlyCollection<string> Ices { get; set; }
40
41 public string IntermediateFolder { get; set; }
42
43 public Intermediate IntermediateRepresentation { get; set; }
44
45 public string OutputPath { get; set; }
46
47 public PdbType PdbType { get; set; }
48
49 public string PdbPath { get; set; }
50
51 public int? ResolvedCodepage { get; set; }
52
53 public int? ResolvedSummaryInformationCodepage { get; set; }
54
55 public int? ResolvedLcid { get; set; }
56
57 public IReadOnlyCollection<string> SuppressIces { get; set; }
58
59 public bool SuppressValidation { get; set; }
60
61 public bool SuppressLayout { get; set; }
62
63 public CancellationToken CancellationToken { get; set; }
64 }
65}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using WixToolset.Extensibility.Data;
6
7 /// <summary>
8 /// Bind file with its path.
9 /// </summary>
10 internal class BindFileWithPath : IBindFileWithPath
11 {
12 /// <summary>
13 /// Gets or sets the identifier of the file with this path.
14 /// </summary>
15 public string Id { get; set; }
16
17 /// <summary>
18 /// Gets or sets the file path.
19 /// </summary>
20 public string Path { get; set; }
21 }
22}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System.Diagnostics;
6 using WixToolset.Extensibility.Data;
7
8 /// <summary>
9 /// Bind path representation.
10 /// </summary>
11 [DebuggerDisplay("Name={Name,nq} Path={Path,nq}")]
12 internal class BindPath : IBindPath
13 {
14 public string Name { get; set; }
15
16 public string Path { get; set; }
17
18 public BindStage Stage { get; set; }
19 }
20}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System;
6 using System.Collections.Generic;
7 using WixToolset.Data;
8 using WixToolset.Extensibility.Data;
9
10 internal class BindResult : IBindResult
11 {
12 private bool disposed;
13
14 public IReadOnlyCollection<IFileTransfer> FileTransfers { get; set; }
15
16 public IReadOnlyCollection<ITrackedFile> TrackedFiles { get; set; }
17
18 public WixOutput Wixout { get; set; }
19
20 #region IDisposable Support
21 /// <summary>
22 /// Disposes of the internal state of the file structure.
23 /// </summary>
24 public void Dispose()
25 {
26 this.Dispose(true);
27 GC.SuppressFinalize(this);
28 }
29
30 /// <summary>
31 /// Disposes of the internsl state of the file structure.
32 /// </summary>
33 /// <param name="disposing">True if disposing.</param>
34 protected virtual void Dispose(bool disposing)
35 {
36 if (!this.disposed)
37 {
38 if (disposing)
39 {
40 this.Wixout?.Dispose();
41 }
42 }
43
44 this.disposed = true;
45 }
46 #endregion
47 }
48}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System;
6 using System.Diagnostics;
7 using System.Linq;
8 using System.Reflection;
9 using WixToolset.Data;
10 using WixToolset.Data.Symbols;
11 using WixToolset.Extensibility;
12 using WixToolset.Extensibility.Data;
13 using WixToolset.Extensibility.Services;
14
15 /// <summary>
16 /// Binder of the WiX toolset.
17 /// </summary>
18 internal class Binder : IBinder
19 {
20 internal Binder(IServiceProvider serviceProvider)
21 {
22 this.ServiceProvider = serviceProvider;
23 }
24
25 public IServiceProvider ServiceProvider { get; }
26
27 public IBindResult Bind(IBindContext context)
28 {
29 // Prebind.
30 //
31 foreach (var extension in context.Extensions)
32 {
33 extension.PreBind(context);
34 }
35
36 // Bind.
37 //
38 this.WriteBuildInfoSymbol(context.IntermediateRepresentation, context.OutputPath, context.PdbPath);
39
40 var bindResult = this.BackendBind(context);
41
42 if (bindResult != null)
43 {
44 // Postbind.
45 //
46 foreach (var extension in context.Extensions)
47 {
48 extension.PostBind(bindResult);
49 }
50 }
51
52 return bindResult;
53 }
54
55 private IBindResult BackendBind(IBindContext context)
56 {
57 var extensionManager = context.ServiceProvider.GetService<IExtensionManager>();
58
59 var backendFactories = extensionManager.GetServices<IBackendFactory>();
60
61 var entrySection = context.IntermediateRepresentation.Sections.First();
62
63 foreach (var factory in backendFactories)
64 {
65 if (factory.TryCreateBackend(entrySection.Type.ToString(), context.OutputPath, out var backend))
66 {
67 var result = backend.Bind(context);
68 return result;
69 }
70 }
71
72 // TODO: messaging that a backend could not be found to bind the output type?
73
74 return null;
75 }
76
77 private void WriteBuildInfoSymbol(Intermediate output, string outputFile, string outputPdbPath)
78 {
79 var entrySection = output.Sections.First(s => s.Type != SectionType.Fragment);
80
81 var executingAssembly = Assembly.GetExecutingAssembly();
82 var fileVersion = FileVersionInfo.GetVersionInfo(executingAssembly.Location);
83
84 var buildInfoSymbol = entrySection.AddSymbol(new WixBuildInfoSymbol()
85 {
86 WixVersion = fileVersion.FileVersion,
87 WixOutputFile = outputFile,
88 });
89
90 if (!String.IsNullOrEmpty(outputPdbPath))
91 {
92 buildInfoSymbol.WixPdbFile = outputPdbPath;
93 }
94 }
95 }
96}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.CommandLine
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 using System.Threading;
10 using System.Threading.Tasks;
11 using System.Xml.Linq;
12 using WixToolset.Data;
13 using WixToolset.Extensibility;
14 using WixToolset.Extensibility.Data;
15 using WixToolset.Extensibility.Services;
16
17 internal class BuildCommand : ICommandLineCommand
18 {
19 private readonly CommandLine commandLine;
20
21 public BuildCommand(IServiceProvider serviceProvider)
22 {
23 this.ServiceProvider = serviceProvider;
24 this.Messaging = serviceProvider.GetService<IMessaging>();
25 this.ExtensionManager = serviceProvider.GetService<IExtensionManager>();
26 this.commandLine = new CommandLine(this.ServiceProvider, this.Messaging);
27 }
28
29 public bool ShowLogo => this.commandLine.ShowLogo;
30
31 public bool StopParsing => this.commandLine.ShowHelp;
32
33 private IServiceProvider ServiceProvider { get; }
34
35 private IMessaging Messaging { get; }
36
37 private IExtensionManager ExtensionManager { get; }
38
39 private string IntermediateFolder { get; set; }
40
41 private OutputType OutputType { get; set; }
42
43 private List<string> IncludeSearchPaths { get; set; }
44
45 public string PdbFile { get; set; }
46
47 public PdbType PdbType { get; set; }
48
49 private Platform Platform { get; set; }
50
51 private string OutputFile { get; set; }
52
53 private CompressionLevel? DefaultCompressionLevel { get; set; }
54
55 private string ContentsFile { get; set; }
56
57 private string OutputsFile { get; set; }
58
59 private string BuiltOutputsFile { get; set; }
60
61 public Task<int> ExecuteAsync(CancellationToken cancellationToken)
62 {
63 if (this.commandLine.ShowHelp)
64 {
65 Console.WriteLine("TODO: Show build command help");
66 return Task.FromResult(-1);
67 }
68
69 this.IntermediateFolder = this.commandLine.CalculateIntermedateFolder();
70
71 this.OutputType = this.commandLine.CalculateOutputType();
72
73 this.IncludeSearchPaths = this.commandLine.IncludeSearchPaths;
74
75 this.PdbFile = this.commandLine.PdbFile;
76
77 this.PdbType = this.commandLine.PdbType;
78
79 this.Platform = this.commandLine.Platform;
80
81 this.ContentsFile = this.commandLine.ContentsFile;
82
83 this.OutputsFile = this.commandLine.OutputsFile;
84
85 this.BuiltOutputsFile = this.commandLine.BuiltOutputsFile;
86
87 this.DefaultCompressionLevel = this.commandLine.DefaultCompressionLevel;
88
89 var preprocessorVariables = this.commandLine.GatherPreprocessorVariables();
90
91 var sourceFiles = this.commandLine.GatherSourceFiles(this.IntermediateFolder);
92
93 var filterCultures = this.commandLine.CalculateFilterCultures();
94
95 var creator = this.ServiceProvider.GetService<ISymbolDefinitionCreator>();
96
97 this.EvaluateSourceFiles(sourceFiles, creator, out var codeFiles, out var wixipl);
98
99 this.OutputFile = this.commandLine.OutputFile;
100
101 if (String.IsNullOrEmpty(this.OutputFile))
102 {
103 if (codeFiles.Count == 1)
104 {
105 // If output type is unknown, the extension will be replaced with the right default based on output type.
106 this.OutputFile = Path.ChangeExtension(codeFiles[0].OutputPath, DefaultExtensionForOutputType(this.OutputType));
107 }
108 else
109 {
110 this.Messaging.Write(ErrorMessages.MustSpecifyOutputWithMoreThanOneInput());
111 }
112 }
113
114 if (this.Messaging.EncounteredError)
115 {
116 return Task.FromResult(this.Messaging.LastErrorNumber);
117 }
118
119 var wixobjs = this.CompilePhase(preprocessorVariables, codeFiles, cancellationToken);
120
121 var wxls = this.LoadLocalizationFiles(this.commandLine.LocalizationFilePaths, preprocessorVariables, cancellationToken);
122
123 if (this.Messaging.EncounteredError)
124 {
125 return Task.FromResult(this.Messaging.LastErrorNumber);
126 }
127
128 if (this.OutputType == OutputType.Library)
129 {
130 using (new IntermediateFieldContext("wix.lib"))
131 {
132 var wixlib = this.LibraryPhase(wixobjs, wxls, this.commandLine.BindFiles, this.commandLine.BindPaths, cancellationToken);
133
134 if (!this.Messaging.EncounteredError)
135 {
136 wixlib.Save(this.OutputFile);
137 }
138 }
139 }
140 else
141 {
142 using (new IntermediateFieldContext("wix.link"))
143 {
144 if (wixipl == null)
145 {
146 wixipl = this.LinkPhase(wixobjs, this.commandLine.LibraryFilePaths, creator, cancellationToken);
147 }
148
149 if (!this.Messaging.EncounteredError)
150 {
151 var outputExtension = Path.GetExtension(this.OutputFile);
152 if (String.IsNullOrEmpty(outputExtension) || ".wix" == outputExtension)
153 {
154 var entrySectionType = wixipl.Sections.Single().Type;
155 this.OutputFile = Path.ChangeExtension(this.OutputFile, DefaultExtensionForSectionType(entrySectionType));
156 }
157
158 if (this.OutputType == OutputType.IntermediatePostLink)
159 {
160 wixipl.Save(this.OutputFile);
161 }
162 else
163 {
164 using (new IntermediateFieldContext("wix.bind"))
165 {
166 this.BindPhase(wixipl, wxls, filterCultures, this.commandLine.CabCachePath, this.commandLine.BindPaths, cancellationToken);
167 }
168 }
169 }
170 }
171 }
172
173 return Task.FromResult(this.Messaging.LastErrorNumber);
174 }
175
176 public bool TryParseArgument(ICommandLineParser parser, string argument)
177 {
178 return this.commandLine.TryParseArgument(argument, parser);
179 }
180
181 private void EvaluateSourceFiles(IEnumerable<SourceFile> sourceFiles, ISymbolDefinitionCreator creator, out List<SourceFile> codeFiles, out Intermediate wixipl)
182 {
183 codeFiles = new List<SourceFile>();
184
185 wixipl = null;
186
187 foreach (var sourceFile in sourceFiles)
188 {
189 var extension = Path.GetExtension(sourceFile.SourcePath);
190
191 if (wixipl != null || ".wxs".Equals(extension, StringComparison.OrdinalIgnoreCase))
192 {
193 codeFiles.Add(sourceFile);
194 }
195 else
196 {
197 try
198 {
199 wixipl = Intermediate.Load(sourceFile.SourcePath, creator);
200 }
201 catch (WixException)
202 {
203 // We'll assume anything that isn't a valid intermediate is source code to compile.
204 codeFiles.Add(sourceFile);
205 }
206 }
207 }
208
209 if (wixipl == null && codeFiles.Count == 0)
210 {
211 this.Messaging.Write(ErrorMessages.NoSourceFiles());
212 }
213 else if (wixipl != null && codeFiles.Count != 0)
214 {
215 this.Messaging.Write(ErrorMessages.WixiplSourceFileIsExclusive());
216 }
217 }
218
219 private IReadOnlyList<Intermediate> CompilePhase(IDictionary<string, string> preprocessorVariables, IEnumerable<SourceFile> sourceFiles, CancellationToken cancellationToken)
220 {
221 var intermediates = new List<Intermediate>();
222
223 foreach (var sourceFile in sourceFiles)
224 {
225 var document = this.Preprocess(preprocessorVariables, sourceFile.SourcePath, cancellationToken);
226
227 if (this.Messaging.EncounteredError)
228 {
229 continue;
230 }
231
232 var context = this.ServiceProvider.GetService<ICompileContext>();
233 context.Extensions = this.ExtensionManager.GetServices<ICompilerExtension>();
234 context.Platform = this.Platform;
235 context.Source = document;
236 context.CancellationToken = cancellationToken;
237
238 Intermediate intermediate = null;
239 try
240 {
241 var compiler = this.ServiceProvider.GetService<ICompiler>();
242 intermediate = compiler.Compile(context);
243 }
244 catch (WixException e)
245 {
246 this.Messaging.Write(e.Error);
247 }
248
249 if (this.Messaging.EncounteredError)
250 {
251 continue;
252 }
253
254 intermediates.Add(intermediate);
255 }
256
257 return intermediates;
258 }
259
260 private Intermediate LibraryPhase(IReadOnlyCollection<Intermediate> intermediates, IReadOnlyCollection<Localization> localizations, bool bindFiles, IReadOnlyCollection<IBindPath> bindPaths, CancellationToken cancellationToken)
261 {
262 var context = this.ServiceProvider.GetService<ILibraryContext>();
263 context.BindFiles = bindFiles;
264 context.BindPaths = bindPaths;
265 context.Extensions = this.ExtensionManager.GetServices<ILibrarianExtension>();
266 context.Localizations = localizations;
267 context.Intermediates = intermediates;
268 context.CancellationToken = cancellationToken;
269
270 Intermediate library = null;
271 try
272 {
273 var librarian = this.ServiceProvider.GetService<ILibrarian>();
274 library = librarian.Combine(context);
275 }
276 catch (WixException e)
277 {
278 this.Messaging.Write(e.Error);
279 }
280
281 return library;
282 }
283
284 private Intermediate LinkPhase(IEnumerable<Intermediate> intermediates, IEnumerable<string> libraryFiles, ISymbolDefinitionCreator creator, CancellationToken cancellationToken)
285 {
286 var libraries = this.LoadLibraries(libraryFiles, creator);
287
288 if (this.Messaging.EncounteredError)
289 {
290 return null;
291 }
292
293 var context = this.ServiceProvider.GetService<ILinkContext>();
294 context.Extensions = this.ExtensionManager.GetServices<ILinkerExtension>();
295 context.ExtensionData = this.ExtensionManager.GetServices<IExtensionData>();
296 context.ExpectedOutputType = this.OutputType;
297 context.Intermediates = intermediates.Concat(libraries).ToList();
298 context.SymbolDefinitionCreator = creator;
299 context.CancellationToken = cancellationToken;
300
301 var linker = this.ServiceProvider.GetService<ILinker>();
302 return linker.Link(context);
303 }
304
305 private void BindPhase(Intermediate output, IReadOnlyCollection<Localization> localizations, IReadOnlyCollection<string> filterCultures, string cabCachePath, IReadOnlyCollection<IBindPath> bindPaths, CancellationToken cancellationToken)
306 {
307 var intermediateFolder = this.IntermediateFolder;
308 if (String.IsNullOrEmpty(intermediateFolder))
309 {
310 intermediateFolder = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
311 }
312
313 IResolveResult resolveResult;
314 {
315 var context = this.ServiceProvider.GetService<IResolveContext>();
316 context.BindPaths = bindPaths;
317 context.Extensions = this.ExtensionManager.GetServices<IResolverExtension>();
318 context.ExtensionData = this.ExtensionManager.GetServices<IExtensionData>();
319 context.FilterCultures = filterCultures;
320 context.IntermediateFolder = intermediateFolder;
321 context.IntermediateRepresentation = output;
322 context.Localizations = localizations;
323 context.CancellationToken = cancellationToken;
324
325 var resolver = this.ServiceProvider.GetService<IResolver>();
326 resolveResult = resolver.Resolve(context);
327 }
328
329 if (this.Messaging.EncounteredError)
330 {
331 return;
332 }
333
334 IBindResult bindResult = null;
335 try
336 {
337 {
338 var context = this.ServiceProvider.GetService<IBindContext>();
339 //context.CabbingThreadCount = this.CabbingThreadCount;
340 context.CabCachePath = cabCachePath;
341 context.ResolvedCodepage = resolveResult.Codepage;
342 context.ResolvedSummaryInformationCodepage = resolveResult.SummaryInformationCodepage;
343 context.ResolvedLcid = resolveResult.PackageLcid;
344 context.DefaultCompressionLevel = this.DefaultCompressionLevel;
345 context.DelayedFields = resolveResult.DelayedFields;
346 context.ExpectedEmbeddedFiles = resolveResult.ExpectedEmbeddedFiles;
347 context.Extensions = this.ExtensionManager.GetServices<IBinderExtension>();
348 context.FileSystemExtensions = this.ExtensionManager.GetServices<IFileSystemExtension>();
349 context.Ices = this.commandLine.Ices;
350 context.IntermediateFolder = intermediateFolder;
351 context.IntermediateRepresentation = resolveResult.IntermediateRepresentation;
352 context.OutputPath = this.OutputFile;
353 context.PdbType = this.PdbType;
354 context.PdbPath = this.PdbType == PdbType.None ? null : this.PdbFile ?? Path.ChangeExtension(this.OutputFile, ".wixpdb");
355 context.SuppressIces = this.commandLine.SuppressIces;
356 context.SuppressValidation = this.commandLine.SuppressValidation;
357 context.CancellationToken = cancellationToken;
358
359 var binder = this.ServiceProvider.GetService<IBinder>();
360 bindResult = binder.Bind(context);
361 }
362
363 if (this.Messaging.EncounteredError)
364 {
365 return;
366 }
367
368 {
369 var context = this.ServiceProvider.GetService<ILayoutContext>();
370 context.Extensions = this.ExtensionManager.GetServices<ILayoutExtension>();
371 context.TrackedFiles = bindResult.TrackedFiles;
372 context.FileTransfers = bindResult.FileTransfers;
373 context.IntermediateFolder = intermediateFolder;
374 context.ContentsFile = this.ContentsFile;
375 context.OutputsFile = this.OutputsFile;
376 context.BuiltOutputsFile = this.BuiltOutputsFile;
377 context.ResetAcls = this.commandLine.ResetAcls;
378 context.CancellationToken = cancellationToken;
379
380 var layout = this.ServiceProvider.GetService<ILayoutCreator>();
381 layout.Layout(context);
382 }
383 }
384 finally
385 {
386 bindResult?.Dispose();
387 }
388 }
389
390 private IEnumerable<Intermediate> LoadLibraries(IEnumerable<string> libraryFiles, ISymbolDefinitionCreator creator)
391 {
392 try
393 {
394 return Intermediate.Load(libraryFiles, creator);
395 }
396 catch (WixCorruptFileException e)
397 {
398 this.Messaging.Write(e.Error);
399 }
400 catch (WixUnexpectedFileFormatException e)
401 {
402 this.Messaging.Write(e.Error);
403 }
404
405 return Array.Empty<Intermediate>();
406 }
407
408 private IReadOnlyList<Localization> LoadLocalizationFiles(IEnumerable<string> locFiles, IDictionary<string, string> preprocessorVariables, CancellationToken cancellationToken)
409 {
410 var localizations = new List<Localization>();
411 var parser = this.ServiceProvider.GetService<ILocalizationParser>();
412
413 foreach (var loc in locFiles)
414 {
415 var document = this.Preprocess(preprocessorVariables, loc, cancellationToken);
416
417 if (this.Messaging.EncounteredError)
418 {
419 continue;
420 }
421
422 var localization = parser.ParseLocalization(document);
423 localizations.Add(localization);
424 }
425
426 return localizations;
427 }
428
429 private XDocument Preprocess(IDictionary<string, string> preprocessorVariables, string sourcePath, CancellationToken cancellationToken)
430 {
431 var context = this.ServiceProvider.GetService<IPreprocessContext>();
432 context.Extensions = this.ExtensionManager.GetServices<IPreprocessorExtension>();
433 context.Platform = this.Platform;
434 context.IncludeSearchPaths = this.IncludeSearchPaths;
435 context.SourcePath = sourcePath;
436 context.Variables = preprocessorVariables;
437 context.CancellationToken = cancellationToken;
438
439 IPreprocessResult result = null;
440 try
441 {
442 var preprocessor = this.ServiceProvider.GetService<IPreprocessor>();
443 result = preprocessor.Preprocess(context);
444 }
445 catch (WixException e)
446 {
447 this.Messaging.Write(e.Error);
448 }
449
450 return result?.Document;
451 }
452
453 private static string DefaultExtensionForSectionType(SectionType sectionType)
454 {
455 switch (sectionType)
456 {
457 case SectionType.Bundle:
458 return ".exe";
459 case SectionType.Module:
460 return ".msm";
461 case SectionType.Product:
462 return ".msi";
463 case SectionType.PatchCreation:
464 return ".pcp";
465 case SectionType.Patch:
466 return ".msp";
467 case SectionType.Fragment:
468 case SectionType.Unknown:
469 default:
470 return ".wix";
471 }
472 }
473
474 private static string DefaultExtensionForOutputType(OutputType outputType)
475 {
476 switch (outputType)
477 {
478 case OutputType.Bundle:
479 return ".exe";
480 case OutputType.Library:
481 return ".wixlib";
482 case OutputType.Module:
483 return ".msm";
484 case OutputType.Patch:
485 return ".msp";
486 case OutputType.PatchCreation:
487 return ".pcp";
488 case OutputType.Product:
489 return ".msi";
490 case OutputType.Transform:
491 return ".mst";
492 case OutputType.IntermediatePostLink:
493 return ".wixipl";
494 case OutputType.Unknown:
495 default:
496 return ".wix";
497 }
498 }
499
500 private class CommandLine
501 {
502 private static readonly char[] BindPathSplit = { '=' };
503
504 public bool BindFiles { get; private set; }
505
506 public List<IBindPath> BindPaths { get; } = new List<IBindPath>();
507
508 public string CabCachePath { get; private set; }
509
510 public List<string> Cultures { get; } = new List<string>();
511
512 public List<string> Defines { get; } = new List<string>();
513
514 public List<string> IncludeSearchPaths { get; } = new List<string>();
515
516 public List<string> LocalizationFilePaths { get; } = new List<string>();
517
518 public List<string> LibraryFilePaths { get; } = new List<string>();
519
520 public List<string> SourceFilePaths { get; } = new List<string>();
521
522 public Platform Platform { get; private set; }
523
524 public string PdbFile { get; private set; }
525
526 public PdbType PdbType { get; private set; }
527
528 public bool ShowLogo { get; private set; }
529
530 public bool ShowHelp { get; private set; }
531
532 public string IntermediateFolder { get; private set; }
533
534 public string OutputFile { get; private set; }
535
536 public string OutputType { get; private set; }
537
538 public CompressionLevel? DefaultCompressionLevel { get; private set; }
539
540 public string ContentsFile { get; private set; }
541
542 public string OutputsFile { get; private set; }
543
544 public string BuiltOutputsFile { get; private set; }
545
546 public List<string> Ices { get; } = new List<string>();
547
548 public List<string> SuppressIces { get; } = new List<string>();
549
550 public bool SuppressValidation { get; set; }
551
552 public bool ResetAcls { get; set; }
553
554 public CommandLine(IServiceProvider serviceProvider, IMessaging messaging)
555 {
556 this.ServiceProvider = serviceProvider;
557 this.Messaging = messaging;
558 }
559
560 private IServiceProvider ServiceProvider { get; }
561
562 private IMessaging Messaging { get; }
563
564 public bool TryParseArgument(string arg, ICommandLineParser parser)
565 {
566 if (parser.IsSwitch(arg))
567 {
568 var parameter = arg.Substring(1).ToLowerInvariant();
569 switch (parameter)
570 {
571 case "?":
572 case "h":
573 case "help":
574 this.ShowHelp = true;
575 return true;
576
577 case "arch":
578 case "platform":
579 {
580 var value = parser.GetNextArgumentOrError(arg);
581 if (Enum.TryParse(value, true, out Platform platform))
582 {
583 this.Platform = platform;
584 return true;
585 }
586 break;
587 }
588
589 case "bf":
590 case "bindfiles":
591 this.BindFiles = true;
592 return true;
593
594 case "bindpath":
595 {
596 var value = parser.GetNextArgumentOrError(arg);
597 if (value != null && this.TryParseBindPath(value, out var bindPath))
598 {
599 this.BindPaths.Add(bindPath);
600 return true;
601 }
602 return false;
603 }
604
605 case "cc":
606 this.CabCachePath = parser.GetNextArgumentOrError(arg);
607 return true;
608
609 case "culture":
610 parser.GetNextArgumentOrError(arg, this.Cultures);
611 return true;
612
613 case "contentsfile":
614 this.ContentsFile = parser.GetNextArgumentAsFilePathOrError(arg);
615 return true;
616
617 case "outputsfile":
618 this.OutputsFile = parser.GetNextArgumentAsFilePathOrError(arg);
619 return true;
620
621 case "builtoutputsfile":
622 this.BuiltOutputsFile = parser.GetNextArgumentAsFilePathOrError(arg);
623 return true;
624
625 case "d":
626 case "define":
627 parser.GetNextArgumentOrError(arg, this.Defines);
628 return true;
629
630 case "dcl":
631 case "defaultcompressionlevel":
632 {
633 var value = parser.GetNextArgumentOrError(arg);
634 if (Enum.TryParse(value, true, out CompressionLevel compressionLevel))
635 {
636 this.DefaultCompressionLevel = compressionLevel;
637 return true;
638 }
639 return false;
640 }
641
642 case "i":
643 case "includepath":
644 parser.GetNextArgumentOrError(arg, this.IncludeSearchPaths);
645 return true;
646
647 case "ice":
648 {
649 var value = parser.GetNextArgumentOrError(arg);
650 this.Ices.Add(value);
651 return true;
652 }
653
654 case "intermediatefolder":
655 this.IntermediateFolder = parser.GetNextArgumentAsDirectoryOrError(arg);
656 return true;
657
658 case "loc":
659 parser.GetNextArgumentAsFilePathOrError(arg, "localization files", this.LocalizationFilePaths);
660 return true;
661
662 case "lib":
663 parser.GetNextArgumentAsFilePathOrError(arg, "library files", this.LibraryFilePaths);
664 return true;
665
666 case "o":
667 case "out":
668 this.OutputFile = parser.GetNextArgumentAsFilePathOrError(arg);
669 return true;
670
671 case "outputtype":
672 this.OutputType = parser.GetNextArgumentOrError(arg);
673 return true;
674
675 case "pdb":
676 this.PdbFile = parser.GetNextArgumentAsFilePathOrError(arg);
677 return true;
678
679 case "pdbtype":
680 {
681 var value = parser.GetNextArgumentOrError(arg);
682 if (Enum.TryParse(value, true, out PdbType pdbType))
683 {
684 this.PdbType = pdbType;
685 return true;
686 }
687 return false;
688 }
689
690 case "sice":
691 {
692 var value = parser.GetNextArgumentOrError(arg);
693 this.SuppressIces.Add(value);
694 return true;
695 }
696
697 case "nologo":
698 this.ShowLogo = false;
699 return true;
700
701 case "v":
702 case "verbose":
703 this.Messaging.ShowVerboseMessages = true;
704 return true;
705
706 case "sval":
707 this.SuppressValidation = true;
708 return true;
709
710 case "resetacls":
711 this.ResetAcls = true;
712 return true;
713 }
714
715 if (parameter.StartsWith("sw"))
716 {
717 this.ParseSuppressWarning(parameter, "sw".Length, parser);
718 return true;
719 }
720 else if (parameter.StartsWith("suppresswarning"))
721 {
722 this.ParseSuppressWarning(parameter, "suppresswarning".Length, parser);
723 return true;
724 }
725 else if (parameter.StartsWith("wx"))
726 {
727 this.ParseWarningAsError(parameter, "wx".Length, parser);
728 return true;
729 }
730
731 return false;
732 }
733 else
734 {
735 parser.GetArgumentAsFilePathOrError(arg, "source code", this.SourceFilePaths);
736 return true;
737 }
738 }
739
740 public string CalculateIntermedateFolder()
741 {
742 return String.IsNullOrEmpty(this.IntermediateFolder) ? Path.GetTempPath() : this.IntermediateFolder;
743 }
744
745 public OutputType CalculateOutputType()
746 {
747 if (String.IsNullOrEmpty(this.OutputType))
748 {
749 this.OutputType = Path.GetExtension(this.OutputFile);
750 }
751
752 switch (this.OutputType?.ToLowerInvariant())
753 {
754 case "bundle":
755 case ".exe":
756 return Data.OutputType.Bundle;
757
758 case "library":
759 case ".wixlib":
760 return Data.OutputType.Library;
761
762 case "module":
763 case ".msm":
764 return Data.OutputType.Module;
765
766 case "patch":
767 case ".msp":
768 return Data.OutputType.Patch;
769
770 case ".pcp":
771 return Data.OutputType.PatchCreation;
772
773 case "product":
774 case "package":
775 case ".msi":
776 return Data.OutputType.Product;
777
778 case "transform":
779 case ".mst":
780 return Data.OutputType.Transform;
781
782 case "intermediatepostlink":
783 case ".wixipl":
784 return Data.OutputType.IntermediatePostLink;
785 }
786
787 return Data.OutputType.Unknown;
788 }
789
790 public IReadOnlyList<string> CalculateFilterCultures()
791 {
792 var result = new List<string>();
793
794 if (this.Cultures == null)
795 {
796 }
797 else if (this.Cultures.Count == 1 && this.Cultures[0].Equals("null", StringComparison.OrdinalIgnoreCase))
798 {
799 // When null is used treat it as if cultures wasn't specified. This is
800 // needed for batching in the MSBuild task since MSBuild doesn't support
801 // empty items.
802 }
803 else
804 {
805 foreach (var culture in this.Cultures)
806 {
807 // Neutral is different from null. For neutral we still want to do culture filtering.
808 // Set the culture to the empty string = identifier for the invariant culture.
809 var filter = (culture.Equals("neutral", StringComparison.OrdinalIgnoreCase)) ? String.Empty : culture;
810 result.Add(filter);
811 }
812 }
813
814 return result;
815 }
816
817 public IDictionary<string, string> GatherPreprocessorVariables()
818 {
819 var variables = new Dictionary<string, string>();
820
821 foreach (var pair in this.Defines)
822 {
823 var value = pair.Split(new[] { '=' }, 2);
824
825 if (variables.ContainsKey(value[0]))
826 {
827 this.Messaging.Write(ErrorMessages.DuplicateVariableDefinition(value[0], (1 == value.Length) ? String.Empty : value[1], variables[value[0]]));
828 continue;
829 }
830
831 variables.Add(value[0], (1 == value.Length) ? String.Empty : value[1]);
832 }
833
834 return variables;
835 }
836
837 public IEnumerable<SourceFile> GatherSourceFiles(string intermediateDirectory)
838 {
839 var files = new List<SourceFile>();
840
841 foreach (var item in this.SourceFilePaths)
842 {
843 var sourcePath = item;
844 var outputPath = Path.Combine(intermediateDirectory, Path.GetFileNameWithoutExtension(sourcePath) + ".wir");
845
846 files.Add(new SourceFile(sourcePath, outputPath));
847 }
848
849 return files;
850 }
851
852 private bool TryParseBindPath(string bindPath, out IBindPath bp)
853 {
854 var namedPath = bindPath.Split(BindPathSplit, 2);
855
856 bp = this.ServiceProvider.GetService<IBindPath>();
857
858 if (1 == namedPath.Length)
859 {
860 bp.Path = namedPath[0];
861 }
862 else
863 {
864 bp.Name = namedPath[0];
865 bp.Path = namedPath[1];
866 }
867
868 if (File.Exists(bp.Path))
869 {
870 this.Messaging.Write(ErrorMessages.ExpectedDirectoryGotFile("-bindpath", bp.Path));
871 return false;
872 }
873
874 return true;
875 }
876
877 private void ParseSuppressWarning(string parameter, int offset, ICommandLineParser parser)
878 {
879 var paramArg = parameter.Substring(offset);
880 if (paramArg.Length == 0)
881 {
882 this.Messaging.SuppressAllWarnings = true;
883 }
884 else if (Int32.TryParse(paramArg, out var suppressWarning) && suppressWarning > 0)
885 {
886 this.Messaging.SuppressWarningMessage(suppressWarning);
887 }
888 else
889 {
890 parser.ReportErrorArgument(parameter, ErrorMessages.IllegalSuppressWarningId(paramArg));
891 }
892 }
893
894 private void ParseWarningAsError(string parameter, int offset, ICommandLineParser parser)
895 {
896 var paramArg = parameter.Substring(offset);
897 if (paramArg.Length == 0)
898 {
899 this.Messaging.WarningsAsError = true;
900 }
901 else if (Int32.TryParse(paramArg, out var elevateWarning) && elevateWarning > 0)
902 {
903 this.Messaging.ElevateWarningMessage(elevateWarning);
904 }
905 else
906 {
907 parser.ReportErrorArgument(parameter, ErrorMessages.IllegalWarningIdAsError(paramArg));
908 }
909 }
910 }
911 }
912}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.CommandLine
4{
5 using System;
6 using System.Collections.Generic;
7 using WixToolset.Extensibility;
8 using WixToolset.Extensibility.Data;
9 using WixToolset.Extensibility.Services;
10
11 internal enum CommandTypes
12 {
13 Unknown,
14 Build,
15 Preprocess,
16 Compile,
17 Link,
18 Bind,
19 Decompile,
20 }
21
22 internal class CommandLine : ICommandLine
23 {
24 public CommandLine(IServiceProvider serviceProvider) => this.ServiceProvider = serviceProvider;
25
26 private IServiceProvider ServiceProvider { get; }
27
28 public ICommandLineCommand CreateCommand(string[] args)
29 {
30 var arguments = this.ServiceProvider.GetService<ICommandLineArguments>();
31 arguments.Populate(args);
32
33 this.LoadExtensions(arguments.Extensions);
34
35 return this.ParseStandardCommandLine(arguments);
36 }
37
38 public ICommandLineCommand CreateCommand(string commandLine)
39 {
40 var arguments = this.ServiceProvider.GetService<ICommandLineArguments>();
41 arguments.Populate(commandLine);
42
43 this.LoadExtensions(arguments.Extensions);
44
45 return this.ParseStandardCommandLine(arguments);
46 }
47
48 public ICommandLineCommand ParseStandardCommandLine(ICommandLineArguments arguments)
49 {
50 var context = this.ServiceProvider.GetService<ICommandLineContext>();
51 context.ExtensionManager = this.ServiceProvider.GetService<IExtensionManager>();
52 context.Arguments = arguments;
53
54 var command = this.Parse(context);
55
56 if (command.ShowLogo)
57 {
58 var branding = this.ServiceProvider.GetService<IWixBranding>();
59 Console.WriteLine(branding.ReplacePlaceholders("[AssemblyProduct] [AssemblyDescription] version [FileVersion]"));
60 Console.WriteLine(branding.ReplacePlaceholders("[AssemblyCopyright]"));
61 }
62
63 return command;
64 }
65
66 private void LoadExtensions(string[] extensions)
67 {
68 var extensionManager = this.ServiceProvider.GetService<IExtensionManager>();
69
70 foreach (var extension in extensions)
71 {
72 extensionManager.Load(extension);
73 }
74 }
75
76 private ICommandLineCommand Parse(ICommandLineContext context)
77 {
78 var branding = context.ServiceProvider.GetService<IWixBranding>();
79 var extensions = context.ExtensionManager.GetServices<IExtensionCommandLine>();
80
81 foreach (var extension in extensions)
82 {
83 extension.PreParse(context);
84 }
85
86 ICommandLineCommand command = null;
87 var parser = context.Arguments.Parse();
88
89 while (command?.StopParsing != true &&
90 String.IsNullOrEmpty(parser.ErrorArgument) &&
91 parser.TryGetNextSwitchOrArgument(out var arg))
92 {
93 if (String.IsNullOrWhiteSpace(arg)) // skip blank arguments.
94 {
95 continue;
96 }
97
98 // First argument must be the command or global switch (that creates a command).
99 if (command == null)
100 {
101 if (!this.TryParseCommand(arg, parser, extensions, out command))
102 {
103 parser.ReportErrorArgument(arg);
104 }
105 }
106 else if (parser.IsSwitch(arg))
107 {
108 if (!command.TryParseArgument(parser, arg) && !TryParseCommandLineArgumentWithExtension(arg, parser, extensions))
109 {
110 parser.ReportErrorArgument(arg);
111 }
112 }
113 else if (!TryParseCommandLineArgumentWithExtension(arg, parser, extensions) && !command.TryParseArgument(parser, arg))
114 {
115 parser.ReportErrorArgument(arg);
116 }
117 }
118
119 foreach (var extension in extensions)
120 {
121 extension.PostParse();
122 }
123
124 return command ?? new HelpCommand(extensions, branding);
125 }
126
127 private bool TryParseCommand(string arg, ICommandLineParser parser, IEnumerable<IExtensionCommandLine> extensions, out ICommandLineCommand command)
128 {
129 command = null;
130
131 if (parser.IsSwitch(arg))
132 {
133 var parameter = arg.Substring(1);
134 switch (parameter.ToLowerInvariant())
135 {
136 case "?":
137 case "h":
138 case "help":
139 case "-help":
140 var branding = this.ServiceProvider.GetService<IWixBranding>();
141 command = new HelpCommand(extensions, branding);
142 break;
143
144 case "version":
145 case "-version":
146 command = new VersionCommand();
147 break;
148 }
149 }
150 else
151 {
152 if (Enum.TryParse(arg, true, out CommandTypes commandType))
153 {
154 switch (commandType)
155 {
156 case CommandTypes.Build:
157 command = new BuildCommand(this.ServiceProvider);
158 break;
159
160 case CommandTypes.Compile:
161 command = new CompileCommand(this.ServiceProvider);
162 break;
163
164 case CommandTypes.Decompile:
165 command = new DecompileCommand(this.ServiceProvider);
166 break;
167 }
168 }
169 else
170 {
171 foreach (var extension in extensions)
172 {
173 if (extension.TryParseCommand(parser, arg, out command))
174 {
175 break;
176 }
177
178 command = null;
179 }
180 }
181 }
182
183 return command != null;
184 }
185
186 private static bool TryParseCommandLineArgumentWithExtension(string arg, ICommandLineParser parse, IEnumerable<IExtensionCommandLine> extensions)
187 {
188 foreach (var extension in extensions)
189 {
190 if (extension.TryParseArgument(parse, arg))
191 {
192 return true;
193 }
194 }
195
196 return false;
197 }
198 }
199}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.CommandLine
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Text;
9 using System.Text.RegularExpressions;
10 using WixToolset.Extensibility.Data;
11 using WixToolset.Extensibility.Services;
12
13 internal class CommandLineArguments : ICommandLineArguments
14 {
15 public CommandLineArguments(IServiceProvider serviceProvider)
16 {
17 this.Messaging = serviceProvider.GetService<IMessaging>();
18 }
19
20 public string[] OriginalArguments { get; set; }
21
22 public string[] Arguments { get; set; }
23
24 public string[] Extensions { get; set; }
25
26 public string ErrorArgument { get; set; }
27
28 private IMessaging Messaging { get; }
29
30 public void Populate(string commandLine)
31 {
32 var args = CommandLineArguments.ParseArgumentsToArray(commandLine);
33
34 this.Populate(args.ToArray());
35 }
36
37 public void Populate(string[] args)
38 {
39 this.FlattenArgumentsWithResponseFilesIntoOriginalArguments(args);
40
41 this.ProcessArgumentsAndParseExtensions(this.OriginalArguments);
42 }
43
44 public ICommandLineParser Parse() => new CommandLineParser(this.Messaging, this.Arguments, this.ErrorArgument);
45
46 private void FlattenArgumentsWithResponseFilesIntoOriginalArguments(string[] commandLineArguments)
47 {
48 var args = new List<string>();
49
50 foreach (var arg in commandLineArguments)
51 {
52 if (arg != null)
53 {
54 if ('@' == arg[0])
55 {
56 var responseFileArguments = CommandLineArguments.ParseResponseFile(arg.Substring(1));
57 args.AddRange(responseFileArguments);
58 }
59 else
60 {
61 args.Add(arg);
62 }
63 }
64 }
65
66 this.OriginalArguments = args.ToArray();
67 }
68
69 private void ProcessArgumentsAndParseExtensions(string[] args)
70 {
71 var arguments = new List<string>();
72 var extensions = new List<string>();
73
74 for (var i = 0; i < args.Length; ++i)
75 {
76 var arg = args[i];
77
78 if ("-ext" == arg || "/ext" == arg)
79 {
80 if (!CommandLineArguments.IsSwitchAt(args, ++i))
81 {
82 extensions.Add(args[i]);
83 }
84 else
85 {
86 this.ErrorArgument = arg;
87 break;
88 }
89 }
90 else
91 {
92 arguments.Add(arg);
93 }
94 }
95
96 this.Arguments = arguments.ToArray();
97 this.Extensions = extensions.ToArray();
98 }
99
100 private static List<string> ParseResponseFile(string responseFile)
101 {
102 string arguments;
103
104 using (var reader = new StreamReader(responseFile))
105 {
106 arguments = reader.ReadToEnd();
107 }
108
109 return CommandLineArguments.ParseArgumentsToArray(arguments);
110 }
111
112 private static List<string> ParseArgumentsToArray(string arguments)
113 {
114 // Scan and parse the arguments string, dividing up the arguments based on whitespace.
115 // Unescaped quotes cause whitespace to be ignored, while the quotes themselves are removed.
116 // Quotes may begin and end inside arguments; they don't necessarily just surround whole arguments.
117 // Escaped quotes and escaped backslashes also need to be unescaped by this process.
118
119 // Collects the final list of arguments to be returned.
120 var argsList = new List<string>();
121
122 // True if we are inside an unescaped quote, meaning whitespace should be ignored.
123 var insideQuote = false;
124
125 // Index of the start of the current argument substring; either the start of the argument
126 // or the start of a quoted or unquoted sequence within it.
127 var partStart = 0;
128
129 // The current argument string being built; when completed it will be added to the list.
130 var arg = new StringBuilder();
131
132 for (var i = 0; i <= arguments.Length; i++)
133 {
134 if (i == arguments.Length || (Char.IsWhiteSpace(arguments[i]) && !insideQuote))
135 {
136 // Reached a whitespace separator or the end of the string.
137
138 // Finish building the current argument.
139 arg.Append(arguments.Substring(partStart, i - partStart));
140
141 // Skip over the whitespace character.
142 partStart = i + 1;
143
144 // Add the argument to the list if it's not empty.
145 if (arg.Length > 0)
146 {
147 argsList.Add(CommandLineArguments.ExpandEnvironmentVariables(arg.ToString()));
148 arg.Length = 0;
149 }
150 }
151 else if (i > partStart && arguments[i - 1] == '\\')
152 {
153 // Check the character following an unprocessed backslash.
154 // Unescape quotes, and backslashes followed by a quote.
155 if (arguments[i] == '"' || (arguments[i] == '\\' && arguments.Length > i + 1 && arguments[i + 1] == '"'))
156 {
157 // Unescape the quote or backslash by skipping the preceeding backslash.
158 arg.Append(arguments.Substring(partStart, i - 1 - partStart));
159 arg.Append(arguments[i]);
160 partStart = i + 1;
161 }
162 }
163 else if (arguments[i] == '"')
164 {
165 // Add the quoted or unquoted section to the argument string.
166 arg.Append(arguments.Substring(partStart, i - partStart));
167
168 // And skip over the quote character.
169 partStart = i + 1;
170
171 insideQuote = !insideQuote;
172 }
173 }
174
175 return argsList;
176 }
177
178 private static string ExpandEnvironmentVariables(string arguments)
179 {
180 var id = Environment.GetEnvironmentVariables();
181
182 var regex = new Regex("(?<=\\%)(?:[\\w\\.]+)(?=\\%)");
183 var matches = regex.Matches(arguments);
184
185 var value = String.Empty;
186 for (var i = 0; i <= (matches.Count - 1); i++)
187 {
188 try
189 {
190 var key = matches[i].Value;
191 regex = new Regex(String.Concat("(?i)(?:\\%)(?:", key, ")(?:\\%)"));
192 value = id[key].ToString();
193 arguments = regex.Replace(arguments, value);
194 }
195 catch (NullReferenceException)
196 {
197 // Collapse unresolved environment variables.
198 arguments = regex.Replace(arguments, value);
199 }
200 }
201
202 return arguments;
203 }
204
205 private static bool IsSwitchAt(string[] args, int index) => args.Length > index && !String.IsNullOrEmpty(args[index]) && ('/' == args[index][0] || '-' == args[index][0]);
206 }
207}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.CommandLine
4{
5 using System;
6 using WixToolset.Extensibility.Data;
7 using WixToolset.Extensibility.Services;
8
9 internal class CommandLineContext : ICommandLineContext
10 {
11 public CommandLineContext(IServiceProvider serviceProvider)
12 {
13 this.ServiceProvider = serviceProvider;
14 }
15
16 public IServiceProvider ServiceProvider { get; }
17
18 public IExtensionManager ExtensionManager { get; set; }
19
20 public ICommandLineArguments Arguments { get; set; }
21 }
22}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.CommandLine
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using WixToolset.Data;
9 using WixToolset.Extensibility.Services;
10
11 internal class CommandLineParser : ICommandLineParser
12 {
13 private const string ExpectedArgument = "expected argument";
14
15 public string ErrorArgument { get; private set; }
16
17 private Queue<string> RemainingArguments { get; }
18
19 private IMessaging Messaging { get; }
20
21 public CommandLineParser(IMessaging messaging, string[] arguments, string errorArgument)
22 {
23 this.Messaging = messaging;
24 this.RemainingArguments = new Queue<string>(arguments);
25 this.ErrorArgument = errorArgument;
26 }
27
28 public bool IsSwitch(string arg)
29 {
30 return !String.IsNullOrEmpty(arg) && '-' == arg[0];
31 }
32
33 public string GetArgumentAsFilePathOrError(string argument, string fileType)
34 {
35 if (!File.Exists(argument))
36 {
37 this.Messaging.Write(ErrorMessages.FileNotFound(null, argument, fileType));
38 return null;
39 }
40
41 return argument;
42 }
43
44 public void GetArgumentAsFilePathOrError(string argument, string fileType, IList<string> paths)
45 {
46 foreach (var path in this.GetFiles(argument, fileType))
47 {
48 paths.Add(path);
49 }
50 }
51
52 public string GetNextArgumentOrError(string commandLineSwitch)
53 {
54 if (this.TryGetNextNonSwitchArgumentOrError(out var argument))
55 {
56 return argument;
57 }
58
59 this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch));
60 return null;
61 }
62
63 public bool GetNextArgumentOrError(string commandLineSwitch, IList<string> args)
64 {
65 if (this.TryGetNextNonSwitchArgumentOrError(out var arg))
66 {
67 args.Add(arg);
68 return true;
69 }
70
71 this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch));
72 return false;
73 }
74
75 public string GetNextArgumentAsDirectoryOrError(string commandLineSwitch)
76 {
77 if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && this.TryGetDirectory(commandLineSwitch, arg, out var directory))
78 {
79 return directory;
80 }
81
82 this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch));
83 return null;
84 }
85
86 public bool GetNextArgumentAsDirectoryOrError(string commandLineSwitch, IList<string> directories)
87 {
88 if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && this.TryGetDirectory(commandLineSwitch, arg, out var directory))
89 {
90 directories.Add(directory);
91 return true;
92 }
93
94 this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch));
95 return false;
96 }
97
98 public string GetNextArgumentAsFilePathOrError(string commandLineSwitch)
99 {
100 if (this.TryGetNextNonSwitchArgumentOrError(out var arg) && this.TryGetFile(commandLineSwitch, arg, out var path))
101 {
102 return path;
103 }
104
105 this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch));
106 return null;
107 }
108
109 public bool GetNextArgumentAsFilePathOrError(string commandLineSwitch, string fileType, IList<string> paths)
110 {
111 if (this.TryGetNextNonSwitchArgumentOrError(out var arg))
112 {
113 foreach (var path in this.GetFiles(arg, fileType))
114 {
115 paths.Add(path);
116 }
117
118 return true;
119 }
120
121 this.Messaging.Write(ErrorMessages.ExpectedArgument(commandLineSwitch));
122 return false;
123 }
124
125 public void ReportErrorArgument(string argument, Message message = null)
126 {
127 this.Messaging.Write(message ?? ErrorMessages.AdditionalArgumentUnexpected(argument));
128 this.ErrorArgument = argument;
129 }
130
131 public bool TryGetNextSwitchOrArgument(out string arg)
132 {
133 if (this.RemainingArguments.Count > 0)
134 {
135 arg = this.RemainingArguments.Dequeue();
136 return true;
137 }
138
139 arg = null;
140 return false;
141 }
142
143 private bool TryGetNextNonSwitchArgumentOrError(out string arg)
144 {
145 var result = this.TryGetNextSwitchOrArgument(out arg);
146
147 if (!result || this.IsSwitch(arg))
148 {
149 this.ErrorArgument = arg ?? CommandLineParser.ExpectedArgument;
150 return false;
151 }
152
153 return result;
154 }
155
156 private bool TryGetDirectory(string commandlineSwitch, string arg, out string directory)
157 {
158 directory = null;
159
160 if (File.Exists(arg))
161 {
162 this.Messaging.Write(ErrorMessages.ExpectedDirectoryGotFile(commandlineSwitch, arg));
163 return false;
164 }
165
166 directory = this.VerifyPath(arg);
167 return directory != null;
168 }
169
170 private bool TryGetFile(string commandlineSwitch, string arg, out string path)
171 {
172 path = null;
173
174 if (String.IsNullOrEmpty(arg) || '-' == arg[0])
175 {
176 this.Messaging.Write(ErrorMessages.FilePathRequired(commandlineSwitch));
177 }
178 else if (Directory.Exists(arg))
179 {
180 this.Messaging.Write(ErrorMessages.ExpectedFileGotDirectory(commandlineSwitch, arg));
181 }
182 else
183 {
184 path = this.VerifyPath(arg);
185 }
186
187 return path != null;
188 }
189
190 /// <summary>
191 /// Get a set of files that possibly have a search pattern in the path (such as '*').
192 /// </summary>
193 /// <param name="searchPath">Search path to find files in.</param>
194 /// <param name="fileType">Type of file; typically "Source".</param>
195 /// <returns>An array of files matching the search path.</returns>
196 /// <remarks>
197 /// This method is written in this verbose way because it needs to support ".." in the path.
198 /// It needs the directory path isolated from the file name in order to use Directory.GetFiles
199 /// or DirectoryInfo.GetFiles. The only way to get this directory path is manually since
200 /// Path.GetDirectoryName does not support ".." in the path.
201 /// </remarks>
202 private string[] GetFiles(string searchPath, string fileType)
203 {
204 if (null == searchPath)
205 {
206 throw new ArgumentNullException(nameof(searchPath));
207 }
208
209 // Convert alternate directory separators to the standard one.
210 var filePath = searchPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
211 var lastSeparator = filePath.LastIndexOf(Path.DirectorySeparatorChar);
212 var files = new string[0];
213
214 try
215 {
216 if (0 > lastSeparator)
217 {
218 files = Directory.GetFiles(".", filePath);
219 }
220 else // found directory separator
221 {
222 files = Directory.GetFiles(filePath.Substring(0, lastSeparator + 1), filePath.Substring(lastSeparator + 1));
223 }
224 }
225 catch (DirectoryNotFoundException)
226 {
227 // Don't let this function throw the DirectoryNotFoundException. This exception
228 // occurs for non-existant directories and invalid characters in the searchPattern.
229 }
230 catch (ArgumentException)
231 {
232 // Don't let this function throw the ArgumentException. This exception
233 // occurs in certain situations such as when passing a malformed UNC path.
234 }
235 catch (IOException)
236 {
237 }
238
239 if (0 == files.Length)
240 {
241 this.Messaging.Write(ErrorMessages.FileNotFound(null, searchPath, fileType));
242 }
243
244 return files;
245 }
246
247 private string VerifyPath(string path)
248 {
249 string fullPath;
250
251 if (0 <= path.IndexOf('\"'))
252 {
253 this.Messaging.Write(ErrorMessages.PathCannotContainQuote(path));
254 return null;
255 }
256
257 try
258 {
259 fullPath = Path.GetFullPath(path);
260 }
261 catch (Exception e)
262 {
263 this.Messaging.Write(ErrorMessages.InvalidCommandLineFileName(path, e.Message));
264 return null;
265 }
266
267 return fullPath;
268 }
269 }
270}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.CommandLine
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Threading;
8 using System.Threading.Tasks;
9 using WixToolset.Data;
10 using WixToolset.Extensibility;
11 using WixToolset.Extensibility.Data;
12 using WixToolset.Extensibility.Services;
13
14 internal class CompileCommand : ICommandLineCommand
15 {
16 public CompileCommand(IServiceProvider serviceProvider)
17 {
18 this.ServiceProvider = serviceProvider;
19 this.Messaging = serviceProvider.GetService<IMessaging>();
20 this.ExtensionManager = serviceProvider.GetService<IExtensionManager>();
21 }
22
23 public CompileCommand(IServiceProvider serviceProvider, IEnumerable<SourceFile> sources, IDictionary<string, string> preprocessorVariables, Platform platform)
24 {
25 this.ServiceProvider = serviceProvider;
26 this.Messaging = serviceProvider.GetService<IMessaging>();
27 this.ExtensionManager = serviceProvider.GetService<IExtensionManager>();
28 this.SourceFiles = sources;
29 this.PreprocessorVariables = preprocessorVariables;
30 this.Platform = platform;
31 }
32
33 private IServiceProvider ServiceProvider { get; }
34
35 public IMessaging Messaging { get; }
36
37 public IExtensionManager ExtensionManager { get; }
38
39 private IEnumerable<SourceFile> SourceFiles { get; }
40
41 private IDictionary<string, string> PreprocessorVariables { get; }
42
43 private Platform Platform { get; }
44
45 public IReadOnlyCollection<string> IncludeSearchPaths { get; }
46
47 public bool ShowLogo => throw new NotImplementedException();
48
49 public bool StopParsing => throw new NotImplementedException();
50
51 public bool TryParseArgument(ICommandLineParser parseHelper, string argument) => throw new NotImplementedException();
52
53 public Task<int> ExecuteAsync(CancellationToken _)
54 {
55 foreach (var sourceFile in this.SourceFiles)
56 {
57 var context = this.ServiceProvider.GetService<IPreprocessContext>();
58 context.Extensions = this.ExtensionManager.GetServices<IPreprocessorExtension>();
59 context.Platform = this.Platform;
60 context.IncludeSearchPaths = this.IncludeSearchPaths;
61 context.SourcePath = sourceFile.SourcePath;
62 context.Variables = this.PreprocessorVariables;
63
64 IPreprocessResult result = null;
65 try
66 {
67 var preprocessor = this.ServiceProvider.GetService<IPreprocessor>();
68 result = preprocessor.Preprocess(context);
69 }
70 catch (WixException e)
71 {
72 this.Messaging.Write(e.Error);
73 }
74
75 if (this.Messaging.EncounteredError)
76 {
77 continue;
78 }
79
80 var compileContext = this.ServiceProvider.GetService<ICompileContext>();
81 compileContext.Extensions = this.ExtensionManager.GetServices<ICompilerExtension>();
82 compileContext.Platform = this.Platform;
83 compileContext.Source = result?.Document;
84
85 var compiler = this.ServiceProvider.GetService<ICompiler>();
86 var intermediate = compiler.Compile(compileContext);
87
88 intermediate.Save(sourceFile.OutputPath);
89 }
90
91 return Task.FromResult(0);
92 }
93 }
94}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.CommandLine
4{
5 using System;
6 using System.IO;
7 using System.Threading;
8 using System.Threading.Tasks;
9 using System.Xml.Linq;
10 using WixToolset.Data;
11 using WixToolset.Extensibility;
12 using WixToolset.Extensibility.Data;
13 using WixToolset.Extensibility.Services;
14
15 internal class DecompileCommand : ICommandLineCommand
16 {
17 private readonly CommandLine commandLine;
18
19 public DecompileCommand(IServiceProvider serviceProvider)
20 {
21 this.ServiceProvider = serviceProvider;
22 this.Messaging = serviceProvider.GetService<IMessaging>();
23 this.commandLine = new CommandLine(this.Messaging);
24 }
25
26 public bool ShowLogo => this.commandLine.ShowLogo;
27
28 public bool StopParsing => this.commandLine.ShowHelp;
29
30 private IServiceProvider ServiceProvider { get; }
31
32 public IMessaging Messaging { get; }
33
34 public Task<int> ExecuteAsync(CancellationToken _)
35 {
36 if (this.commandLine.ShowHelp || String.IsNullOrEmpty(this.commandLine.DecompileFilePath))
37 {
38 Console.WriteLine("TODO: Show decompile command help");
39 return Task.FromResult(-1);
40 }
41
42 var context = this.ServiceProvider.GetService<IDecompileContext>();
43 context.Extensions = this.ServiceProvider.GetService<IExtensionManager>().GetServices<IDecompilerExtension>();
44 context.DecompilePath = this.commandLine.DecompileFilePath;
45 context.DecompileType = this.commandLine.CalculateDecompileType();
46 context.IntermediateFolder = this.commandLine.CalculateIntermedateFolder();
47 context.OutputPath = this.commandLine.CalculateOutputPath();
48
49 try
50 {
51 var decompiler = this.ServiceProvider.GetService<IDecompiler>();
52 var result = decompiler.Decompile(context);
53
54 if (!this.Messaging.EncounteredError)
55 {
56 Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(context.OutputPath)));
57 result.Document.Save(context.OutputPath, SaveOptions.OmitDuplicateNamespaces);
58 }
59 }
60 catch (WixException e)
61 {
62 this.Messaging.Write(e.Error);
63 }
64
65 if (this.Messaging.EncounteredError)
66 {
67 return Task.FromResult(1);
68 }
69
70 return Task.FromResult(0);
71 }
72
73 public bool TryParseArgument(ICommandLineParser parser, string argument)
74 {
75 return this.commandLine.TryParseArgument(argument, parser);
76 }
77
78 private class CommandLine
79 {
80 public CommandLine(IMessaging messaging)
81 {
82 this.Messaging = messaging;
83 }
84
85 private IMessaging Messaging { get; }
86
87 public string DecompileFilePath { get; private set; }
88
89 public string DecompileType { get; private set; }
90
91 public Platform Platform { get; private set; }
92
93 public bool ShowLogo { get; private set; }
94
95 public bool ShowHelp { get; private set; }
96
97 public string IntermediateFolder { get; private set; }
98
99 public string OutputFile { get; private set; }
100
101 public bool TryParseArgument(string arg, ICommandLineParser parser)
102 {
103 if (parser.IsSwitch(arg))
104 {
105 var parameter = arg.Substring(1);
106 switch (parameter.ToLowerInvariant())
107 {
108 case "?":
109 case "h":
110 case "help":
111 this.ShowHelp = true;
112 return true;
113
114 case "intermediatefolder":
115 this.IntermediateFolder = parser.GetNextArgumentAsDirectoryOrError(arg);
116 return true;
117
118 case "o":
119 case "out":
120 this.OutputFile = parser.GetNextArgumentAsFilePathOrError(arg);
121 return true;
122
123 case "nologo":
124 this.ShowLogo = false;
125 return true;
126
127 case "v":
128 case "verbose":
129 this.Messaging.ShowVerboseMessages = true;
130 return true;
131 }
132
133 if (parameter.StartsWith("sw"))
134 {
135 this.ParseSuppressWarning(parameter, "sw".Length, parser);
136 return true;
137 }
138 else if (parameter.StartsWith("suppresswarning"))
139 {
140 this.ParseSuppressWarning(parameter, "suppresswarning".Length, parser);
141 return true;
142 }
143 else if (parameter.StartsWith("wx"))
144 {
145 this.ParseWarningAsError(parameter, "wx".Length, parser);
146 return true;
147 }
148 }
149 else
150 {
151 if (String.IsNullOrEmpty(this.DecompileFilePath))
152 {
153 this.DecompileFilePath = parser.GetArgumentAsFilePathOrError(arg, "decompile file");
154 return true;
155 }
156 else if (String.IsNullOrEmpty(this.OutputFile))
157 {
158 this.OutputFile = parser.GetArgumentAsFilePathOrError(arg, "output file");
159 return true;
160 }
161 }
162
163 return false;
164 }
165
166 public OutputType CalculateDecompileType()
167 {
168 if (String.IsNullOrEmpty(this.DecompileType))
169 {
170 this.DecompileType = Path.GetExtension(this.DecompileFilePath);
171 }
172
173 switch (this.DecompileType.ToLowerInvariant())
174 {
175 case "bundle":
176 case ".exe":
177 return OutputType.Bundle;
178
179 case "library":
180 case ".wixlib":
181 return OutputType.Library;
182
183 case "module":
184 case ".msm":
185 return OutputType.Module;
186
187 case "patch":
188 case ".msp":
189 return OutputType.Patch;
190
191 case ".pcp":
192 return OutputType.PatchCreation;
193
194 case "product":
195 case "package":
196 case ".msi":
197 return OutputType.Product;
198
199 case "transform":
200 case ".mst":
201 return OutputType.Transform;
202
203 case "intermediatepostlink":
204 case ".wixipl":
205 return OutputType.IntermediatePostLink;
206 }
207
208 return OutputType.Unknown;
209 }
210
211 public string CalculateIntermedateFolder()
212 {
213 return String.IsNullOrEmpty(this.IntermediateFolder) ? Path.GetTempPath() : this.IntermediateFolder;
214 }
215
216 public string CalculateOutputPath()
217 {
218 return String.IsNullOrEmpty(this.OutputFile) ? Path.ChangeExtension(this.DecompileFilePath, ".wxs") : this.OutputFile;
219 }
220
221 private void ParseSuppressWarning(string parameter, int offset, ICommandLineParser parser)
222 {
223 var paramArg = parameter.Substring(offset);
224 if (paramArg.Length == 0)
225 {
226 this.Messaging.SuppressAllWarnings = true;
227 }
228 else if (Int32.TryParse(paramArg, out var suppressWarning) && suppressWarning > 0)
229 {
230 this.Messaging.SuppressWarningMessage(suppressWarning);
231 }
232 else
233 {
234 parser.ReportErrorArgument(parameter, ErrorMessages.IllegalSuppressWarningId(paramArg));
235 }
236 }
237
238 private void ParseWarningAsError(string parameter, int offset, ICommandLineParser parser)
239 {
240 var paramArg = parameter.Substring(offset);
241 if (paramArg.Length == 0)
242 {
243 this.Messaging.WarningsAsError = true;
244 }
245 else if (Int32.TryParse(paramArg, out var elevateWarning) && elevateWarning > 0)
246 {
247 this.Messaging.ElevateWarningMessage(elevateWarning);
248 }
249 else
250 {
251 parser.ReportErrorArgument(parameter, ErrorMessages.IllegalWarningIdAsError(paramArg));
252 }
253 }
254 }
255 }
256}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.CommandLine
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using System.Threading;
9 using System.Threading.Tasks;
10 using WixToolset.Extensibility;
11 using WixToolset.Extensibility.Data;
12 using WixToolset.Extensibility.Services;
13
14 internal class HelpCommand : ICommandLineCommand
15 {
16 private static readonly ExtensionCommandLineSwitch[] BuiltInSwitches = new ExtensionCommandLineSwitch[]
17 {
18 new ExtensionCommandLineSwitch { Switch = "build", Description = "Build a wixlib, package or bundle." },
19 new ExtensionCommandLineSwitch { Switch = "decompile", Description = "Decompile a package or bundle into source code." },
20 };
21
22 public HelpCommand(IEnumerable<IExtensionCommandLine> extensions, IWixBranding branding)
23 {
24 this.Extensions = extensions;
25 this.Branding = branding;
26 }
27
28 public bool ShowLogo => true;
29
30 public bool StopParsing => true;
31
32 private IEnumerable<IExtensionCommandLine> Extensions { get; }
33
34 private IWixBranding Branding { get; }
35
36 public Task<int> ExecuteAsync(CancellationToken _)
37 {
38 var commandLineSwitches = new List<ExtensionCommandLineSwitch>(BuiltInSwitches);
39 commandLineSwitches.AddRange(this.Extensions.SelectMany(e => e.CommandLineSwitches).OrderBy(s => s.Switch, StringComparer.Ordinal));
40
41 Console.WriteLine();
42 Console.WriteLine("Usage: wix [option]");
43 Console.WriteLine("Usage: wix [command]");
44 Console.WriteLine();
45 Console.WriteLine("Options:");
46 Console.WriteLine(" -h|--help Show command line help.");
47 Console.WriteLine(" --version Display WiX Toolset version in use.");
48 Console.WriteLine();
49
50 Console.WriteLine("Commands:");
51 foreach (var commandLineSwitch in commandLineSwitches)
52 {
53 Console.WriteLine(" {0,-17} {1}", commandLineSwitch.Switch, commandLineSwitch.Description);
54 }
55
56 Console.WriteLine();
57 Console.WriteLine("Run 'wix [command] --help' for more information on a command.");
58 Console.WriteLine();
59 Console.WriteLine(this.Branding.ReplacePlaceholders("For more information see: [SupportUrl]"));
60
61 return Task.FromResult(-1);
62 }
63
64 public bool TryParseArgument(ICommandLineParser parseHelper, string argument) => true; // eat any arguments
65 }
66}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.CommandLine
4{
5 using System;
6 using System.Threading;
7 using System.Threading.Tasks;
8 using WixToolset.Extensibility.Data;
9 using WixToolset.Extensibility.Services;
10
11 internal class VersionCommand : ICommandLineCommand
12 {
13 public bool ShowLogo => true;
14
15 public bool StopParsing => true;
16
17 public Task<int> ExecuteAsync(CancellationToken cancellationToken)
18 {
19 Console.WriteLine(ThisAssembly.AssemblyInformationalVersion);
20
21 return Task.FromResult(0);
22 }
23
24 public bool TryParseArgument(ICommandLineParser parseHelper, string argument) => true; // eat any arguments
25 }
26}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System;
6 using System.Diagnostics;
7 using System.Globalization;
8 using System.IO;
9 using System.Linq;
10 using System.Security.Cryptography;
11 using System.Text;
12 using System.Xml;
13 using System.Xml.Linq;
14 using WixToolset.Data;
15 using WixToolset.Extensibility;
16 using WixToolset.Extensibility.Services;
17
18 /// <summary>
19 /// Common Wix utility methods and types.
20 /// </summary>
21 internal static class Common
22 {
23 private static readonly char[] IllegalShortFilenameCharacters = new[] { '\\', '?', '|', '>', '<', ':', '/', '*', '\"', '+', ',', ';', '=', '[', ']', '.', ' ' };
24 private static readonly char[] IllegalWildcardShortFilenameCharacters = new[] { '\\', '|', '>', '<', ':', '/', '\"', '+', ',', ';', '=', '[', ']', '.', ' ' };
25
26 internal static readonly char[] IllegalLongFilenameCharacters = new[] { '\\', '/', '?', '*', '|', '>', '<', ':', '\"' }; // illegal: \ / ? | > < : / * "
27 internal static readonly char[] IllegalRelativeLongFilenameCharacters = new[] { '?', '*', '|', '>', '<', ':', '\"' }; // like illegal, but we allow '\' and '/'
28 internal static readonly char[] IllegalWildcardLongFilenameCharacters = new[] { '\\', '/', '|', '>', '<', ':', '\"' }; // like illegal: but we allow '*' and '?'
29
30 public static string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath, IMessaging messageHandler)
31 {
32 const string root = @"C:\";
33 if (!Path.IsPathRooted(relativePath))
34 {
35 var normalizedPath = Path.GetFullPath(root + relativePath);
36 if (normalizedPath.StartsWith(root))
37 {
38 var canonicalizedPath = normalizedPath.Substring(root.Length);
39 if (canonicalizedPath != relativePath)
40 {
41 messageHandler.Write(WarningMessages.PathCanonicalized(sourceLineNumbers, elementName, attributeName, relativePath, canonicalizedPath));
42 }
43 return canonicalizedPath;
44 }
45 }
46
47 messageHandler.Write(ErrorMessages.PayloadMustBeRelativeToCache(sourceLineNumbers, elementName, attributeName, relativePath));
48 return relativePath;
49 }
50
51 /// <summary>
52 /// Gets a valid code page from the given web name or integer value.
53 /// </summary>
54 /// <param name="value">A code page web name or integer value as a string.</param>
55 /// <param name="allowNoChange">Whether to allow -1 which does not change the database code pages. This may be the case with wxl files.</param>
56 /// <param name="onlyAnsi">Whether to allow Unicode (UCS) or UTF code pages.</param>
57 /// <param name="sourceLineNumbers">Source line information for the current authoring.</param>
58 /// <returns>A valid code page number.</returns>
59 /// <exception cref="ArgumentOutOfRangeException">The value is an integer less than 0 or greater than 65535.</exception>
60 /// <exception cref="ArgumentNullException"><paramref name="value"/> is null.</exception>
61 /// <exception cref="NotSupportedException">The value doesn't not represent a valid code page name or integer value.</exception>
62 /// <exception cref="WixException">The code page is invalid for summary information.</exception>
63 public static int GetValidCodePage(string value, bool allowNoChange = false, bool onlyAnsi = false, SourceLineNumber sourceLineNumbers = null)
64 {
65 Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
66
67 try
68 {
69 Encoding encoding;
70
71 // Check if a integer as a string was passed.
72 if (Int32.TryParse(value, out var codePage))
73 {
74 if (0 == codePage)
75 {
76 // 0 represents a neutral database
77 return 0;
78 }
79 else if (allowNoChange && -1 == codePage)
80 {
81 // -1 means no change to the database code page
82 return -1;
83 }
84
85 encoding = Encoding.GetEncoding(codePage);
86 }
87 else
88 {
89 encoding = Encoding.GetEncoding(value);
90 }
91
92 // Windows Installer parses some code page references
93 // as unsigned shorts which fail to open the database.
94 if (onlyAnsi)
95 {
96 codePage = encoding.CodePage;
97 if (0 > codePage || Int16.MaxValue < codePage)
98 {
99 throw new WixException(ErrorMessages.InvalidSummaryInfoCodePage(sourceLineNumbers, codePage));
100 }
101 }
102
103 if (encoding == null)
104 {
105 throw new WixException(ErrorMessages.IllegalCodepage(sourceLineNumbers, codePage));
106 }
107
108 return encoding.CodePage;
109 }
110 catch (ArgumentException ex)
111 {
112 // Rethrow as NotSupportedException since either can be thrown
113 // if the system does not support the specified code page.
114 throw new NotSupportedException(ex.Message, ex);
115 }
116 }
117
118 /// <summary>
119 /// Verifies if an identifier is a valid binder variable name.
120 /// </summary>
121 /// <param name="variable">Binder variable name to verify.</param>
122 /// <returns>True if the identifier is a valid binder variable name.</returns>
123 public static bool IsValidBinderVariable(string variable)
124 {
125 return TryParseWixVariable(variable, 0, out var parsed) && parsed.Index == 0 && parsed.Length == variable.Length && (parsed.Namespace == "bind" || parsed.Namespace == "wix");
126 }
127
128 /// <summary>
129 /// Verifies if a string contains a valid binder variable name.
130 /// </summary>
131 /// <param name="verify">String to verify.</param>
132 /// <returns>True if the string contains a valid binder variable name.</returns>
133 public static bool ContainsValidBinderVariable(string verify)
134 {
135 return TryParseWixVariable(verify, 0, out var parsed) && (parsed.Namespace == "bind" || parsed.Namespace == "wix");
136 }
137
138 /// <summary>
139 /// Verifies the given string is a valid 4-part version module or bundle version.
140 /// </summary>
141 /// <param name="version">The version to verify.</param>
142 /// <returns>True if version is a valid module or bundle version.</returns>
143 public static bool IsValidFourPartVersion(string version)
144 {
145 if (!Common.IsValidBinderVariable(version))
146 {
147 if (!Version.TryParse(version, out var ver) || 65535 < ver.Major || 65535 < ver.Minor || 65535 < ver.Build || 65535 < ver.Revision)
148 {
149 return false;
150 }
151 }
152
153 return true;
154 }
155
156 public static bool IsValidLongFilename(string filename, bool allowWildcards, bool allowRelative)
157 {
158 if (String.IsNullOrEmpty(filename))
159 {
160 return false;
161 }
162 else if (filename.Length > 259)
163 {
164 return false;
165 }
166
167 // Check for a non-period character (all periods is not legal)
168 var allPeriods = true;
169 foreach (var character in filename)
170 {
171 if ('.' != character)
172 {
173 allPeriods = false;
174 break;
175 }
176 }
177
178 if (allPeriods)
179 {
180 return false;
181 }
182
183 if (allowWildcards)
184 {
185 return filename.IndexOfAny(Common.IllegalWildcardLongFilenameCharacters) == -1;
186 }
187 else if (allowRelative)
188 {
189 return filename.IndexOfAny(Common.IllegalRelativeLongFilenameCharacters) == -1;
190 }
191 else
192 {
193 return filename.IndexOfAny(Common.IllegalLongFilenameCharacters) == -1;
194 }
195 }
196
197 public static bool IsValidShortFilename(string filename, bool allowWildcards)
198 {
199 if (String.IsNullOrEmpty(filename))
200 {
201 return false;
202 }
203
204 if (allowWildcards)
205 {
206 var expectedDot = filename.IndexOfAny(IllegalWildcardShortFilenameCharacters);
207 if (expectedDot == -1)
208 {
209 }
210 else if (filename[expectedDot] != '.')
211 {
212 return false;
213 }
214 else if (expectedDot < filename.Length)
215 {
216 var extensionInvalids = filename.IndexOfAny(IllegalWildcardShortFilenameCharacters, expectedDot + 1);
217 if (extensionInvalids != -1)
218 {
219 return false;
220 }
221 }
222
223 var foundPeriod = false;
224 var beforePeriod = 0;
225 var afterPeriod = 0;
226
227 // count the number of characters before and after the period
228 // '*' is not counted because it may represent zero characters
229 foreach (var character in filename)
230 {
231 if ('.' == character)
232 {
233 foundPeriod = true;
234 }
235 else if ('*' != character)
236 {
237 if (foundPeriod)
238 {
239 afterPeriod++;
240 }
241 else
242 {
243 beforePeriod++;
244 }
245 }
246 }
247
248 if (8 >= beforePeriod && 3 >= afterPeriod)
249 {
250 return true;
251 }
252
253 return false;
254 }
255 else
256 {
257 if (filename.Length > 12)
258 {
259 return false;
260 }
261
262 var expectedDot = filename.IndexOfAny(IllegalShortFilenameCharacters);
263 if (expectedDot == -1)
264 {
265 return filename.Length < 9;
266 }
267 else if (expectedDot > 8 || filename[expectedDot] != '.' || expectedDot + 4 < filename.Length)
268 {
269 return false;
270 }
271
272 var validExtension = filename.IndexOfAny(IllegalShortFilenameCharacters, expectedDot + 1);
273 return validExtension == -1;
274 }
275 }
276
277 /// <summary>
278 /// Generate a new Windows Installer-friendly guid.
279 /// </summary>
280 /// <returns>A new guid.</returns>
281 public static string GenerateGuid()
282 {
283 return Guid.NewGuid().ToString("B").ToUpperInvariant();
284 }
285
286 /// <summary>
287 /// Generate an identifier by hashing data from the row.
288 /// </summary>
289 /// <param name="prefix">Three letter or less prefix for generated row identifier.</param>
290 /// <param name="args">Information to hash.</param>
291 /// <returns>The generated identifier.</returns>
292 public static string GenerateIdentifier(string prefix, params string[] args)
293 {
294 string base64;
295
296 using (var sha1 = new SHA1CryptoServiceProvider())
297 {
298 var combined = String.Join("|", args);
299 var data = Encoding.UTF8.GetBytes(combined);
300 var hash = sha1.ComputeHash(data);
301 base64 = Convert.ToBase64String(hash);
302 }
303
304 var identifier = new StringBuilder(32);
305 identifier.Append(prefix);
306 identifier.Append(base64);
307 identifier.Length -= 1; // removes the trailing '=' from base64
308 identifier.Replace('+', '.');
309 identifier.Replace('/', '_');
310
311 return identifier.ToString();
312 }
313
314 /// <summary>
315 /// Return an identifier based on provided file or directory name
316 /// </summary>
317 /// <param name="name">File/directory name to generate identifer from</param>
318 /// <returns>A version of the name that is a legal identifier.</returns>
319 internal static string GetIdentifierFromName(string name)
320 {
321 StringBuilder sb = null;
322 var offset = 0;
323
324 // MSI identifiers must begin with an alphabetic character or an
325 // underscore. Prefix all other values with an underscore.
326 if (!ValidIdentifierChar(name[0], true))
327 {
328 sb = new StringBuilder("_" + name);
329 offset = 1;
330 }
331
332 for (var i = 0; i < name.Length; ++i)
333 {
334 if (!ValidIdentifierChar(name[i], false))
335 {
336 if (sb == null)
337 {
338 sb = new StringBuilder(name);
339 }
340
341 sb[i + offset] = '_';
342 }
343 }
344
345 return sb?.ToString() ?? name;
346 }
347
348 /// <summary>
349 /// Checks if the string contains a property (i.e. "foo[Property]bar")
350 /// </summary>
351 /// <param name="possibleProperty">String to evaluate for properties.</param>
352 /// <returns>True if a property is found in the string.</returns>
353 internal static bool ContainsProperty(string possibleProperty)
354 {
355 var start = possibleProperty.IndexOf('[');
356 if (start != -1 && start < possibleProperty.Length - 2)
357 {
358 var end = possibleProperty.IndexOf(']', start + 1);
359 if (end > start + 1)
360 {
361 // Skip supported property modifiers.
362 if (possibleProperty[start + 1] == '#' || possibleProperty[start + 1] == '$' || possibleProperty[start + 1] == '!')
363 {
364 ++start;
365 }
366
367 var id = possibleProperty.Substring(start + 1, end - 1);
368
369 if (Common.IsIdentifier(id))
370 {
371 return true;
372 }
373 }
374 }
375
376 return false;
377 }
378
379 /// <summary>
380 /// Recursively loops through a directory, changing an attribute on all of the underlying files.
381 /// An example is to add/remove the ReadOnly flag from each file.
382 /// </summary>
383 /// <param name="path">The directory path to start deleting from.</param>
384 /// <param name="fileAttribute">The FileAttribute to change on each file.</param>
385 /// <param name="messageHandler">The message handler.</param>
386 /// <param name="markAttribute">If true, add the attribute to each file. If false, remove it.</param>
387 private static void RecursiveFileAttributes(string path, FileAttributes fileAttribute, bool markAttribute, IMessaging messageHandler)
388 {
389 foreach (var subDirectory in Directory.GetDirectories(path))
390 {
391 RecursiveFileAttributes(subDirectory, fileAttribute, markAttribute, messageHandler);
392 }
393
394 foreach (var filePath in Directory.GetFiles(path))
395 {
396 var attributes = File.GetAttributes(filePath);
397 if (markAttribute)
398 {
399 attributes = attributes | fileAttribute; // add to list of attributes
400 }
401 else if (fileAttribute == (attributes & fileAttribute)) // if attribute set
402 {
403 attributes = attributes ^ fileAttribute; // remove from list of attributes
404 }
405
406 try
407 {
408 File.SetAttributes(filePath, attributes);
409 }
410 catch (UnauthorizedAccessException)
411 {
412 messageHandler.Write(WarningMessages.AccessDeniedForSettingAttributes(null, filePath));
413 }
414 }
415 }
416
417 /// <summary>
418 /// Takes an id, and demodularizes it (if possible).
419 /// </summary>
420 /// <remarks>
421 /// If the output type is a module, returns a demodularized version of an id. Otherwise, returns the id.
422 /// </remarks>
423 /// <param name="outputType">The type of the output to bind.</param>
424 /// <param name="modularizationGuid">The modularization GUID.</param>
425 /// <param name="id">The id to demodularize.</param>
426 /// <returns>The demodularized id.</returns>
427 public static string Demodularize(OutputType outputType, string modularizationGuid, string id)
428 {
429 if (OutputType.Module == outputType && id.EndsWith(String.Concat(".", modularizationGuid), StringComparison.Ordinal))
430 {
431 id = id.Substring(0, id.Length - 37);
432 }
433
434 return id;
435 }
436
437 /// <summary>
438 /// Get the source/target and short/long file names from an MSI Filename column.
439 /// </summary>
440 /// <param name="value">The Filename value.</param>
441 /// <returns>An array of strings of length 4. The contents are: short target, long target, short source, and long source.</returns>
442 /// <remarks>
443 /// If any particular file name part is not parsed, its set to null in the appropriate location of the returned array of strings.
444 /// Thus the returned array will always be of length 4.
445 /// </remarks>
446 public static string[] GetNames(string value)
447 {
448 var targetSeparator = value.IndexOf(':');
449
450 // split source and target
451 string sourceName = null;
452 var targetName = value;
453 if (0 <= targetSeparator)
454 {
455 sourceName = value.Substring(targetSeparator + 1);
456 targetName = value.Substring(0, targetSeparator);
457 }
458
459 // split the source short and long names
460 string sourceLongName = null;
461 if (null != sourceName)
462 {
463 var sourceLongNameSeparator = sourceName.IndexOf('|');
464 if (0 <= sourceLongNameSeparator)
465 {
466 sourceLongName = sourceName.Substring(sourceLongNameSeparator + 1);
467 sourceName = sourceName.Substring(0, sourceLongNameSeparator);
468 }
469 }
470
471 // split the target short and long names
472 var targetLongNameSeparator = targetName.IndexOf('|');
473 string targetLongName = null;
474 if (0 <= targetLongNameSeparator)
475 {
476 targetLongName = targetName.Substring(targetLongNameSeparator + 1);
477 targetName = targetName.Substring(0, targetLongNameSeparator);
478 }
479
480 // Remove the long source name when its identical to the short source name.
481 if (null != sourceName && sourceName == sourceLongName)
482 {
483 sourceLongName = null;
484 }
485
486 // Remove the long target name when its identical to the long target name.
487 if (null != targetName && targetName == targetLongName)
488 {
489 targetLongName = null;
490 }
491
492 // Remove the source names when they are identical to the target names.
493 if (sourceName == targetName && sourceLongName == targetLongName)
494 {
495 sourceName = null;
496 sourceLongName = null;
497 }
498
499 // target name(s)
500 if ("." == targetName)
501 {
502 targetName = null;
503 }
504
505 if ("." == targetLongName)
506 {
507 targetLongName = null;
508 }
509
510 // source name(s)
511 if ("." == sourceName)
512 {
513 sourceName = null;
514 }
515
516 if ("." == sourceLongName)
517 {
518 sourceLongName = null;
519 }
520
521 return new[] { targetName, targetLongName, sourceName, sourceLongName };
522 }
523
524 /// <summary>
525 /// Get a source/target and short/long file name from an MSI Filename column.
526 /// </summary>
527 /// <param name="value">The Filename value.</param>
528 /// <param name="source">true to get a source name; false to get a target name</param>
529 /// <param name="longName">true to get a long name; false to get a short name</param>
530 /// <returns>The name.</returns>
531 public static string GetName(string value, bool source, bool longName)
532 {
533 var names = GetNames(value);
534
535 if (source)
536 {
537 if (longName && null != names[3])
538 {
539 return names[3];
540 }
541 else if (null != names[2])
542 {
543 return names[2];
544 }
545 }
546
547 if (longName && null != names[1])
548 {
549 return names[1];
550 }
551 else
552 {
553 return names[0];
554 }
555 }
556
557 /// <summary>
558 /// Get an attribute value.
559 /// </summary>
560 /// <param name="messaging"></param>
561 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
562 /// <param name="attribute">The attribute containing the value to get.</param>
563 /// <param name="emptyRule">A rule for the contents of the value. If the contents do not follow the rule, an error is thrown.</param>
564 /// <returns>The attribute's value.</returns>
565 internal static string GetAttributeValue(IMessaging messaging, SourceLineNumber sourceLineNumbers, XAttribute attribute, EmptyRule emptyRule)
566 {
567 var value = attribute.Value;
568
569 if ((emptyRule == EmptyRule.MustHaveNonWhitespaceCharacters && String.IsNullOrEmpty(value.Trim())) ||
570 (emptyRule == EmptyRule.CanBeWhitespaceOnly && String.IsNullOrEmpty(value)))
571 {
572 messaging.Write(ErrorMessages.IllegalEmptyAttributeValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName));
573 return String.Empty;
574 }
575
576 return value;
577 }
578
579 /// <summary>
580 /// Verifies that a value is a legal identifier.
581 /// </summary>
582 /// <param name="value">The value to verify.</param>
583 /// <returns>true if the value is an identifier; false otherwise.</returns>
584 public static bool IsIdentifier(string value)
585 {
586 if (String.IsNullOrEmpty(value))
587 {
588 return false;
589 }
590
591 for (var i = 0; i < value.Length; ++i)
592 {
593 if (!ValidIdentifierChar(value[i], i == 0))
594 {
595 return false;
596 }
597 }
598
599 return true;
600 }
601
602 /// <summary>
603 /// Get an identifier attribute value and displays an error for an illegal identifier value.
604 /// </summary>
605 /// <param name="messaging"></param>
606 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
607 /// <param name="attribute">The attribute containing the value to get.</param>
608 /// <returns>The attribute's identifier value or a special value if an error occurred.</returns>
609 internal static string GetAttributeIdentifierValue(IMessaging messaging, SourceLineNumber sourceLineNumbers, XAttribute attribute)
610 {
611 var value = Common.GetAttributeValue(messaging, sourceLineNumbers, attribute, EmptyRule.CanBeWhitespaceOnly);
612
613 if (Common.IsIdentifier(value))
614 {
615 if (72 < value.Length)
616 {
617 messaging.Write(WarningMessages.IdentifierTooLong(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
618 }
619
620 return value;
621 }
622 else
623 {
624 if (value.StartsWith("[", StringComparison.Ordinal) && value.EndsWith("]", StringComparison.Ordinal))
625 {
626 messaging.Write(ErrorMessages.IllegalIdentifierLooksLikeFormatted(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
627 }
628 else
629 {
630 messaging.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
631 }
632
633 return String.Empty;
634 }
635 }
636
637 /// <summary>
638 /// Get an integer attribute value and displays an error for an illegal integer value.
639 /// </summary>
640 /// <param name="messaging"></param>
641 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
642 /// <param name="attribute">The attribute containing the value to get.</param>
643 /// <param name="minimum">The minimum legal value.</param>
644 /// <param name="maximum">The maximum legal value.</param>
645 /// <returns>The attribute's integer value or a special value if an error occurred during conversion.</returns>
646 public static int GetAttributeIntegerValue(IMessaging messaging, SourceLineNumber sourceLineNumbers, XAttribute attribute, int minimum, int maximum)
647 {
648 Debug.Assert(minimum > CompilerConstants.IntegerNotSet && minimum > CompilerConstants.IllegalInteger, "The legal values for this attribute collide with at least one sentinel used during parsing.");
649
650 var value = Common.GetAttributeValue(messaging, sourceLineNumbers, attribute, EmptyRule.CanBeWhitespaceOnly);
651 var integer = CompilerConstants.IllegalInteger;
652
653 if (0 < value.Length)
654 {
655 if (Int32.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture.NumberFormat, out integer))
656 {
657 if (CompilerConstants.IntegerNotSet == integer || CompilerConstants.IllegalInteger == integer)
658 {
659 messaging.Write(ErrorMessages.IntegralValueSentinelCollision(sourceLineNumbers, integer));
660 }
661 else if (minimum > integer || maximum < integer)
662 {
663 messaging.Write(ErrorMessages.IntegralValueOutOfRange(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, integer, minimum, maximum));
664 integer = CompilerConstants.IllegalInteger;
665 }
666 }
667 else
668 {
669 messaging.Write(ErrorMessages.IllegalIntegerValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
670 }
671 }
672
673 return integer;
674 }
675
676 /// <summary>
677 /// Gets a yes/no value and displays an error for an illegal yes/no value.
678 /// </summary>
679 /// <param name="messaging"></param>
680 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
681 /// <param name="attribute">The attribute containing the value to get.</param>
682 /// <returns>The attribute's YesNoType value.</returns>
683 internal static YesNoType GetAttributeYesNoValue(IMessaging messaging, SourceLineNumber sourceLineNumbers, XAttribute attribute)
684 {
685 var value = Common.GetAttributeValue(messaging, sourceLineNumbers, attribute, EmptyRule.CanBeWhitespaceOnly);
686 var yesNo = YesNoType.IllegalValue;
687
688 if ("yes".Equals(value) || "true".Equals(value))
689 {
690 yesNo = YesNoType.Yes;
691 }
692 else if ("no".Equals(value) || "false".Equals(value))
693 {
694 yesNo = YesNoType.No;
695 }
696 else
697 {
698 messaging.Write(ErrorMessages.IllegalYesNoValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
699 }
700
701 return yesNo;
702 }
703
704 /// <summary>
705 /// Gets the text of an XElement.
706 /// </summary>
707 /// <param name="node">Element to get text.</param>
708 /// <returns>The element's text.</returns>
709 internal static string GetInnerText(XElement node)
710 {
711 var text = node.Nodes().Where(n => XmlNodeType.Text == n.NodeType || XmlNodeType.CDATA == n.NodeType).Cast<XText>().FirstOrDefault();
712 return text?.Value;
713 }
714
715 internal static bool TryParseWixVariable(string value, int start, out ParsedWixVariable parsedVariable)
716 {
717 parsedVariable = null;
718
719 if (String.IsNullOrEmpty(value) || start >= value.Length)
720 {
721 return false;
722 }
723
724 var startWixVariable = value.IndexOf("!(", start, StringComparison.Ordinal);
725 if (startWixVariable == -1)
726 {
727 return false;
728 }
729
730 var firstDot = value.IndexOf('.', startWixVariable + 1);
731 if (firstDot == -1)
732 {
733 return false;
734 }
735
736 var ns = value.Substring(startWixVariable + 2, firstDot - startWixVariable - 2);
737 if (ns != "loc" && ns != "bind" && ns != "wix")
738 {
739 return false;
740 }
741
742 var closeParen = value.IndexOf(')', firstDot);
743 if (closeParen == -1)
744 {
745 return false;
746 }
747
748 string name;
749 string scope = null;
750 string defaultValue = null;
751
752 var equalsDefaultValue = value.IndexOf('=', firstDot + 1, closeParen - firstDot);
753 var end = equalsDefaultValue == -1 ? closeParen : equalsDefaultValue;
754 var secondDot = value.IndexOf('.', firstDot + 1, end - firstDot);
755
756 if (secondDot == -1)
757 {
758 name = value.Substring(firstDot + 1, end - firstDot - 1);
759 }
760 else
761 {
762 name = value.Substring(firstDot + 1, secondDot - firstDot - 1);
763 scope = value.Substring(secondDot + 1, end - secondDot - 1);
764
765 if (!Common.IsIdentifier(scope))
766 {
767 return false;
768 }
769 }
770
771 if (!Common.IsIdentifier(name))
772 {
773 return false;
774 }
775
776 if (equalsDefaultValue != -1 && equalsDefaultValue < closeParen)
777 {
778 defaultValue = value.Substring(equalsDefaultValue + 1, closeParen - equalsDefaultValue - 1);
779 }
780
781 parsedVariable = new ParsedWixVariable
782 {
783 Index = startWixVariable,
784 Length = closeParen - startWixVariable + 1,
785 Namespace = ns,
786 Name = name,
787 Scope = scope,
788 DefaultValue = defaultValue
789 };
790
791 return true;
792 }
793
794 /// <summary>
795 /// Display an unexpected attribute error.
796 /// </summary>
797 /// <param name="messaging"></param>
798 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
799 /// <param name="attribute">The attribute.</param>
800 public static void UnexpectedAttribute(IMessaging messaging, SourceLineNumber sourceLineNumbers, XAttribute attribute)
801 {
802 // Ignore elements defined by the W3C because we'll assume they are always right.
803 if (!((String.IsNullOrEmpty(attribute.Name.NamespaceName) && attribute.Name.LocalName.Equals("xmlns", StringComparison.Ordinal)) ||
804 attribute.Name.NamespaceName.StartsWith(CompilerCore.W3SchemaPrefix.NamespaceName, StringComparison.Ordinal)))
805 {
806 messaging.Write(ErrorMessages.UnexpectedAttribute(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName));
807 }
808 }
809
810 /// <summary>
811 /// Display an unsupported extension attribute error.
812 /// </summary>
813 /// <param name="messaging"></param>
814 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
815 /// <param name="extensionAttribute">The extension attribute.</param>
816 internal static void UnsupportedExtensionAttribute(IMessaging messaging, SourceLineNumber sourceLineNumbers, XAttribute extensionAttribute)
817 {
818 // Ignore elements defined by the W3C because we'll assume they are always right.
819 if (!((String.IsNullOrEmpty(extensionAttribute.Name.NamespaceName) && extensionAttribute.Name.LocalName.Equals("xmlns", StringComparison.Ordinal)) ||
820 extensionAttribute.Name.NamespaceName.StartsWith(CompilerCore.W3SchemaPrefix.NamespaceName, StringComparison.Ordinal)))
821 {
822 messaging.Write(ErrorMessages.UnsupportedExtensionAttribute(sourceLineNumbers, extensionAttribute.Parent.Name.LocalName, extensionAttribute.Name.LocalName));
823 }
824 }
825
826 private static bool ValidIdentifierChar(char c, bool firstChar)
827 {
828 return ('A' <= c && 'Z' >= c) || ('a' <= c && 'z' >= c) || '_' == c ||
829 (!firstChar && (Char.IsDigit(c) || '.' == c));
830 }
831 }
832}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System;
6 using System.IO;
7 using System.Xml.Linq;
8 using WixToolset.Data;
9 using WixToolset.Data.Burn;
10 using WixToolset.Data.Symbols;
11
12 internal class CompilerPayload
13 {
14 public YesNoDefaultType Compressed { get; set; } = YesNoDefaultType.Default;
15
16 public string Description { get; set; }
17
18 public string DownloadUrl { get; set; }
19
20 public string Hash { get; set; }
21
22 public Identifier Id { get; set; }
23
24 public bool IsRemoteAllowed { get; set; }
25
26 public bool IsRequired { get; set; } = true;
27
28 public string Name { get; set; }
29
30 public string ProductName { get; set; }
31
32 public long? Size { get; set; }
33
34 public string SourceFile { get; set; }
35
36 public string Version { get; set; }
37
38 public CompilerPayload(CompilerCore core, SourceLineNumber sourceLineNumbers, XElement element)
39 {
40 this.Core = core;
41 this.Element = element;
42 this.SourceLineNumbers = sourceLineNumbers;
43 }
44
45 private CompilerCore Core { get; }
46
47 private XElement Element { get; }
48
49 private SourceLineNumber SourceLineNumbers { get; }
50
51 private void CalculateAndVerifyFields()
52 {
53 var isRemote = this.IsRemoteAllowed && !String.IsNullOrEmpty(this.Hash);
54
55 if (String.IsNullOrEmpty(this.SourceFile))
56 {
57 if (!String.IsNullOrEmpty(this.Name) && !isRemote)
58 {
59 this.SourceFile = Path.Combine("SourceDir", this.Name);
60 }
61 }
62 else if (this.SourceFile.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal))
63 {
64 if (String.IsNullOrEmpty(this.Name))
65 {
66 this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Name", "SourceFile", this.SourceFile));
67 }
68 else
69 {
70 this.SourceFile = Path.Combine(this.SourceFile, Path.GetFileName(this.Name));
71 }
72 }
73
74 if (String.IsNullOrEmpty(this.SourceFile) && !isRemote)
75 {
76 if (this.IsRequired)
77 {
78 if (!this.IsRemoteAllowed)
79 {
80 this.Core.Write(ErrorMessages.ExpectedAttributes(this.SourceLineNumbers, this.Element.Name.LocalName, "Name", "SourceFile"));
81 }
82 else
83 {
84 this.Core.Write(ErrorMessages.ExpectedAttributes(this.SourceLineNumbers, this.Element.Name.LocalName, "SourceFile", "Hash"));
85 }
86 }
87 }
88 else if (this.IsRemoteAllowed)
89 {
90 var isLocal = !String.IsNullOrEmpty(this.SourceFile);
91
92 if (isLocal)
93 {
94 if (isRemote)
95 {
96 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Hash", "SourceFile"));
97 }
98
99 if (!String.IsNullOrEmpty(this.Description))
100 {
101 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Description", "SourceFile"));
102 }
103
104 if (!String.IsNullOrEmpty(this.ProductName))
105 {
106 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "ProductName", "SourceFile"));
107 }
108
109 if (this.Size.HasValue)
110 {
111 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Size", "SourceFile"));
112 }
113
114 if (!String.IsNullOrEmpty(this.Version))
115 {
116 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Version", "SourceFile"));
117 }
118 }
119 else
120 {
121 if (String.IsNullOrEmpty(this.DownloadUrl))
122 {
123 this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "DownloadUrl", "Hash"));
124 }
125
126 if (String.IsNullOrEmpty(this.Name))
127 {
128 this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Name", "Hash"));
129 }
130
131 if (!this.Size.HasValue)
132 {
133 this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Size", "Hash"));
134 }
135
136 if (YesNoDefaultType.Yes == this.Compressed)
137 {
138 this.Core.Write(WarningMessages.RemotePayloadsMustNotAlsoBeCompressed(this.SourceLineNumbers, this.Element.Name.LocalName));
139 }
140
141 this.Compressed = YesNoDefaultType.No;
142 }
143 }
144 }
145
146 public WixBundlePayloadSymbol CreatePayloadSymbol(ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType = ComplexReferenceChildType.Unknown, string previousId = null)
147 {
148 WixBundlePayloadSymbol symbol = null;
149
150 if (parentType == ComplexReferenceParentType.Container && parentId == BurnConstants.BurnUXContainerName)
151 {
152 if (this.Compressed == YesNoDefaultType.No)
153 {
154 this.Core.Write(WarningMessages.UxPayloadsOnlySupportEmbedding(this.SourceLineNumbers, this.SourceFile));
155 }
156
157 if (!String.IsNullOrEmpty(this.DownloadUrl))
158 {
159 this.Core.Write(WarningMessages.DownloadUrlNotSupportedForBAPayloads(this.SourceLineNumbers, this.Id.Id));
160 }
161
162 this.Compressed = YesNoDefaultType.Yes;
163 this.DownloadUrl = null;
164 }
165
166 if (!this.Core.EncounteredError)
167 {
168 symbol = this.Core.AddSymbol(new WixBundlePayloadSymbol(this.SourceLineNumbers, this.Id)
169 {
170 Name = String.IsNullOrEmpty(this.Name) ? Path.GetFileName(this.SourceFile) : this.Name,
171 SourceFile = new IntermediateFieldPathValue { Path = this.SourceFile },
172 DownloadUrl = this.DownloadUrl,
173 Compressed = (this.Compressed == YesNoDefaultType.Yes) ? true : (this.Compressed == YesNoDefaultType.No) ? (bool?)false : null,
174 UnresolvedSourceFile = this.SourceFile, // duplicate of sourceFile but in a string column so it won't get resolved to a full path during binding.
175 DisplayName = this.ProductName,
176 Description = this.Description,
177 Hash = this.Hash,
178 FileSize = this.Size,
179 Version = this.Version,
180 });
181
182 this.Core.CreateGroupAndOrderingRows(this.SourceLineNumbers, parentType, parentId, ComplexReferenceChildType.Payload, symbol.Id.Id, previousType, previousId);
183 }
184
185 return symbol;
186 }
187
188 public void FinishCompilingPackage()
189 {
190 this.CalculateAndVerifyFields();
191 this.GenerateIdFromFilename();
192
193 if (this.Id == null)
194 {
195 this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Id"));
196 this.Id = Identifier.Invalid;
197 }
198 }
199
200 public void FinishCompilingPackagePayload()
201 {
202 this.CalculateAndVerifyFields();
203 this.GenerateIdFromFilename();
204 this.GenerateIdFromPrefix("ppy");
205 }
206
207 public void FinishCompilingPayload()
208 {
209 this.CalculateAndVerifyFields();
210 this.GenerateIdFromPrefix("pay");
211 }
212
213 private void GenerateIdFromFilename()
214 {
215 if (this.Id == null)
216 {
217 if (!String.IsNullOrEmpty(this.Name))
218 {
219 this.Id = this.Core.CreateIdentifierFromFilename(Path.GetFileName(this.Name));
220 }
221 else if (!String.IsNullOrEmpty(this.SourceFile))
222 {
223 this.Id = this.Core.CreateIdentifierFromFilename(Path.GetFileName(this.SourceFile));
224 }
225 }
226 }
227
228 private void GenerateIdFromPrefix(string prefix)
229 {
230 if (this.Id == null)
231 {
232 this.Id = this.Core.CreateIdentifier(prefix, this.SourceFile?.ToUpperInvariant() ?? String.Empty);
233 }
234 }
235
236 public void ParseCompressed(XAttribute attrib)
237 {
238 this.Compressed = this.Core.GetAttributeYesNoDefaultValue(this.SourceLineNumbers, attrib);
239 }
240
241 public void ParseDescription(XAttribute attrib)
242 {
243 this.Description = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib);
244 }
245
246 public void ParseDownloadUrl(XAttribute attrib)
247 {
248 this.DownloadUrl = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib);
249 }
250
251 public void ParseHash(XAttribute attrib)
252 {
253 this.Hash = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib);
254 }
255
256 public void ParseId(XAttribute attrib)
257 {
258 this.Id = this.Core.GetAttributeIdentifier(this.SourceLineNumbers, attrib);
259 }
260
261 public void ParseName(XAttribute attrib)
262 {
263 this.Name = this.Core.GetAttributeLongFilename(this.SourceLineNumbers, attrib, false, true);
264 if (!this.Core.IsValidLongFilename(this.Name, false, true))
265 {
266 this.Core.Write(ErrorMessages.IllegalLongFilename(this.SourceLineNumbers, this.Element.Name.LocalName, "Name", this.Name));
267 }
268 }
269
270 public void ParseProductName(XAttribute attrib)
271 {
272 this.ProductName = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib);
273 }
274
275 public void ParseSize(XAttribute attrib)
276 {
277 this.Size = this.Core.GetAttributeLongValue(this.SourceLineNumbers, attrib, 1, Int64.MaxValue);
278 }
279
280 public void ParseSourceFile(XAttribute attrib)
281 {
282 this.SourceFile = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib);
283 }
284
285 public void ParseVersion(XAttribute attrib)
286 {
287 this.Version = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib);
288 }
289
290 }
291}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Threading;
8 using System.Xml.Linq;
9 using WixToolset.Data;
10 using WixToolset.Extensibility;
11 using WixToolset.Extensibility.Data;
12
13 internal class CompileContext : ICompileContext
14 {
15 internal CompileContext(IServiceProvider serviceProvider)
16 {
17 this.ServiceProvider = serviceProvider;
18 }
19
20 public IServiceProvider ServiceProvider { get; }
21
22 public string CompilationId { get; set; }
23
24 public IReadOnlyCollection<ICompilerExtension> Extensions { get; set; }
25
26 public Platform Platform { get; set; }
27
28 public bool IsCurrentPlatform64Bit => this.Platform == Platform.ARM64 || this.Platform == Platform.X64;
29
30 public XDocument Source { get; set; }
31
32 public CancellationToken CancellationToken { get; set; }
33 }
34}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.Globalization;
9 using System.IO;
10 using System.Linq;
11 using System.Text;
12 using System.Xml.Linq;
13 using WixToolset.Data;
14 using WixToolset.Data.Symbols;
15 using WixToolset.Data.WindowsInstaller;
16 using WixToolset.Extensibility;
17 using WixToolset.Extensibility.Data;
18 using WixToolset.Extensibility.Services;
19
20 /// <summary>
21 /// Compiler of the WiX toolset.
22 /// </summary>
23 internal partial class Compiler : ICompiler
24 {
25 private const int MinValueOfMaxCabSizeForLargeFileSplitting = 20; // 20 MB
26 private const int MaxValueOfMaxCabSizeForLargeFileSplitting = 2 * 1024; // 2048 MB (i.e. 2 GB)
27
28 private const char ComponentIdPlaceholderStart = (char)167;
29 private const char ComponentIdPlaceholderEnd = (char)167;
30 private Dictionary<string, string> componentIdPlaceholders;
31
32 // If these are true you know you are building a module or product
33 // but if they are false you cannot not be sure they will not end
34 // up a product or module. Use these flags carefully.
35 private bool compilingModule;
36 private bool compilingProduct;
37
38 private string activeName;
39 private string activeLanguage;
40
41 /// <summary>
42 /// Type of RadioButton element in a group.
43 /// </summary>
44 private enum RadioButtonType
45 {
46 /// <summary>Not set, yet.</summary>
47 NotSet,
48
49 /// <summary>Text</summary>
50 Text,
51
52 /// <summary>Bitmap</summary>
53 Bitmap,
54
55 /// <summary>Icon</summary>
56 Icon,
57 }
58
59 internal Compiler(IServiceProvider serviceProvider)
60 {
61 this.Messaging = serviceProvider.GetService<IMessaging>();
62 }
63
64 public IMessaging Messaging { get; }
65
66 private ICompileContext Context { get; set; }
67
68 private CompilerCore Core { get; set; }
69
70 /// <summary>
71 /// Gets or sets the platform which the compiler will use when defaulting 64-bit attributes and elements.
72 /// </summary>
73 /// <value>The platform which the compiler will use when defaulting 64-bit attributes and elements.</value>
74 public Platform CurrentPlatform => this.Context.Platform;
75
76 /// <summary>
77 /// Gets or sets the option to show pedantic messages.
78 /// </summary>
79 /// <value>The option to show pedantic messages.</value>
80 public bool ShowPedanticMessages { get; set; }
81
82 /// <summary>
83 /// Compiles the provided Xml document into an intermediate object
84 /// </summary>
85 /// <returns>Intermediate object representing compiled source document.</returns>
86 /// <remarks>This method is not thread-safe.</remarks>
87 public Intermediate Compile(ICompileContext context)
88 {
89 var target = new Intermediate();
90
91 if (String.IsNullOrEmpty(context.CompilationId))
92 {
93 context.CompilationId = target.Id;
94 }
95
96 this.Context = context;
97
98 var extensionsByNamespace = new Dictionary<XNamespace, ICompilerExtension>();
99
100 foreach (var extension in this.Context.Extensions)
101 {
102 if (!extensionsByNamespace.TryGetValue(extension.Namespace, out var collidingExtension))
103 {
104 extensionsByNamespace.Add(extension.Namespace, extension);
105 }
106 else
107 {
108 this.Messaging.Write(ErrorMessages.DuplicateExtensionXmlSchemaNamespace(extension.GetType().ToString(), extension.Namespace.NamespaceName, collidingExtension.GetType().ToString()));
109 }
110
111 extension.PreCompile(this.Context);
112 }
113
114 // Try to compile it.
115 try
116 {
117 var parseHelper = this.Context.ServiceProvider.GetService<IParseHelper>();
118
119 this.Core = new CompilerCore(target, this.Messaging, parseHelper, extensionsByNamespace);
120 this.Core.ShowPedanticMessages = this.ShowPedanticMessages;
121 this.componentIdPlaceholders = new Dictionary<string, string>();
122
123 // parse the document
124 var source = this.Context.Source;
125 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(source.Root);
126 if ("Wix" == source.Root.Name.LocalName)
127 {
128 if (CompilerCore.WixNamespace == source.Root.Name.Namespace)
129 {
130 this.ParseWixElement(source.Root);
131 }
132 else // invalid or missing namespace
133 {
134 if (String.IsNullOrEmpty(source.Root.Name.NamespaceName))
135 {
136 this.Core.Write(ErrorMessages.InvalidWixXmlNamespace(sourceLineNumbers, "Wix", CompilerCore.WixNamespace.ToString()));
137 }
138 else
139 {
140 this.Core.Write(ErrorMessages.InvalidWixXmlNamespace(sourceLineNumbers, "Wix", source.Root.Name.NamespaceName, CompilerCore.WixNamespace.ToString()));
141 }
142 }
143 }
144 else
145 {
146 this.Core.Write(ErrorMessages.InvalidDocumentElement(sourceLineNumbers, source.Root.Name.LocalName, "source", "Wix"));
147 }
148
149 // Resolve any Component Id placeholders compiled into the intermediate.
150 this.ResolveComponentIdPlaceholders(target);
151 }
152 finally
153 {
154 foreach (var extension in this.Context.Extensions)
155 {
156 extension.PostCompile(target);
157 }
158
159 this.Core = null;
160 }
161
162 target.UpdateLevel(Data.IntermediateLevels.Compiled);
163
164 return this.Messaging.EncounteredError ? null : target;
165 }
166
167 /// <summary>
168 /// Parses a Wix element.
169 /// </summary>
170 /// <param name="node">Element to parse.</param>
171 private void ParseWixElement(XElement node)
172 {
173 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
174 string requiredVersion = null;
175
176 foreach (var attrib in node.Attributes())
177 {
178 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
179 {
180 switch (attrib.Name.LocalName)
181 {
182 case "RequiredVersion":
183 requiredVersion = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib);
184 break;
185 default:
186 this.Core.UnexpectedAttribute(node, attrib);
187 break;
188 }
189 }
190 else
191 {
192 this.Core.ParseExtensionAttribute(node, attrib);
193 }
194 }
195
196 if (null != requiredVersion)
197 {
198 this.Core.VerifyRequiredVersion(sourceLineNumbers, requiredVersion);
199 }
200
201 foreach (var child in node.Elements())
202 {
203 if (CompilerCore.WixNamespace == child.Name.Namespace)
204 {
205 switch (child.Name.LocalName)
206 {
207 case "Bundle":
208 this.ParseBundleElement(child);
209 break;
210 case "Fragment":
211 this.ParseFragmentElement(child);
212 break;
213 case "Module":
214 this.ParseModuleElement(child);
215 break;
216 case "PatchCreation":
217 this.ParsePatchCreationElement(child);
218 break;
219 case "Package":
220 this.ParsePackageElement(child);
221 break;
222 case "Patch":
223 this.ParsePatchElement(child);
224 break;
225 default:
226 this.Core.UnexpectedElement(node, child);
227 break;
228 }
229 }
230 else
231 {
232 this.Core.ParseExtensionElement(node, child);
233 }
234 }
235 }
236
237 private void ResolveComponentIdPlaceholders(Intermediate target)
238 {
239 if (0 < this.componentIdPlaceholders.Count)
240 {
241 foreach (var section in target.Sections)
242 {
243 foreach (var symbol in section.Symbols)
244 {
245 foreach (var field in symbol.Fields)
246 {
247 if (field != null && field.Type == IntermediateFieldType.String)
248 {
249 var data = field.AsString();
250 if (!String.IsNullOrEmpty(data))
251 {
252 var changed = false;
253 var start = data.IndexOf(ComponentIdPlaceholderStart);
254 while (start != -1)
255 {
256 var end = data.IndexOf(ComponentIdPlaceholderEnd, start + 1);
257 if (end == -1)
258 {
259 break;
260 }
261
262 var placeholderId = data.Substring(start, end - start + 1);
263 if (this.componentIdPlaceholders.TryGetValue(placeholderId, out var value))
264 {
265 var sb = new StringBuilder(data);
266 sb.Remove(start, end - start + 1);
267 sb.Insert(start, value);
268
269 data = sb.ToString();
270 changed = true;
271
272 end = start + value.Length;
273 }
274
275 start = data.IndexOf(ComponentIdPlaceholderStart, end);
276 }
277
278 if (changed)
279 {
280 field.Overwrite(data);
281 }
282 }
283 }
284 }
285 }
286 }
287 }
288 }
289
290 /// <summary>
291 /// Uppercases the first character of a string.
292 /// </summary>
293 /// <param name="s">String to uppercase first character of.</param>
294 /// <returns>String with first character uppercased.</returns>
295 private static string UppercaseFirstChar(string s)
296 {
297 if (0 == s.Length)
298 {
299 return s;
300 }
301
302 return String.Concat(s.Substring(0, 1).ToUpperInvariant(), s.Substring(1));
303 }
304
305 /// <summary>
306 /// Lowercases the string if present.
307 /// </summary>
308 /// <param name="s">String to lowercase.</param>
309 /// <returns>Null if the string is null, otherwise returns the lowercase.</returns>
310 private static string LowercaseOrNull(string s)
311 {
312 return s?.ToLowerInvariant();
313 }
314
315 /// <summary>
316 /// Adds a search property to the active section.
317 /// </summary>
318 /// <param name="sourceLineNumbers">Current source/line number of processing.</param>
319 /// <param name="propertyId">Property to add to search.</param>
320 /// <param name="signature">Signature for search.</param>
321 private void AddAppSearch(SourceLineNumber sourceLineNumbers, Identifier propertyId, string signature)
322 {
323 if (!this.Core.EncounteredError)
324 {
325 if (propertyId.Id != propertyId.Id.ToUpperInvariant())
326 {
327 this.Core.Write(ErrorMessages.SearchPropertyNotUppercase(sourceLineNumbers, "Property", "Id", propertyId.Id));
328 }
329
330 this.Core.AddSymbol(new AppSearchSymbol(sourceLineNumbers, new Identifier(propertyId.Access, propertyId.Id, signature))
331 {
332 PropertyRef = propertyId.Id,
333 SignatureRef = signature
334 });
335 }
336 }
337
338 /// <summary>
339 /// Adds a property to the active section.
340 /// </summary>
341 /// <param name="sourceLineNumbers">Current source/line number of processing.</param>
342 /// <param name="propertyId">Identifier of property to add.</param>
343 /// <param name="value">Value of property.</param>
344 /// <param name="admin">Flag if property is an admin property.</param>
345 /// <param name="secure">Flag if property is a secure property.</param>
346 /// <param name="hidden">Flag if property is to be hidden.</param>
347 /// <param name="fragment">Adds the property to a new section.</param>
348 private void AddProperty(SourceLineNumber sourceLineNumbers, Identifier propertyId, string value, bool admin, bool secure, bool hidden, bool fragment)
349 {
350 // properties without a valid identifier should not be processed any further
351 if (null == propertyId || String.IsNullOrEmpty(propertyId.Id))
352 {
353 return;
354 }
355
356 if (!String.IsNullOrEmpty(value))
357 {
358 var start = value.IndexOf('[');
359 while (start != -1 && start < value.Length)
360 {
361 var end = value.IndexOf(']', start + 1);
362 if (end == -1)
363 {
364 break;
365 }
366
367 var id = value.Substring(start + 1, end - 1);
368 if (Common.IsIdentifier(id))
369 {
370 this.Core.Write(WarningMessages.PropertyValueContainsPropertyReference(sourceLineNumbers, propertyId.Id, id));
371 }
372
373 start = (end < value.Length) ? value.IndexOf('[', end + 1) : -1;
374 }
375 }
376
377 if (!this.Core.EncounteredError)
378 {
379 var section = this.Core.ActiveSection;
380
381 // Add the symbol to a separate section if requested.
382 if (fragment)
383 {
384 var id = String.Concat(this.Core.ActiveSection.Id, ".", propertyId.Id);
385
386 section = this.Core.CreateSection(id, SectionType.Fragment, this.Context.CompilationId);
387
388 // Reference the property in the active section.
389 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, propertyId.Id);
390 }
391
392 // Allow symbol to exist with no value so that PropertyRefs can be made for *Search elements
393 // the linker will remove these symbols before the final output is created.
394 section.AddSymbol(new PropertySymbol(sourceLineNumbers, propertyId)
395 {
396 Value = value,
397 });
398
399 if (admin || hidden || secure)
400 {
401 this.AddWixPropertySymbol(sourceLineNumbers, propertyId, admin, secure, hidden, section);
402 }
403 }
404 }
405
406 private void AddWixPropertySymbol(SourceLineNumber sourceLineNumbers, Identifier property, bool admin, bool secure, bool hidden, IntermediateSection section = null)
407 {
408 if (secure && property.Id != property.Id.ToUpperInvariant())
409 {
410 this.Core.Write(ErrorMessages.SecurePropertyNotUppercase(sourceLineNumbers, "Property", "Id", property.Id));
411 }
412
413 if (null == section)
414 {
415 section = this.Core.ActiveSection;
416
417 this.Core.EnsureTable(sourceLineNumbers, WindowsInstallerTableDefinitions.Property); // Property table is always required when using WixProperty table.
418 }
419
420 section.AddSymbol(new WixPropertySymbol(sourceLineNumbers)
421 {
422 PropertyRef = property.Id,
423 Admin = admin,
424 Hidden = hidden,
425 Secure = secure
426 });
427 }
428
429 /// <summary>
430 /// Adds a "implemented category" registry key to active section.
431 /// </summary>
432 /// <param name="sourceLineNumbers">Current source/line number of processing.</param>
433 /// <param name="categoryId">GUID for category.</param>
434 /// <param name="classId">ClassId for to mark "implemented".</param>
435 /// <param name="componentId">Identifier of parent component.</param>
436 private void RegisterImplementedCategories(SourceLineNumber sourceLineNumbers, string categoryId, string classId, string componentId)
437 {
438 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\Implemented Categories\\", categoryId), "*", null, componentId);
439 }
440
441 /// <summary>
442 /// Parses an application identifer element.
443 /// </summary>
444 /// <param name="node">Element to parse.</param>
445 /// <param name="componentId">Identifier of parent component.</param>
446 /// <param name="advertise">The required advertise state (set depending upon the parent).</param>
447 /// <param name="fileServer">Optional file identifier for CLSID when not advertised.</param>
448 /// <param name="typeLibId">Optional TypeLib GUID for CLSID.</param>
449 /// <param name="typeLibVersion">Optional TypeLib Version for CLSID Interfaces (if any).</param>
450 private void ParseAppIdElement(XElement node, string componentId, YesNoType advertise, string fileServer, string typeLibId, string typeLibVersion)
451 {
452 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
453 string appId = null;
454 string remoteServerName = null;
455 string localService = null;
456 string serviceParameters = null;
457 string dllSurrogate = null;
458 bool? activateAtStorage = null;
459 var appIdAdvertise = YesNoType.NotSet;
460 bool? runAsInteractiveUser = null;
461 string description = null;
462
463 foreach (var attrib in node.Attributes())
464 {
465 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
466 {
467 switch (attrib.Name.LocalName)
468 {
469 case "Id":
470 appId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
471 break;
472 case "ActivateAtStorage":
473 activateAtStorage = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
474 break;
475 case "Advertise":
476 appIdAdvertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
477 break;
478 case "Description":
479 description = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
480 break;
481 case "DllSurrogate":
482 dllSurrogate = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty);
483 break;
484 case "LocalService":
485 localService = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
486 break;
487 case "RemoteServerName":
488 remoteServerName = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
489 break;
490 case "RunAsInteractiveUser":
491 runAsInteractiveUser = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
492 break;
493 case "ServiceParameters":
494 serviceParameters = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
495 break;
496 default:
497 this.Core.UnexpectedAttribute(node, attrib);
498 break;
499 }
500 }
501 else
502 {
503 this.Core.ParseExtensionAttribute(node, attrib);
504 }
505 }
506
507 if (null == appId)
508 {
509 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
510 }
511
512 if ((YesNoType.No == advertise && YesNoType.Yes == appIdAdvertise) || (YesNoType.Yes == advertise && YesNoType.No == appIdAdvertise))
513 {
514 this.Core.Write(ErrorMessages.AppIdIncompatibleAdvertiseState(sourceLineNumbers, node.Name.LocalName, "Advertise", appIdAdvertise.ToString(), advertise.ToString()));
515 }
516 else
517 {
518 advertise = appIdAdvertise;
519 }
520
521 // if the advertise state has not been set, default to non-advertised
522 if (YesNoType.NotSet == advertise)
523 {
524 advertise = YesNoType.No;
525 }
526
527 foreach (var child in node.Elements())
528 {
529 if (CompilerCore.WixNamespace == child.Name.Namespace)
530 {
531 switch (child.Name.LocalName)
532 {
533 case "Class":
534 this.ParseClassElement(child, componentId, advertise, fileServer, typeLibId, typeLibVersion, appId);
535 break;
536 default:
537 this.Core.UnexpectedElement(node, child);
538 break;
539 }
540 }
541 else
542 {
543 this.Core.ParseExtensionElement(node, child);
544 }
545 }
546
547 if (YesNoType.Yes == advertise)
548 {
549 if (null != description)
550 {
551 this.Core.Write(ErrorMessages.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "Description"));
552 }
553
554 if (!this.Core.EncounteredError)
555 {
556 this.Core.AddSymbol(new AppIdSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, appId))
557 {
558 AppId = appId,
559 RemoteServerName = remoteServerName,
560 LocalService = localService,
561 ServiceParameters = serviceParameters,
562 DllSurrogate = dllSurrogate,
563 ActivateAtStorage = activateAtStorage,
564 RunAsInteractiveUser = runAsInteractiveUser,
565 });
566 }
567 }
568 else if (YesNoType.No == advertise)
569 {
570 if (null != description)
571 {
572 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), null, description, componentId);
573 }
574 else
575 {
576 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "+", null, componentId);
577 }
578
579 if (null != remoteServerName)
580 {
581 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "RemoteServerName", remoteServerName, componentId);
582 }
583
584 if (null != localService)
585 {
586 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "LocalService", localService, componentId);
587 }
588
589 if (null != serviceParameters)
590 {
591 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "ServiceParameters", serviceParameters, componentId);
592 }
593
594 if (null != dllSurrogate)
595 {
596 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "DllSurrogate", dllSurrogate, componentId);
597 }
598
599 if (true == activateAtStorage)
600 {
601 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "ActivateAtStorage", "Y", componentId);
602 }
603
604 if (true == runAsInteractiveUser)
605 {
606 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("AppID\\", appId), "RunAs", "Interactive User", componentId);
607 }
608 }
609 }
610
611 /// <summary>
612 /// Parses an AssemblyName element.
613 /// </summary>
614 /// <param name="node">File element to parse.</param>
615 /// <param name="componentId">Parent's component id.</param>
616 private void ParseAssemblyName(XElement node, string componentId)
617 {
618 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
619 string id = null;
620 string value = null;
621
622 foreach (var attrib in node.Attributes())
623 {
624 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
625 {
626 switch (attrib.Name.LocalName)
627 {
628 case "Id":
629 id = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
630 break;
631 case "Value":
632 value = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
633 break;
634 default:
635 this.Core.UnexpectedAttribute(node, attrib);
636 break;
637 }
638 }
639 else
640 {
641 this.Core.ParseExtensionAttribute(node, attrib);
642 }
643 }
644
645 if (null == id)
646 {
647 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
648 }
649
650 this.Core.ParseForExtensionElements(node);
651
652 if (!this.Core.EncounteredError)
653 {
654 this.Core.AddSymbol(new MsiAssemblyNameSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, componentId, id))
655 {
656 ComponentRef = componentId,
657 Name = id,
658 Value = value,
659 });
660 }
661 }
662
663 /// <summary>
664 /// Parses a binary element.
665 /// </summary>
666 /// <param name="node">Element to parse.</param>
667 /// <returns>Identifier for the new row.</returns>
668 private Identifier ParseBinaryElement(XElement node)
669 {
670 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
671 Identifier id = null;
672 string sourceFile = null;
673 var suppressModularization = YesNoType.NotSet;
674
675 foreach (var attrib in node.Attributes())
676 {
677 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
678 {
679 switch (attrib.Name.LocalName)
680 {
681 case "Id":
682 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
683 break;
684 case "SourceFile":
685 sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
686 break;
687 case "SuppressModularization":
688 suppressModularization = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
689 break;
690 default:
691 this.Core.UnexpectedAttribute(node, attrib);
692 break;
693 }
694 }
695 else
696 {
697 this.Core.ParseExtensionAttribute(node, attrib);
698 }
699 }
700
701 if (null == id)
702 {
703 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
704 id = Identifier.Invalid;
705 }
706 else if (!String.IsNullOrEmpty(id.Id)) // only check legal values
707 {
708 if (55 < id.Id.Length)
709 {
710 this.Core.Write(ErrorMessages.StreamNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, id.Id.Length, 55));
711 }
712 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
713 {
714 if (18 < id.Id.Length)
715 {
716 this.Core.Write(WarningMessages.IdentifierCannotBeModularized(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, id.Id.Length, 18));
717 }
718 }
719 }
720
721 if (null == sourceFile)
722 {
723 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile"));
724 }
725
726 this.Core.ParseForExtensionElements(node);
727
728 if (!this.Core.EncounteredError)
729 {
730 this.Core.AddSymbol(new BinarySymbol(sourceLineNumbers, id)
731 {
732 Data = new IntermediateFieldPathValue { Path = sourceFile }
733 });
734
735 if (YesNoType.Yes == suppressModularization)
736 {
737 this.Core.AddSymbol(new WixSuppressModularizationSymbol(sourceLineNumbers)
738 {
739 SuppressIdentifier = id.Id
740 });
741 }
742 }
743
744 return id;
745 }
746
747 /// <summary>
748 /// Parses an icon element.
749 /// </summary>
750 /// <param name="node">Element to parse.</param>
751 /// <returns>Identifier for the new row.</returns>
752 private string ParseIconElement(XElement node)
753 {
754 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
755 Identifier id = null;
756 string sourceFile = null;
757
758 foreach (var attrib in node.Attributes())
759 {
760 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
761 {
762 switch (attrib.Name.LocalName)
763 {
764 case "Id":
765 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
766 break;
767 case "SourceFile":
768 sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
769 break;
770 default:
771 this.Core.UnexpectedAttribute(node, attrib);
772 break;
773 }
774 }
775 else
776 {
777 this.Core.ParseExtensionAttribute(node, attrib);
778 }
779 }
780
781 if (null == id)
782 {
783 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
784 id = Identifier.Invalid;
785 }
786 else if (!String.IsNullOrEmpty(id.Id)) // only check legal values
787 {
788 if (57 < id.Id.Length)
789 {
790 this.Core.Write(ErrorMessages.StreamNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, id.Id.Length, 57));
791 }
792 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
793 {
794 if (20 < id.Id.Length)
795 {
796 this.Core.Write(WarningMessages.IdentifierCannotBeModularized(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, id.Id.Length, 20));
797 }
798 }
799 }
800
801 if (null == sourceFile)
802 {
803 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile"));
804 }
805
806 this.Core.ParseForExtensionElements(node);
807
808 if (!this.Core.EncounteredError)
809 {
810 this.Core.AddSymbol(new IconSymbol(sourceLineNumbers, id)
811 {
812 Data = new IntermediateFieldPathValue { Path = sourceFile },
813 });
814 }
815
816 return id.Id;
817 }
818
819 /// <summary>
820 /// Parses an InstanceTransforms element.
821 /// </summary>
822 /// <param name="node">Element to parse.</param>
823 private void ParseInstanceTransformsElement(XElement node)
824 {
825 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
826 string property = null;
827
828 foreach (var attrib in node.Attributes())
829 {
830 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
831 {
832 switch (attrib.Name.LocalName)
833 {
834 case "Property":
835 property = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
836 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, property);
837 break;
838 default:
839 this.Core.UnexpectedAttribute(node, attrib);
840 break;
841 }
842 }
843 else
844 {
845 this.Core.ParseExtensionAttribute(node, attrib);
846 }
847 }
848
849 if (null == property)
850 {
851 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property"));
852 }
853
854 // find unexpected child elements
855 foreach (var child in node.Elements())
856 {
857 if (CompilerCore.WixNamespace == child.Name.Namespace)
858 {
859 switch (child.Name.LocalName)
860 {
861 case "Instance":
862 this.ParseInstanceElement(child, property);
863 break;
864 default:
865 this.Core.UnexpectedElement(node, child);
866 break;
867 }
868 }
869 else
870 {
871 this.Core.ParseExtensionElement(node, child);
872 }
873 }
874 }
875
876 /// <summary>
877 /// Parses an instance element.
878 /// </summary>
879 /// <param name="node">Element to parse.</param>
880 /// <param name="propertyId">Identifier of instance property.</param>
881 private void ParseInstanceElement(XElement node, string propertyId)
882 {
883 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
884 Identifier id = null;
885 string productCode = null;
886 string productName = null;
887 string upgradeCode = null;
888
889 foreach (var attrib in node.Attributes())
890 {
891 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
892 {
893 switch (attrib.Name.LocalName)
894 {
895 case "Id":
896 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
897 break;
898 case "ProductCode":
899 productCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, true);
900 break;
901 case "ProductName":
902 productName = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
903 break;
904 case "UpgradeCode":
905 upgradeCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
906 break;
907 default:
908 this.Core.UnexpectedAttribute(node, attrib);
909 break;
910 }
911 }
912 else
913 {
914 this.Core.ParseExtensionAttribute(node, attrib);
915 }
916 }
917
918 if (null == id)
919 {
920 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
921 }
922
923 if (null == productCode)
924 {
925 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ProductCode"));
926 }
927
928 this.Core.ParseForExtensionElements(node);
929
930 if (!this.Core.EncounteredError)
931 {
932 this.Core.AddSymbol(new WixInstanceTransformsSymbol(sourceLineNumbers, id)
933 {
934 PropertyId = propertyId,
935 ProductCode = productCode,
936 ProductName = productName,
937 UpgradeCode = upgradeCode
938 });
939 }
940 }
941
942 /// <summary>
943 /// Parses a category element.
944 /// </summary>
945 /// <param name="node">Element to parse.</param>
946 /// <param name="componentId">Identifier of parent component.</param>
947 private void ParseCategoryElement(XElement node, string componentId)
948 {
949 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
950 string id = null;
951 string appData = null;
952 string feature = null;
953 string qualifier = null;
954
955 foreach (var attrib in node.Attributes())
956 {
957 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
958 {
959 switch (attrib.Name.LocalName)
960 {
961 case "Id":
962 id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
963 break;
964 case "AppData":
965 appData = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
966 break;
967 case "Feature":
968 feature = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
969 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Feature, feature);
970 break;
971 case "Qualifier":
972 qualifier = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
973 break;
974 default:
975 this.Core.UnexpectedAttribute(node, attrib);
976 break;
977 }
978 }
979 else
980 {
981 this.Core.ParseExtensionAttribute(node, attrib);
982 }
983 }
984
985 if (null == id)
986 {
987 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
988 }
989
990 if (null == qualifier)
991 {
992 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Qualifier"));
993 }
994
995 this.Core.ParseForExtensionElements(node);
996
997 if (!this.Core.EncounteredError)
998 {
999 this.Core.AddSymbol(new PublishComponentSymbol(sourceLineNumbers)
1000 {
1001 ComponentId = id,
1002 Qualifier = qualifier,
1003 ComponentRef = componentId,
1004 AppData = appData,
1005 FeatureRef = feature ?? Guid.Empty.ToString("B"),
1006 });
1007 }
1008 }
1009
1010 /// <summary>
1011 /// Parses a class element.
1012 /// </summary>
1013 /// <param name="node">Element to parse.</param>
1014 /// <param name="componentId">Identifier of parent component.</param>
1015 /// <param name="advertise">Optional Advertise State for the parent AppId element (if any).</param>
1016 /// <param name="fileServer">Optional file identifier for CLSID when not advertised.</param>
1017 /// <param name="typeLibId">Optional TypeLib GUID for CLSID.</param>
1018 /// <param name="typeLibVersion">Optional TypeLib Version for CLSID Interfaces (if any).</param>
1019 /// <param name="parentAppId">Optional parent AppId.</param>
1020 private void ParseClassElement(XElement node, string componentId, YesNoType advertise, string fileServer, string typeLibId, string typeLibVersion, string parentAppId)
1021 {
1022 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1023
1024 string appId = null;
1025 string argument = null;
1026 var class16bit = false;
1027 var class32bit = false;
1028 string classId = null;
1029 var classAdvertise = YesNoType.NotSet;
1030 var contexts = new string[0];
1031 string formattedContextString = null;
1032 var control = false;
1033 string defaultInprocHandler = null;
1034 string defaultProgId = null;
1035 string description = null;
1036 string fileTypeMask = null;
1037 string foreignServer = null;
1038 string icon = null;
1039 var iconIndex = CompilerConstants.IntegerNotSet;
1040 string insertable = null;
1041 string localFileServer = null;
1042 var programmable = false;
1043 var relativePath = YesNoType.NotSet;
1044 var safeForInit = false;
1045 var safeForScripting = false;
1046 var shortServerPath = false;
1047 string threadingModel = null;
1048 string version = null;
1049
1050 foreach (var attrib in node.Attributes())
1051 {
1052 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
1053 {
1054 switch (attrib.Name.LocalName)
1055 {
1056 case "Id":
1057 classId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
1058 break;
1059 case "Advertise":
1060 classAdvertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1061 break;
1062 case "AppId":
1063 appId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
1064 break;
1065 case "Argument":
1066 argument = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1067 break;
1068 case "Context":
1069 contexts = this.Core.GetAttributeValue(sourceLineNumbers, attrib).Split("\r\n\t ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
1070 break;
1071 case "Control":
1072 control = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1073 break;
1074 case "Description":
1075 description = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1076 break;
1077 case "Handler":
1078 defaultInprocHandler = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1079 break;
1080 case "Icon":
1081 icon = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
1082 break;
1083 case "IconIndex":
1084 iconIndex = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int16.MinValue + 1, Int16.MaxValue);
1085 break;
1086 case "RelativePath":
1087 relativePath = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1088 break;
1089
1090 // The following attributes result in rows always added to the Registry table rather than the Class table
1091 case "Insertable":
1092 insertable = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? "Insertable" : "NotInsertable";
1093 break;
1094 case "Programmable":
1095 programmable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1096 break;
1097 case "SafeForInitializing":
1098 safeForInit = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1099 break;
1100 case "SafeForScripting":
1101 safeForScripting = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1102 break;
1103 case "ForeignServer":
1104 foreignServer = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1105 break;
1106 case "Server":
1107 localFileServer = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1108 break;
1109 case "ShortPath":
1110 shortServerPath = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1111 break;
1112 case "ThreadingModel":
1113 threadingModel = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1114 break;
1115 case "Version":
1116 version = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1117 break;
1118 default:
1119 this.Core.UnexpectedAttribute(node, attrib);
1120 break;
1121 }
1122 }
1123 else
1124 {
1125 this.Core.ParseExtensionAttribute(node, attrib);
1126 }
1127 }
1128
1129 if (null == classId)
1130 {
1131 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
1132 }
1133
1134 var uniqueContexts = new HashSet<string>();
1135 foreach (var context in contexts)
1136 {
1137 if (uniqueContexts.Contains(context))
1138 {
1139 this.Core.Write(ErrorMessages.DuplicateContextValue(sourceLineNumbers, context));
1140 }
1141 else
1142 {
1143 uniqueContexts.Add(context);
1144 }
1145
1146 if (context.EndsWith("32", StringComparison.Ordinal))
1147 {
1148 class32bit = true;
1149 }
1150 else
1151 {
1152 class16bit = true;
1153 }
1154 }
1155
1156 if ((YesNoType.No == advertise && YesNoType.Yes == classAdvertise) || (YesNoType.Yes == advertise && YesNoType.No == classAdvertise))
1157 {
1158 this.Core.Write(ErrorMessages.AdvertiseStateMustMatch(sourceLineNumbers, classAdvertise.ToString(), advertise.ToString()));
1159 }
1160 else
1161 {
1162 advertise = classAdvertise;
1163 }
1164
1165 // If the advertise state has not been set, default to non-advertised.
1166 if (YesNoType.NotSet == advertise)
1167 {
1168 advertise = YesNoType.No;
1169 }
1170
1171 if (YesNoType.Yes == advertise && 0 == contexts.Length)
1172 {
1173 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Context", "Advertise", "yes"));
1174 }
1175
1176 if (!String.IsNullOrEmpty(parentAppId) && !String.IsNullOrEmpty(appId))
1177 {
1178 this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "AppId", node.Parent.Name.LocalName));
1179 }
1180
1181 if (!String.IsNullOrEmpty(localFileServer))
1182 {
1183 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, localFileServer);
1184 }
1185
1186 // Local variables used strictly for child node processing.
1187 var fileTypeMaskIndex = 0;
1188 var firstProgIdForClass = YesNoType.Yes;
1189
1190 foreach (var child in node.Elements())
1191 {
1192 if (CompilerCore.WixNamespace == child.Name.Namespace)
1193 {
1194 switch (child.Name.LocalName)
1195 {
1196 case "FileTypeMask":
1197 if (YesNoType.Yes == advertise)
1198 {
1199 fileTypeMask = String.Concat(fileTypeMask, null == fileTypeMask ? String.Empty : ";", this.ParseFileTypeMaskElement(child));
1200 }
1201 else if (YesNoType.No == advertise)
1202 {
1203 var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
1204 this.Core.CreateRegistryRow(childSourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("FileType\\", classId, "\\", fileTypeMaskIndex.ToString()), String.Empty, this.ParseFileTypeMaskElement(child), componentId);
1205 fileTypeMaskIndex++;
1206 }
1207 break;
1208 case "Interface":
1209 this.ParseInterfaceElement(child, componentId, class16bit ? classId : null, class32bit ? classId : null, typeLibId, typeLibVersion);
1210 break;
1211 case "ProgId":
1212 {
1213 var foundExtension = false;
1214 var progId = this.ParseProgIdElement(child, componentId, advertise, classId, description, null, ref foundExtension, firstProgIdForClass);
1215 if (null == defaultProgId)
1216 {
1217 defaultProgId = progId;
1218 }
1219 firstProgIdForClass = YesNoType.No;
1220 }
1221 break;
1222 default:
1223 this.Core.UnexpectedElement(node, child);
1224 break;
1225 }
1226 }
1227 else
1228 {
1229 this.Core.ParseExtensionElement(node, child);
1230 }
1231 }
1232
1233 // If this Class is being advertised.
1234 if (YesNoType.Yes == advertise)
1235 {
1236 if (null != fileServer || null != localFileServer)
1237 {
1238 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Server", "Advertise", "yes"));
1239 }
1240
1241 if (null != foreignServer)
1242 {
1243 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "ForeignServer", "Advertise", "yes"));
1244 }
1245
1246 if (null == appId && null != parentAppId)
1247 {
1248 appId = parentAppId;
1249 }
1250
1251 // add a Class row for each context
1252 if (!this.Core.EncounteredError)
1253 {
1254 foreach (var context in contexts)
1255 {
1256 var symbol = this.Core.AddSymbol(new ClassSymbol(sourceLineNumbers)
1257 {
1258 CLSID = classId,
1259 Context = context,
1260 ComponentRef = componentId,
1261 DefaultProgIdRef = defaultProgId,
1262 Description = description,
1263 FileTypeMask = fileTypeMask,
1264 DefInprocHandler = defaultInprocHandler,
1265 Argument = argument,
1266 FeatureRef = Guid.Empty.ToString("B"),
1267 RelativePath = YesNoType.Yes == relativePath,
1268 });
1269
1270 if (null != appId)
1271 {
1272 symbol.AppIdRef = appId;
1273 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.AppId, appId);
1274 }
1275
1276 if (null != icon)
1277 {
1278 symbol.IconRef = icon;
1279 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Icon, icon);
1280 }
1281
1282 if (CompilerConstants.IntegerNotSet != iconIndex)
1283 {
1284 symbol.IconIndex = iconIndex;
1285 }
1286 }
1287 }
1288 }
1289 else if (YesNoType.No == advertise)
1290 {
1291 if (null == fileServer && null == localFileServer && null == foreignServer)
1292 {
1293 this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "ForeignServer", "Server"));
1294 }
1295
1296 if (null != fileServer && null != foreignServer)
1297 {
1298 this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "ForeignServer", "File"));
1299 }
1300 else if (null != localFileServer && null != foreignServer)
1301 {
1302 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "ForeignServer", "Server"));
1303 }
1304 else if (null == fileServer)
1305 {
1306 fileServer = localFileServer;
1307 }
1308
1309 if (null != appId) // need to use nesting (not a reference) for the unadvertised Class elements
1310 {
1311 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "AppId", "Advertise", "no"));
1312 }
1313
1314 // add the core registry keys for each context in the class
1315 foreach (var context in contexts)
1316 {
1317 if (context.StartsWith("InprocServer", StringComparison.Ordinal)) // dll server
1318 {
1319 if (null != argument)
1320 {
1321 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Arguments", "Context", context));
1322 }
1323
1324 if (null != fileServer)
1325 {
1326 formattedContextString = String.Concat("[", shortServerPath ? "!" : "#", fileServer, "]");
1327 }
1328 else if (null != foreignServer)
1329 {
1330 formattedContextString = foreignServer;
1331 }
1332 }
1333 else if (context.StartsWith("LocalServer", StringComparison.Ordinal)) // exe server (quote the long path)
1334 {
1335 if (null != fileServer)
1336 {
1337 if (shortServerPath)
1338 {
1339 formattedContextString = String.Concat("[!", fileServer, "]");
1340 }
1341 else
1342 {
1343 formattedContextString = String.Concat("\"[#", fileServer, "]\"");
1344 }
1345 }
1346 else if (null != foreignServer)
1347 {
1348 formattedContextString = foreignServer;
1349 }
1350
1351 if (null != argument)
1352 {
1353 formattedContextString = String.Concat(formattedContextString, " ", argument);
1354 }
1355 }
1356 else
1357 {
1358 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Context", context, "InprocServer", "InprocServer32", "LocalServer", "LocalServer32"));
1359 }
1360
1361 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\", context), String.Empty, formattedContextString, componentId); // ClassId context
1362
1363 if (null != icon) // ClassId default icon
1364 {
1365 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, icon);
1366
1367 icon = String.Format(CultureInfo.InvariantCulture, "\"[#{0}]\"", icon);
1368
1369 if (CompilerConstants.IntegerNotSet != iconIndex)
1370 {
1371 icon = String.Concat(icon, ",", iconIndex);
1372 }
1373 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\DefaultIcon"), String.Empty, icon, componentId);
1374 }
1375 }
1376
1377 if (null != parentAppId) // ClassId AppId (must be specified via nesting, not with the AppId attribute)
1378 {
1379 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId), "AppID", parentAppId, componentId);
1380 }
1381
1382 if (null != description) // ClassId description
1383 {
1384 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId), String.Empty, description, componentId);
1385 }
1386
1387 if (null != defaultInprocHandler)
1388 {
1389 switch (defaultInprocHandler) // ClassId Default Inproc Handler
1390 {
1391 case "1":
1392 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\InprocHandler"), String.Empty, "ole2.dll", componentId);
1393 break;
1394 case "2":
1395 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\InprocHandler32"), String.Empty, "ole32.dll", componentId);
1396 break;
1397 case "3":
1398 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\InprocHandler"), String.Empty, "ole2.dll", componentId);
1399 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\InprocHandler32"), String.Empty, "ole32.dll", componentId);
1400 break;
1401 default:
1402 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\InprocHandler32"), String.Empty, defaultInprocHandler, componentId);
1403 break;
1404 }
1405 }
1406
1407 if (YesNoType.NotSet != relativePath) // ClassId's RelativePath
1408 {
1409 this.Core.Write(ErrorMessages.RelativePathForRegistryElement(sourceLineNumbers));
1410 }
1411 }
1412
1413 if (null != threadingModel)
1414 {
1415 threadingModel = Compiler.UppercaseFirstChar(threadingModel);
1416
1417 // add a threading model for each context in the class
1418 foreach (var context in contexts)
1419 {
1420 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\", context), "ThreadingModel", threadingModel, componentId);
1421 }
1422 }
1423
1424 if (null != typeLibId)
1425 {
1426 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\TypeLib"), null, typeLibId, componentId);
1427 }
1428
1429 if (null != version)
1430 {
1431 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\Version"), null, version, componentId);
1432 }
1433
1434 if (null != insertable)
1435 {
1436 // Add "*" for name so that any subkeys (shouldn't be any) are removed on uninstall.
1437 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\", insertable), "*", null, componentId);
1438 }
1439
1440 if (control)
1441 {
1442 // Add "*" for name so that any subkeys (shouldn't be any) are removed on uninstall.
1443 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\Control"), "*", null, componentId);
1444 }
1445
1446 if (programmable)
1447 {
1448 // Add "*" for name so that any subkeys (shouldn't be any) are removed on uninstall.
1449 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\Programmable"), "*", null, componentId);
1450 }
1451
1452 if (safeForInit)
1453 {
1454 this.RegisterImplementedCategories(sourceLineNumbers, "{7DD95802-9882-11CF-9FA9-00AA006C42C4}", classId, componentId);
1455 }
1456
1457 if (safeForScripting)
1458 {
1459 this.RegisterImplementedCategories(sourceLineNumbers, "{7DD95801-9882-11CF-9FA9-00AA006C42C4}", classId, componentId);
1460 }
1461 }
1462
1463 /// <summary>
1464 /// Parses an Interface element.
1465 /// </summary>
1466 /// <param name="node">Element to parse.</param>
1467 /// <param name="componentId">Identifier of parent component.</param>
1468 /// <param name="proxyId">16-bit proxy for interface.</param>
1469 /// <param name="proxyId32">32-bit proxy for interface.</param>
1470 /// <param name="typeLibId">Optional TypeLib GUID for CLSID.</param>
1471 /// <param name="typelibVersion">Version of the TypeLib to which this interface belongs. Required if typeLibId is specified</param>
1472 private void ParseInterfaceElement(XElement node, string componentId, string proxyId, string proxyId32, string typeLibId, string typelibVersion)
1473 {
1474 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1475 string baseInterface = null;
1476 string interfaceId = null;
1477 string name = null;
1478 var numMethods = CompilerConstants.IntegerNotSet;
1479 var versioned = true;
1480
1481 foreach (var attrib in node.Attributes())
1482 {
1483 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
1484 {
1485 switch (attrib.Name.LocalName)
1486 {
1487 case "Id":
1488 interfaceId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
1489 break;
1490 case "BaseInterface":
1491 baseInterface = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
1492 break;
1493 case "Name":
1494 name = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1495 break;
1496 case "NumMethods":
1497 numMethods = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue);
1498 break;
1499 case "ProxyStubClassId":
1500 proxyId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib);
1501 break;
1502 case "ProxyStubClassId32":
1503 proxyId32 = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
1504 break;
1505 case "Versioned":
1506 versioned = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1507 break;
1508 default:
1509 this.Core.UnexpectedAttribute(node, attrib);
1510 break;
1511 }
1512 }
1513 else
1514 {
1515 this.Core.ParseExtensionAttribute(node, attrib);
1516 }
1517 }
1518
1519 if (null == interfaceId)
1520 {
1521 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
1522 }
1523
1524 if (null == name)
1525 {
1526 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
1527 }
1528
1529 this.Core.ParseForExtensionElements(node);
1530
1531 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId), null, name, componentId);
1532 if (null != typeLibId)
1533 {
1534 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\TypeLib"), null, typeLibId, componentId);
1535 if (versioned)
1536 {
1537 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\TypeLib"), "Version", typelibVersion, componentId);
1538 }
1539 }
1540
1541 if (null != baseInterface)
1542 {
1543 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\BaseInterface"), null, baseInterface, componentId);
1544 }
1545
1546 if (CompilerConstants.IntegerNotSet != numMethods)
1547 {
1548 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\NumMethods"), null, numMethods.ToString(), componentId);
1549 }
1550
1551 if (null != proxyId)
1552 {
1553 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\ProxyStubClsid"), null, proxyId, componentId);
1554 }
1555
1556 if (null != proxyId32)
1557 {
1558 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("Interface\\", interfaceId, "\\ProxyStubClsid32"), null, proxyId32, componentId);
1559 }
1560 }
1561
1562 /// <summary>
1563 /// Parses a CLSID's file type mask element.
1564 /// </summary>
1565 /// <param name="node">Element to parse.</param>
1566 /// <returns>String representing the file type mask elements.</returns>
1567 private string ParseFileTypeMaskElement(XElement node)
1568 {
1569 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1570 var cb = 0;
1571 var offset = CompilerConstants.IntegerNotSet;
1572 string mask = null;
1573 string value = null;
1574
1575 foreach (var attrib in node.Attributes())
1576 {
1577 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
1578 {
1579 switch (attrib.Name.LocalName)
1580 {
1581 case "Mask":
1582 mask = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1583 break;
1584 case "Offset":
1585 offset = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue);
1586 break;
1587 case "Value":
1588 value = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1589 break;
1590 default:
1591 this.Core.UnexpectedAttribute(node, attrib);
1592 break;
1593 }
1594 }
1595 else
1596 {
1597 this.Core.ParseExtensionAttribute(node, attrib);
1598 }
1599 }
1600
1601
1602 if (null == mask)
1603 {
1604 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Mask"));
1605 }
1606
1607 if (CompilerConstants.IntegerNotSet == offset)
1608 {
1609 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Offset"));
1610 }
1611
1612 if (null == value)
1613 {
1614 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value"));
1615 }
1616
1617 this.Core.ParseForExtensionElements(node);
1618
1619 if (!this.Core.EncounteredError)
1620 {
1621 if (mask.Length != value.Length)
1622 {
1623 this.Core.Write(ErrorMessages.ValueAndMaskMustBeSameLength(sourceLineNumbers));
1624 }
1625 cb = mask.Length / 2;
1626 }
1627
1628 return String.Concat(offset.ToString(CultureInfo.InvariantCulture.NumberFormat), ",", cb.ToString(CultureInfo.InvariantCulture.NumberFormat), ",", mask, ",", value);
1629 }
1630
1631 /// <summary>
1632 /// Parses a product search element.
1633 /// </summary>
1634 /// <param name="node">Element to parse.</param>
1635 /// <param name="propertyId"></param>
1636 /// <returns>Signature for search element.</returns>
1637 private void ParseProductSearchElement(XElement node, string propertyId)
1638 {
1639 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1640
1641 string upgradeCode = null;
1642 string language = null;
1643 string maximum = null;
1644 string minimum = null;
1645 var excludeLanguages = false;
1646 var maxInclusive = false;
1647 var minInclusive = true;
1648
1649 foreach (var attrib in node.Attributes())
1650 {
1651 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
1652 {
1653 switch (attrib.Name.LocalName)
1654 {
1655 case "ExcludeLanguages":
1656 excludeLanguages = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1657 break;
1658 case "IncludeMaximum":
1659 maxInclusive = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1660 break;
1661 case "IncludeMinimum":
1662 minInclusive = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1663 break;
1664 case "Language":
1665 language = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1666 break;
1667 case "Minimum":
1668 minimum = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1669 break;
1670 case "Maximum":
1671 maximum = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1672 break;
1673 case "UpgradeCode":
1674 upgradeCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
1675 break;
1676 default:
1677 this.Core.UnexpectedAttribute(node, attrib);
1678 break;
1679 }
1680 }
1681 else
1682 {
1683 this.Core.ParseExtensionAttribute(node, attrib);
1684 }
1685 }
1686
1687 if (null == minimum && null == maximum)
1688 {
1689 this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "Minimum", "Maximum"));
1690 }
1691
1692 this.Core.ParseForExtensionElements(node);
1693
1694 if (!this.Core.EncounteredError)
1695 {
1696 this.Core.AddSymbol(new UpgradeSymbol(sourceLineNumbers)
1697 {
1698 UpgradeCode = upgradeCode,
1699 VersionMin = minimum,
1700 VersionMax = maximum,
1701 Language = language,
1702 ActionProperty = propertyId,
1703 OnlyDetect = true,
1704 ExcludeLanguages = excludeLanguages,
1705 VersionMaxInclusive = maxInclusive,
1706 VersionMinInclusive = minInclusive,
1707 });
1708 }
1709 }
1710
1711 /// <summary>
1712 /// Parses a registry search element.
1713 /// </summary>
1714 /// <param name="node">Element to parse.</param>
1715 /// <returns>Signature for search element.</returns>
1716 private string ParseRegistrySearchElement(XElement node)
1717 {
1718 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1719 Identifier id = null;
1720 string key = null;
1721 string name = null;
1722 RegistryRootType? root = null;
1723 RegLocatorType? type = null;
1724 var search64bit = this.Context.IsCurrentPlatform64Bit;
1725
1726 foreach (var attrib in node.Attributes())
1727 {
1728 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
1729 {
1730 switch (attrib.Name.LocalName)
1731 {
1732 case "Id":
1733 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
1734 break;
1735 case "Bitness":
1736 var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1737 switch (bitnessValue)
1738 {
1739 case "always32":
1740 search64bit = false;
1741 break;
1742 case "always64":
1743 search64bit = true;
1744 break;
1745 case "default":
1746 case "":
1747 break;
1748 default:
1749 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64"));
1750 break;
1751 }
1752 break;
1753 case "Key":
1754 key = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1755 break;
1756 case "Name":
1757 name = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1758 break;
1759 case "Root":
1760 root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, false);
1761 break;
1762 case "Type":
1763 var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1764 switch (typeValue)
1765 {
1766 case "directory":
1767 type = RegLocatorType.Directory;
1768 break;
1769 case "file":
1770 type = RegLocatorType.FileName;
1771 break;
1772 case "raw":
1773 type = RegLocatorType.Raw;
1774 break;
1775 case "":
1776 break;
1777 default:
1778 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Type", typeValue, "directory", "file", "raw"));
1779 break;
1780 }
1781 break;
1782 default:
1783 this.Core.UnexpectedAttribute(node, attrib);
1784 break;
1785 }
1786 }
1787 else
1788 {
1789 this.Core.ParseExtensionAttribute(node, attrib);
1790 }
1791 }
1792
1793 if (null == id)
1794 {
1795 id = this.Core.CreateIdentifier("reg", root.ToString(), key, name, type.ToString(), search64bit.ToString());
1796 }
1797
1798 if (null == key)
1799 {
1800 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key"));
1801 }
1802
1803 if (!root.HasValue)
1804 {
1805 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root"));
1806 }
1807
1808 if (!type.HasValue)
1809 {
1810 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Type"));
1811 }
1812
1813 var signature = id.Id;
1814 var oneChild = false;
1815 foreach (var child in node.Elements())
1816 {
1817 if (CompilerCore.WixNamespace == child.Name.Namespace)
1818 {
1819 switch (child.Name.LocalName)
1820 {
1821 case "DirectorySearch":
1822 if (oneChild)
1823 {
1824 this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName));
1825 }
1826 oneChild = true;
1827
1828 // directorysearch parentage should work like directory element, not the rest of the signature type because of the DrLocator.Parent column
1829 signature = this.ParseDirectorySearchElement(child, id.Id);
1830 break;
1831 case "DirectorySearchRef":
1832 if (oneChild)
1833 {
1834 this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName));
1835 }
1836 oneChild = true;
1837 signature = this.ParseDirectorySearchRefElement(child, id.Id);
1838 break;
1839 case "FileSearch":
1840 if (oneChild)
1841 {
1842 this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName));
1843 }
1844 oneChild = true;
1845 signature = this.ParseFileSearchElement(child, id.Id, false, CompilerConstants.IntegerNotSet);
1846 id = new Identifier(AccessModifier.Section, signature); // FileSearch signatures override parent signatures
1847 break;
1848 case "FileSearchRef":
1849 if (oneChild)
1850 {
1851 this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName));
1852 }
1853 oneChild = true;
1854 var newId = this.ParseSimpleRefElement(child, SymbolDefinitions.Signature); // FileSearch signatures override parent signatures
1855 id = new Identifier(AccessModifier.Section, newId);
1856 signature = null;
1857 break;
1858 default:
1859 this.Core.UnexpectedElement(node, child);
1860 break;
1861 }
1862 }
1863 else
1864 {
1865 this.Core.ParseExtensionElement(node, child);
1866 }
1867 }
1868
1869 if (!this.Core.EncounteredError)
1870 {
1871 this.Core.AddSymbol(new RegLocatorSymbol(sourceLineNumbers, id)
1872 {
1873 Root = root.Value,
1874 Key = key,
1875 Name = name,
1876 Type = type.Value,
1877 Win64 = search64bit,
1878 });
1879 }
1880
1881 return signature;
1882 }
1883
1884 /// <summary>
1885 /// Parses a registry search reference element.
1886 /// </summary>
1887 /// <param name="node">Element to parse.</param>
1888 /// <returns>Signature of referenced search element.</returns>
1889 private string ParseRegistrySearchRefElement(XElement node)
1890 {
1891 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1892 string id = null;
1893
1894 foreach (var attrib in node.Attributes())
1895 {
1896 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
1897 {
1898 switch (attrib.Name.LocalName)
1899 {
1900 case "Id":
1901 id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
1902 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.RegLocator, id);
1903 break;
1904 default:
1905 this.Core.UnexpectedAttribute(node, attrib);
1906 break;
1907 }
1908 }
1909 else
1910 {
1911 this.Core.ParseExtensionAttribute(node, attrib);
1912 }
1913 }
1914
1915 if (null == id)
1916 {
1917 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
1918 }
1919
1920 this.Core.ParseForExtensionElements(node);
1921
1922 return id; // the id of the RegistrySearchRef element is its signature
1923 }
1924
1925 /// <summary>
1926 /// Parses child elements for search signatures.
1927 /// </summary>
1928 /// <param name="node">Node whose children we are parsing.</param>
1929 /// <returns>Returns list of string signatures.</returns>
1930 private List<string> ParseSearchSignatures(XElement node)
1931 {
1932 var signatures = new List<string>();
1933
1934 foreach (var child in node.Elements())
1935 {
1936 string signature = null;
1937 if (CompilerCore.WixNamespace == child.Name.Namespace)
1938 {
1939 switch (child.Name.LocalName)
1940 {
1941 case "ComplianceDrive":
1942 signature = this.ParseComplianceDriveElement(child);
1943 break;
1944 case "ComponentSearch":
1945 signature = this.ParseComponentSearchElement(child);
1946 break;
1947 case "DirectorySearch":
1948 signature = this.ParseDirectorySearchElement(child, String.Empty);
1949 break;
1950 case "DirectorySearchRef":
1951 signature = this.ParseDirectorySearchRefElement(child, String.Empty);
1952 break;
1953 case "IniFileSearch":
1954 signature = this.ParseIniFileSearchElement(child);
1955 break;
1956 case "ProductSearch":
1957 // handled in ParsePropertyElement
1958 break;
1959 case "RegistrySearch":
1960 signature = this.ParseRegistrySearchElement(child);
1961 break;
1962 case "RegistrySearchRef":
1963 signature = this.ParseRegistrySearchRefElement(child);
1964 break;
1965 default:
1966 this.Core.UnexpectedElement(node, child);
1967 break;
1968 }
1969 }
1970 else
1971 {
1972 this.Core.ParseExtensionElement(node, child);
1973 }
1974
1975
1976 if (!String.IsNullOrEmpty(signature))
1977 {
1978 signatures.Add(signature);
1979 }
1980 }
1981
1982 return signatures;
1983 }
1984
1985 /// <summary>
1986 /// Parses a compliance drive element.
1987 /// </summary>
1988 /// <param name="node">Element to parse.</param>
1989 /// <returns>Signature of nested search elements.</returns>
1990 private string ParseComplianceDriveElement(XElement node)
1991 {
1992 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1993 string signature = null;
1994
1995 var oneChild = false;
1996 foreach (var child in node.Elements())
1997 {
1998 if (CompilerCore.WixNamespace == child.Name.Namespace)
1999 {
2000 var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2001 switch (child.Name.LocalName)
2002 {
2003 case "DirectorySearch":
2004 if (oneChild)
2005 {
2006 this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName));
2007 }
2008 oneChild = true;
2009 signature = this.ParseDirectorySearchElement(child, "CCP_DRIVE");
2010 break;
2011 case "DirectorySearchRef":
2012 if (oneChild)
2013 {
2014 this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName));
2015 }
2016 oneChild = true;
2017 signature = this.ParseDirectorySearchRefElement(child, "CCP_DRIVE");
2018 break;
2019 default:
2020 this.Core.UnexpectedElement(node, child);
2021 break;
2022 }
2023 }
2024 else
2025 {
2026 this.Core.ParseExtensionElement(node, child);
2027 }
2028 }
2029
2030 if (null == signature)
2031 {
2032 this.Core.Write(ErrorMessages.SearchElementRequired(sourceLineNumbers, node.Name.LocalName));
2033 }
2034
2035 return signature;
2036 }
2037
2038 /// <summary>
2039 /// Parses a compilance check element.
2040 /// </summary>
2041 /// <param name="node">Element to parse.</param>
2042 private void ParseComplianceCheckElement(XElement node)
2043 {
2044 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2045
2046 foreach (var attrib in node.Attributes())
2047 {
2048 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
2049 {
2050 switch (attrib.Name.LocalName)
2051 {
2052 default:
2053 this.Core.UnexpectedAttribute(node, attrib);
2054 break;
2055 }
2056 }
2057 else
2058 {
2059 this.Core.ParseExtensionAttribute(node, attrib);
2060 }
2061 }
2062
2063 string signature = null;
2064
2065 // see if this property is used for appSearch
2066 var signatures = this.ParseSearchSignatures(node);
2067 foreach (var sig in signatures)
2068 {
2069 // if we haven't picked a signature for this ComplianceCheck pick
2070 // this one
2071 if (null == signature)
2072 {
2073 signature = sig;
2074 }
2075 else if (signature != sig)
2076 {
2077 // all signatures under a ComplianceCheck must be the same
2078 this.Core.Write(ErrorMessages.MultipleIdentifiersFound(sourceLineNumbers, node.Name.LocalName, sig, signature));
2079 }
2080 }
2081
2082 if (null == signature)
2083 {
2084 this.Core.Write(ErrorMessages.SearchElementRequired(sourceLineNumbers, node.Name.LocalName));
2085 }
2086
2087 if (!this.Core.EncounteredError)
2088 {
2089 this.Core.AddSymbol(new CCPSearchSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, signature)));
2090 }
2091 }
2092
2093 /// <summary>
2094 /// Parses a component element.
2095 /// </summary>
2096 /// <param name="node">Element to parse.</param>
2097 /// <param name="parentType">Type of component's complex reference parent. Will be Uknown if there is no parent.</param>
2098 /// <param name="parentId">Optional identifier for component's primary parent.</param>
2099 /// <param name="parentLanguage">Optional string for component's parent's language.</param>
2100 /// <param name="diskId">Optional disk id inherited from parent directory.</param>
2101 /// <param name="directoryId">Optional identifier for component's directory.</param>
2102 /// <param name="srcPath">Optional source path for files up to this point.</param>
2103 private void ParseComponentElement(XElement node, ComplexReferenceParentType parentType, string parentId, string parentLanguage, int diskId, string directoryId, string srcPath)
2104 {
2105 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2106
2107 var comPlusBits = CompilerConstants.IntegerNotSet;
2108 string condition = null;
2109 string subdirectory = null;
2110 var encounteredODBCDataSource = false;
2111 var files = 0;
2112 var guid = "*";
2113 Identifier id = null;
2114 string componentIdPlaceholder = null;
2115 var keyFound = false;
2116 string keyPath = null;
2117
2118 var keyPathType = ComponentKeyPathType.Directory;
2119 var location = ComponentLocation.LocalOnly;
2120 var disableRegistryReflection = false;
2121
2122 var neverOverwrite = false;
2123 var permanent = false;
2124 var shared = false;
2125 var sharedDllRefCount = false;
2126 var transitive = false;
2127 var uninstallWhenSuperseded = false;
2128 var win64 = this.Context.IsCurrentPlatform64Bit;
2129
2130 var multiInstance = false;
2131 var symbols = new List<string>();
2132 string feature = null;
2133
2134 foreach (var attrib in node.Attributes())
2135 {
2136 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
2137 {
2138 switch (attrib.Name.LocalName)
2139 {
2140 case "Id":
2141 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
2142 break;
2143 case "Bitness":
2144 var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
2145 switch (bitnessValue)
2146 {
2147 case "always32":
2148 win64 = false;
2149 break;
2150 case "always64":
2151 win64 = true;
2152 break;
2153 case "default":
2154 case "":
2155 break;
2156 default:
2157 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64"));
2158 break;
2159 }
2160 break;
2161 case "ComPlusFlags":
2162 comPlusBits = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue);
2163 break;
2164 case "DisableRegistryReflection":
2165 disableRegistryReflection = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
2166 break;
2167 case "Condition":
2168 condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
2169 break;
2170 case "Directory":
2171 directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
2172 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId);
2173 break;
2174 case "Subdirectory":
2175 subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true);
2176 break;
2177 case "DiskId":
2178 diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue);
2179 break;
2180 case "Feature":
2181 feature = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
2182 break;
2183 case "Guid":
2184 guid = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, true, true);
2185 break;
2186 case "KeyPath":
2187 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
2188 {
2189 keyFound = true;
2190 keyPath = null;
2191 }
2192 break;
2193 case "Location":
2194 var locationValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
2195 switch (locationValue)
2196 {
2197 case "either":
2198 location = ComponentLocation.Either;
2199 break;
2200 case "local": // this is the default
2201 location = ComponentLocation.LocalOnly;
2202 break;
2203 case "source":
2204 location = ComponentLocation.SourceOnly;
2205 break;
2206 case "":
2207 break;
2208 default:
2209 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, locationValue, "either", "local", "source"));
2210 break;
2211 }
2212 break;
2213 case "MultiInstance":
2214 multiInstance = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
2215 break;
2216 case "NeverOverwrite":
2217 neverOverwrite = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
2218 break;
2219 case "Permanent":
2220 permanent = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
2221 break;
2222 case "Shared":
2223 shared = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
2224 break;
2225 case "SharedDllRefCount":
2226 sharedDllRefCount = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
2227 break;
2228 case "Transitive":
2229 transitive = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
2230 break;
2231 case "UninstallWhenSuperseded":
2232 uninstallWhenSuperseded = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
2233 break;
2234 default:
2235 this.Core.UnexpectedAttribute(node, attrib);
2236 break;
2237 }
2238 }
2239 else
2240 {
2241 this.Core.ParseExtensionAttribute(node, attrib);
2242 }
2243 }
2244
2245 if (id == null)
2246 {
2247 // Placeholder id for defaulting Component/@Id to keypath id.
2248 componentIdPlaceholder = String.Concat(Compiler.ComponentIdPlaceholderStart, this.componentIdPlaceholders.Count, Compiler.ComponentIdPlaceholderEnd);
2249 id = new Identifier(AccessModifier.Section, componentIdPlaceholder);
2250 }
2251
2252 if (String.IsNullOrEmpty(directoryId))
2253 {
2254 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Directory"));
2255 }
2256
2257 if (!String.IsNullOrEmpty(subdirectory))
2258 {
2259 directoryId = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, directoryId, subdirectory);
2260 }
2261
2262 if (String.IsNullOrEmpty(guid) && shared)
2263 {
2264 this.Core.Write(ErrorMessages.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Shared", "yes", "Guid", ""));
2265 }
2266
2267 if (String.IsNullOrEmpty(guid) && permanent)
2268 {
2269 this.Core.Write(ErrorMessages.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Permanent", "yes", "Guid", ""));
2270 }
2271
2272 if (null != feature)
2273 {
2274 if (this.compilingModule)
2275 {
2276 this.Core.Write(ErrorMessages.IllegalAttributeInMergeModule(sourceLineNumbers, node.Name.LocalName, "Feature"));
2277 }
2278 else
2279 {
2280 if (ComplexReferenceParentType.Feature == parentType || ComplexReferenceParentType.FeatureGroup == parentType)
2281 {
2282 this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "Feature", node.Parent.Name.LocalName));
2283 }
2284 else
2285 {
2286 this.Core.CreateComplexReference(sourceLineNumbers, ComplexReferenceParentType.Feature, feature, null, ComplexReferenceChildType.Component, id.Id, true);
2287 }
2288 }
2289 }
2290
2291 foreach (var child in node.Elements())
2292 {
2293 var keyPathSet = YesNoType.NotSet;
2294 string keyPossible = null;
2295 ComponentKeyPathType? keyBit = null;
2296
2297 if (CompilerCore.WixNamespace == child.Name.Namespace)
2298 {
2299 switch (child.Name.LocalName)
2300 {
2301 case "AppId":
2302 this.ParseAppIdElement(child, id.Id, YesNoType.NotSet, null, null, null);
2303 break;
2304 case "Category":
2305 this.ParseCategoryElement(child, id.Id);
2306 break;
2307 case "Class":
2308 this.ParseClassElement(child, id.Id, YesNoType.NotSet, null, null, null, null);
2309 break;
2310 case "CopyFile":
2311 this.ParseCopyFileElement(child, id.Id, null);
2312 break;
2313 case "CreateFolder":
2314 var createdFolder = this.ParseCreateFolderElement(child, id.Id, directoryId, win64);
2315 break;
2316 case "Environment":
2317 this.ParseEnvironmentElement(child, id.Id);
2318 break;
2319 case "Extension":
2320 this.ParseExtensionElement(child, id.Id, YesNoType.NotSet, null);
2321 break;
2322 case "File":
2323 keyPathSet = this.ParseFileElement(child, id.Id, directoryId, diskId, srcPath, out keyPossible, win64, guid);
2324 keyBit = ComponentKeyPathType.File;
2325 files++;
2326 break;
2327 case "IniFile":
2328 this.ParseIniFileElement(child, id.Id);
2329 break;
2330 case "Interface":
2331 this.ParseInterfaceElement(child, id.Id, null, null, null, null);
2332 break;
2333 case "IsolateComponent":
2334 this.ParseIsolateComponentElement(child, id.Id);
2335 break;
2336 case "ODBCDataSource":
2337 keyPathSet = this.ParseODBCDataSource(child, id.Id, null, out keyPossible);
2338 keyBit = ComponentKeyPathType.OdbcDataSource;
2339 encounteredODBCDataSource = true;
2340 break;
2341 case "ODBCDriver":
2342 this.ParseODBCDriverOrTranslator(child, id.Id, null, SymbolDefinitionType.ODBCDriver);
2343 break;
2344 case "ODBCTranslator":
2345 this.ParseODBCDriverOrTranslator(child, id.Id, null, SymbolDefinitionType.ODBCTranslator);
2346 break;
2347 case "ProgId":
2348 var foundExtension = false;
2349 this.ParseProgIdElement(child, id.Id, YesNoType.NotSet, null, null, null, ref foundExtension, YesNoType.NotSet);
2350 break;
2351 case "Provides":
2352 if (win64)
2353 {
2354 this.Messaging.Write(CompilerWarnings.Win64Component(sourceLineNumbers, id.Id));
2355 }
2356
2357 keyPathSet = this.ParseProvidesElement(child, null, id.Id, out keyPossible);
2358 keyBit = ComponentKeyPathType.Registry;
2359 break;
2360
2361 case "RegistryKey":
2362 keyPathSet = this.ParseRegistryKeyElement(child, id.Id, null, null, win64, out keyPossible);
2363 keyBit = ComponentKeyPathType.Registry;
2364 break;
2365 case "RegistryValue":
2366 keyPathSet = this.ParseRegistryValueElement(child, id.Id, null, null, win64, out keyPossible);
2367 keyBit = ComponentKeyPathType.Registry;
2368 break;
2369 case "RemoveFile":
2370 this.ParseRemoveFileElement(child, id.Id, directoryId);
2371 break;
2372 case "RemoveFolder":
2373 this.ParseRemoveFolderElement(child, id.Id, directoryId);
2374 break;
2375 case "RemoveRegistryKey":
2376 this.ParseRemoveRegistryKeyElement(child, id.Id);
2377 break;
2378 case "RemoveRegistryValue":
2379 this.ParseRemoveRegistryValueElement(child, id.Id);
2380 break;
2381 case "ReserveCost":
2382 this.ParseReserveCostElement(child, id.Id, directoryId);
2383 break;
2384 case "ServiceConfig":
2385 this.ParseServiceConfigElement(child, id.Id, null);
2386 break;
2387 case "ServiceConfigFailureActions":
2388 this.ParseServiceConfigFailureActionsElement(child, id.Id, null);
2389 break;
2390 case "ServiceControl":
2391 this.ParseServiceControlElement(child, id.Id);
2392 break;
2393 case "ServiceInstall":
2394 this.ParseServiceInstallElement(child, id.Id, win64);
2395 break;
2396 case "Shortcut":
2397 this.ParseShortcutElement(child, id.Id, node.Name.LocalName, directoryId, YesNoType.No);
2398 break;
2399 case "SymbolPath":
2400 symbols.Add(this.ParseSymbolPathElement(child));
2401 break;
2402 case "TypeLib":
2403 this.ParseTypeLibElement(child, id.Id, null, win64);
2404 break;
2405 default:
2406 this.Core.UnexpectedElement(node, child);
2407 break;
2408 }
2409 }
2410 else
2411 {
2412 var context = new Dictionary<string, string>() { { "ComponentId", id?.Id }, { "DirectoryId", directoryId }, { "Win64", win64.ToString() }, };
2413 var possibleKeyPath = this.Core.ParsePossibleKeyPathExtensionElement(node, child, context);
2414 if (null != possibleKeyPath)
2415 {
2416 if (PossibleKeyPathType.None == possibleKeyPath.Type)
2417 {
2418 keyPathSet = YesNoType.No;
2419 }
2420 else
2421 {
2422 keyPathSet = possibleKeyPath.Explicit ? YesNoType.Yes : YesNoType.NotSet;
2423
2424 if (!String.IsNullOrEmpty(possibleKeyPath.Id))
2425 {
2426 keyPossible = possibleKeyPath.Id;
2427 }
2428
2429 if (PossibleKeyPathType.Registry == possibleKeyPath.Type || PossibleKeyPathType.RegistryFormatted == possibleKeyPath.Type)
2430 {
2431 keyBit = ComponentKeyPathType.Registry; //MsiInterop.MsidbComponentAttributesRegistryKeyPath;
2432 }
2433 }
2434 }
2435 }
2436
2437 // Verify that either the key path is not set, or it is set along with a key path ID.
2438 Debug.Assert(YesNoType.Yes != keyPathSet || (YesNoType.Yes == keyPathSet && null != keyPossible));
2439
2440 if (keyFound && YesNoType.Yes == keyPathSet)
2441 {
2442 this.Core.Write(ErrorMessages.ComponentMultipleKeyPaths(sourceLineNumbers, node.Name.LocalName, "KeyPath", "yes", "File", "RegistryValue", "ODBCDataSource"));
2443 }
2444
2445 // if a possible KeyPath has been found and that value was explicitly set as
2446 // the KeyPath of the component, set it now. Alternatively, if a possible
2447 // KeyPath has been found and no KeyPath has been previously set, use this
2448 // value as the default KeyPath of the component
2449 if (!String.IsNullOrEmpty(keyPossible) && (YesNoType.Yes == keyPathSet || (YesNoType.NotSet == keyPathSet && String.IsNullOrEmpty(keyPath) && !keyFound)))
2450 {
2451 keyFound = YesNoType.Yes == keyPathSet;
2452 keyPath = keyPossible;
2453 keyPathType = keyBit.Value;
2454 }
2455 }
2456
2457 // Check for conditions that exclude this component from using implicit ids and/or generated guids.
2458 var allowImplicitIds = true;
2459 if (encounteredODBCDataSource || ComponentKeyPathType.Directory == keyPathType)
2460 {
2461 allowImplicitIds = false;
2462 if (guid == "*")
2463 {
2464 this.Core.Write(ErrorMessages.IllegalComponentWithAutoGeneratedGuid(sourceLineNumbers));
2465 }
2466 }
2467 else if (0 < files && ComponentKeyPathType.Registry == keyPathType)
2468 {
2469 allowImplicitIds = false;
2470 if (guid == "*")
2471 {
2472 this.Core.Write(ErrorMessages.IllegalComponentWithAutoGeneratedGuid(sourceLineNumbers, true));
2473 }
2474 }
2475
2476 // Check for implicit KeyPath which can easily be accidentally changed
2477 if (this.ShowPedanticMessages && !keyFound && !allowImplicitIds)
2478 {
2479 this.Core.Write(ErrorMessages.ImplicitComponentKeyPath(sourceLineNumbers, id.Id));
2480 }
2481
2482 // If there isn't an @Id attribute value, replace the placeholder with the id of the keypath.
2483 // either an explicit KeyPath="yes" attribute must be specified or requirements for
2484 // generatable guid must be met.
2485 if (componentIdPlaceholder == id.Id)
2486 {
2487 if (allowImplicitIds || keyFound && !String.IsNullOrEmpty(keyPath))
2488 {
2489 this.componentIdPlaceholders.Add(componentIdPlaceholder, keyPath);
2490
2491 id = new Identifier(AccessModifier.Section, keyPath);
2492 }
2493 else
2494 {
2495 this.Core.Write(ErrorMessages.CannotDefaultComponentId(sourceLineNumbers));
2496 }
2497 }
2498
2499 // finally add the Component table row
2500 if (!this.Core.EncounteredError)
2501 {
2502 this.Core.AddSymbol(new ComponentSymbol(sourceLineNumbers, id)
2503 {
2504 ComponentId = guid,
2505 DirectoryRef = directoryId,
2506 Location = location,
2507 Condition = condition,
2508 KeyPath = keyPath,
2509 KeyPathType = keyPathType,
2510 DisableRegistryReflection = disableRegistryReflection,
2511 NeverOverwrite = neverOverwrite,
2512 Permanent = permanent,
2513 SharedDllRefCount = sharedDllRefCount,
2514 Shared = shared,
2515 Transitive = transitive,
2516 UninstallWhenSuperseded = uninstallWhenSuperseded,
2517 Win64 = win64,
2518 });
2519
2520 if (multiInstance)
2521 {
2522 this.Core.AddSymbol(new WixInstanceComponentSymbol(sourceLineNumbers, id)
2523 {
2524 ComponentRef = id.Id,
2525 });
2526 }
2527
2528 if (0 < symbols.Count)
2529 {
2530 this.Core.AddSymbol(new WixDeltaPatchSymbolPathsSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, SymbolPathType.Component, id.Id))
2531 {
2532 SymbolType = SymbolPathType.Component,
2533 SymbolId = id.Id,
2534 SymbolPaths = String.Join(";", symbols),
2535 });
2536 }
2537
2538 // Complus
2539 if (CompilerConstants.IntegerNotSet != comPlusBits)
2540 {
2541 this.Core.AddSymbol(new ComplusSymbol(sourceLineNumbers)
2542 {
2543 ComponentRef = id.Id,
2544 ExpType = comPlusBits,
2545 });
2546 }
2547
2548 // if this is a module, automatically add this component to the references to ensure it gets in the ModuleComponents table
2549 if (this.compilingModule)
2550 {
2551 this.Core.CreateComplexReference(sourceLineNumbers, ComplexReferenceParentType.Module, this.activeName, this.activeLanguage, ComplexReferenceChildType.Component, id.Id, false);
2552 }
2553 else if (ComplexReferenceParentType.Unknown != parentType && null != parentId) // if parent was provided, add a complex reference to that.
2554 {
2555 // If the Component is defined directly under a feature, then mark the complex reference primary.
2556 this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, parentLanguage, ComplexReferenceChildType.Component, id.Id, ComplexReferenceParentType.Feature == parentType);
2557 }
2558 }
2559 }
2560
2561 /// <summary>
2562 /// Parses a component group element.
2563 /// </summary>
2564 /// <param name="node">Element to parse.</param>
2565 /// <param name="parentType"></param>
2566 /// <param name="parentId"></param>
2567 private void ParseComponentGroupElement(XElement node, ComplexReferenceParentType parentType, string parentId)
2568 {
2569 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2570 Identifier id = null;
2571 string directoryId = null;
2572 string subdirectory = null;
2573 string source = null;
2574
2575 foreach (var attrib in node.Attributes())
2576 {
2577 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
2578 {
2579 switch (attrib.Name.LocalName)
2580 {
2581 case "Id":
2582 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
2583 break;
2584 case "Directory":
2585 directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
2586 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId);
2587 break;
2588 case "Subdirectory":
2589 subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true);
2590 break;
2591 case "Source":
2592 source = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
2593 break;
2594 default:
2595 this.Core.UnexpectedAttribute(node, attrib);
2596 break;
2597 }
2598 }
2599 else
2600 {
2601 this.Core.ParseExtensionAttribute(node, attrib);
2602 }
2603 }
2604
2605 if (null == id)
2606 {
2607 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
2608 id = Identifier.Invalid;
2609 }
2610
2611 directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId, subdirectory, "Directory", "Subdirectory");
2612
2613 if (!String.IsNullOrEmpty(source) && !source.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal))
2614 {
2615 source = String.Concat(source, Path.DirectorySeparatorChar);
2616 }
2617
2618 foreach (var child in node.Elements())
2619 {
2620 if (CompilerCore.WixNamespace == child.Name.Namespace)
2621 {
2622 switch (child.Name.LocalName)
2623 {
2624 case "ComponentGroupRef":
2625 this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.ComponentGroup, id.Id, null);
2626 break;
2627 case "ComponentRef":
2628 this.ParseComponentRefElement(child, ComplexReferenceParentType.ComponentGroup, id.Id, null);
2629 break;
2630 case "Component":
2631 this.ParseComponentElement(child, ComplexReferenceParentType.ComponentGroup, id.Id, null, CompilerConstants.IntegerNotSet, directoryId, source);
2632 break;
2633 default:
2634 this.Core.UnexpectedElement(node, child);
2635 break;
2636 }
2637 }
2638 else
2639 {
2640 this.Core.ParseExtensionElement(node, child);
2641 }
2642 }
2643
2644 if (!this.Core.EncounteredError)
2645 {
2646 this.Core.AddSymbol(new WixComponentGroupSymbol(sourceLineNumbers, id));
2647
2648 // Add this componentGroup and its parent in WixGroup.
2649 this.Core.CreateWixGroupRow(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.ComponentGroup, id.Id);
2650 }
2651 }
2652
2653 /// <summary>
2654 /// Parses a component group reference element.
2655 /// </summary>
2656 /// <param name="node">Element to parse.</param>
2657 /// <param name="parentType">ComplexReferenceParentType of parent element.</param>
2658 /// <param name="parentId">Identifier of parent element (usually a Feature or Module).</param>
2659 /// <param name="parentLanguage">Optional language of parent (only useful for Modules).</param>
2660 private void ParseComponentGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId, string parentLanguage)
2661 {
2662 Debug.Assert(ComplexReferenceParentType.ComponentGroup == parentType || ComplexReferenceParentType.FeatureGroup == parentType || ComplexReferenceParentType.Feature == parentType || ComplexReferenceParentType.Module == parentType);
2663
2664 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2665 string id = null;
2666 var primary = YesNoType.NotSet;
2667
2668 foreach (var attrib in node.Attributes())
2669 {
2670 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
2671 {
2672 switch (attrib.Name.LocalName)
2673 {
2674 case "Id":
2675 id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
2676 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixComponentGroup, id);
2677 break;
2678 case "Primary":
2679 primary = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
2680 break;
2681 default:
2682 this.Core.UnexpectedAttribute(node, attrib);
2683 break;
2684 }
2685 }
2686 else
2687 {
2688 this.Core.ParseExtensionAttribute(node, attrib);
2689 }
2690 }
2691
2692 if (null == id)
2693 {
2694 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
2695 }
2696
2697 this.Core.ParseForExtensionElements(node);
2698
2699 this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, parentLanguage, ComplexReferenceChildType.ComponentGroup, id, (YesNoType.Yes == primary));
2700 }
2701
2702 /// <summary>
2703 /// Parses a component reference element.
2704 /// </summary>
2705 /// <param name="node">Element to parse.</param>
2706 /// <param name="parentType">ComplexReferenceParentType of parent element.</param>
2707 /// <param name="parentId">Identifier of parent element (usually a Feature or Module).</param>
2708 /// <param name="parentLanguage">Optional language of parent (only useful for Modules).</param>
2709 private void ParseComponentRefElement(XElement node, ComplexReferenceParentType parentType, string parentId, string parentLanguage)
2710 {
2711 Debug.Assert(ComplexReferenceParentType.FeatureGroup == parentType || ComplexReferenceParentType.ComponentGroup == parentType || ComplexReferenceParentType.Feature == parentType || ComplexReferenceParentType.Module == parentType);
2712
2713 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2714 string id = null;
2715 var primary = YesNoType.NotSet;
2716
2717 foreach (var attrib in node.Attributes())
2718 {
2719 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
2720 {
2721 switch (attrib.Name.LocalName)
2722 {
2723 case "Id":
2724 id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
2725 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Component, id);
2726 break;
2727 case "Primary":
2728 primary = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
2729 break;
2730 default:
2731 this.Core.UnexpectedAttribute(node, attrib);
2732 break;
2733 }
2734 }
2735 else
2736 {
2737 this.Core.ParseExtensionAttribute(node, attrib);
2738 }
2739 }
2740
2741 if (null == id)
2742 {
2743 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
2744 }
2745
2746 this.Core.ParseForExtensionElements(node);
2747
2748 this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, parentLanguage, ComplexReferenceChildType.Component, id, (YesNoType.Yes == primary));
2749 }
2750
2751 /// <summary>
2752 /// Parses a component search element.
2753 /// </summary>
2754 /// <param name="node">Element to parse.</param>
2755 /// <returns>Signature for search element.</returns>
2756 private string ParseComponentSearchElement(XElement node)
2757 {
2758 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2759 Identifier id = null;
2760 string componentId = null;
2761 var type = LocatorType.Filename;
2762
2763 foreach (var attrib in node.Attributes())
2764 {
2765 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
2766 {
2767 switch (attrib.Name.LocalName)
2768 {
2769 case "Id":
2770 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
2771 break;
2772 case "Guid":
2773 componentId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
2774 break;
2775 case "Type":
2776 var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
2777 switch (typeValue)
2778 {
2779 case "directory":
2780 type = LocatorType.Directory;
2781 break;
2782 case "file":
2783 type = LocatorType.Filename;
2784 break;
2785 case "":
2786 break;
2787 default:
2788 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, typeValue, "directory", "file"));
2789 break;
2790 }
2791 break;
2792 default:
2793 this.Core.UnexpectedAttribute(node, attrib);
2794 break;
2795 }
2796 }
2797 else
2798 {
2799 this.Core.ParseExtensionAttribute(node, attrib);
2800 }
2801 }
2802
2803 if (null == id)
2804 {
2805 id = this.Core.CreateIdentifier("cmp", componentId, type.ToString());
2806 }
2807
2808 var signature = id.Id;
2809 var oneChild = false;
2810 foreach (var child in node.Elements())
2811 {
2812 if (CompilerCore.WixNamespace == child.Name.Namespace)
2813 {
2814 switch (child.Name.LocalName)
2815 {
2816 case "DirectorySearch":
2817 if (oneChild)
2818 {
2819 this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName));
2820 }
2821 oneChild = true;
2822
2823 // directorysearch parentage should work like directory element, not the rest of the signature type because of the DrLocator.Parent column
2824 signature = this.ParseDirectorySearchElement(child, id.Id);
2825 break;
2826 case "DirectorySearchRef":
2827 if (oneChild)
2828 {
2829 this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName));
2830 }
2831 oneChild = true;
2832 signature = this.ParseDirectorySearchRefElement(child, id.Id);
2833 break;
2834 case "FileSearch":
2835 if (oneChild)
2836 {
2837 this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName));
2838 }
2839 oneChild = true;
2840 signature = this.ParseFileSearchElement(child, id.Id, false, CompilerConstants.IntegerNotSet);
2841 id = new Identifier(AccessModifier.Section, signature); // FileSearch signatures override parent signatures
2842 break;
2843 case "FileSearchRef":
2844 if (oneChild)
2845 {
2846 this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName));
2847 }
2848 oneChild = true;
2849 var newId = this.ParseSimpleRefElement(child, SymbolDefinitions.Signature); // FileSearch signatures override parent signatures
2850 id = new Identifier(AccessModifier.Section, newId);
2851 signature = null;
2852 break;
2853 default:
2854 this.Core.UnexpectedElement(node, child);
2855 break;
2856 }
2857 }
2858 else
2859 {
2860 this.Core.ParseExtensionElement(node, child);
2861 }
2862 }
2863
2864 if (!this.Core.EncounteredError)
2865 {
2866 this.Core.AddSymbol(new CompLocatorSymbol(sourceLineNumbers, id)
2867 {
2868 SignatureRef = id.Id,
2869 ComponentId = componentId,
2870 Type = type,
2871 });
2872 }
2873
2874 return signature;
2875 }
2876
2877 /// <summary>
2878 /// Parses a create folder element.
2879 /// </summary>
2880 /// <param name="node">Element to parse.</param>
2881 /// <param name="componentId">Identifier for parent component.</param>
2882 /// <param name="directoryId">Default identifier for directory to create.</param>
2883 /// <param name="win64Component">true if the component is 64-bit.</param>
2884 /// <returns>Identifier for the directory that will be created</returns>
2885 private string ParseCreateFolderElement(XElement node, string componentId, string directoryId, bool win64Component)
2886 {
2887 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2888 string subdirectory = null;
2889
2890 foreach (var attrib in node.Attributes())
2891 {
2892 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
2893 {
2894 switch (attrib.Name.LocalName)
2895 {
2896 case "Directory":
2897 directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
2898 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId);
2899 break;
2900 case "Subdirectory":
2901 subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true);
2902 break;
2903 default:
2904 this.Core.UnexpectedAttribute(node, attrib);
2905 break;
2906 }
2907 }
2908 else
2909 {
2910 this.Core.ParseExtensionAttribute(node, attrib);
2911 }
2912 }
2913
2914 directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId, subdirectory, "Directory", "Subdirectory");
2915
2916 foreach (var child in node.Elements())
2917 {
2918 if (CompilerCore.WixNamespace == child.Name.Namespace)
2919 {
2920 switch (child.Name.LocalName)
2921 {
2922 case "Shortcut":
2923 this.ParseShortcutElement(child, componentId, node.Name.LocalName, directoryId, YesNoType.No);
2924 break;
2925 case "Permission":
2926 this.ParsePermissionElement(child, directoryId, "CreateFolder");
2927 break;
2928 case "PermissionEx":
2929 this.ParsePermissionExElement(child, directoryId, "CreateFolder");
2930 break;
2931 default:
2932 this.Core.UnexpectedElement(node, child);
2933 break;
2934 }
2935 }
2936 else
2937 {
2938 var context = new Dictionary<string, string>() { { "DirectoryId", directoryId }, { "ComponentId", componentId }, { "Win64", win64Component.ToString() } };
2939 this.Core.ParseExtensionElement(node, child, context);
2940 }
2941 }
2942
2943 if (!this.Core.EncounteredError)
2944 {
2945 this.Core.AddSymbol(new CreateFolderSymbol(sourceLineNumbers)
2946 {
2947 DirectoryRef = directoryId,
2948 ComponentRef = componentId,
2949 });
2950 }
2951
2952 return directoryId;
2953 }
2954
2955 /// <summary>
2956 /// Parses a copy file element.
2957 /// </summary>
2958 /// <param name="node">Element to parse.</param>
2959 /// <param name="componentId">Identifier of parent component.</param>
2960 /// <param name="fileId">Identifier of file to copy (null if moving the file).</param>
2961 private void ParseCopyFileElement(XElement node, string componentId, string fileId)
2962 {
2963 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2964 Identifier id = null;
2965 var delete = false;
2966 string destinationDirectory = null;
2967 string destinationSubdirectory = null;
2968 string destinationName = null;
2969 string destinationShortName = null;
2970 string destinationProperty = null;
2971 string sourceDirectory = null;
2972 string sourceSubdirectory = null;
2973 string sourceFolder = null;
2974 string sourceName = null;
2975 string sourceProperty = null;
2976
2977 foreach (var attrib in node.Attributes())
2978 {
2979 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
2980 {
2981 switch (attrib.Name.LocalName)
2982 {
2983 case "Id":
2984 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
2985 break;
2986 case "Delete":
2987 delete = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
2988 break;
2989 case "DestinationDirectory":
2990 destinationDirectory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
2991 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, destinationDirectory);
2992 break;
2993 case "DestinationSubdirectory":
2994 destinationSubdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true);
2995 break;
2996 case "DestinationName":
2997 destinationName = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib);
2998 break;
2999 case "DestinationProperty":
3000 destinationProperty = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3001 break;
3002 case "DestinationShortName":
3003 destinationShortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib);
3004 break;
3005 case "FileId":
3006 if (null != fileId)
3007 {
3008 this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, node.Parent.Name.LocalName));
3009 }
3010 fileId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3011 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, fileId);
3012 break;
3013 case "SourceDirectory":
3014 sourceDirectory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3015 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, sourceDirectory);
3016 break;
3017 case "SourceSubdirectory":
3018 sourceSubdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true);
3019 break;
3020 case "SourceName":
3021 sourceName = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3022 break;
3023 case "SourceProperty":
3024 sourceProperty = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3025 break;
3026 default:
3027 this.Core.UnexpectedAttribute(node, attrib);
3028 break;
3029 }
3030 }
3031 else
3032 {
3033 this.Core.ParseExtensionAttribute(node, attrib);
3034 }
3035 }
3036
3037 if (null != sourceFolder && null != sourceDirectory) // SourceFolder and SourceDirectory cannot coexist
3038 {
3039 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFolder", "SourceDirectory"));
3040 }
3041
3042 if (null != sourceFolder && null != sourceProperty) // SourceFolder and SourceProperty cannot coexist
3043 {
3044 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFolder", "SourceProperty"));
3045 }
3046
3047 if (null != sourceDirectory && null != sourceProperty) // SourceDirectory and SourceProperty cannot coexist
3048 {
3049 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceProperty", "SourceDirectory"));
3050 }
3051
3052 sourceDirectory = this.HandleSubdirectory(sourceLineNumbers, node, sourceDirectory, sourceSubdirectory, "SourceDirectory", "SourceSubdirectory");
3053
3054 if (null != destinationDirectory && null != destinationProperty) // DestinationDirectory and DestinationProperty cannot coexist
3055 {
3056 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DestinationProperty", "DestinationDirectory"));
3057 }
3058
3059 destinationDirectory = this.HandleSubdirectory(sourceLineNumbers, node, destinationDirectory, destinationSubdirectory, "DestinationDirectory", "DestinationSubdirectory");
3060
3061 if (null == id)
3062 {
3063 id = this.Core.CreateIdentifier("cf", sourceFolder, sourceDirectory, sourceProperty, destinationDirectory, destinationProperty, destinationName);
3064 }
3065
3066 this.Core.ParseForExtensionElements(node);
3067
3068 if (null == fileId)
3069 {
3070 // DestinationDirectory or DestinationProperty must be specified
3071 if (null == destinationDirectory && null == destinationProperty)
3072 {
3073 this.Core.Write(ErrorMessages.ExpectedAttributesWithoutOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DestinationDirectory", "DestinationProperty", "FileId"));
3074 }
3075
3076 if (!this.Core.EncounteredError)
3077 {
3078 this.Core.AddSymbol(new MoveFileSymbol(sourceLineNumbers, id)
3079 {
3080 ComponentRef = componentId,
3081 SourceName = sourceName,
3082 DestinationName = destinationName,
3083 DestinationShortName = destinationShortName,
3084 SourceFolder = sourceDirectory ?? sourceProperty,
3085 DestFolder = destinationDirectory ?? destinationProperty,
3086 Delete = delete,
3087 });
3088 }
3089 }
3090 else // copy the file
3091 {
3092 if (null != sourceDirectory)
3093 {
3094 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceDirectory", "FileId"));
3095 }
3096
3097 if (null != sourceFolder)
3098 {
3099 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFolder", "FileId"));
3100 }
3101
3102 if (null != sourceName)
3103 {
3104 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceName", "FileId"));
3105 }
3106
3107 if (null != sourceProperty)
3108 {
3109 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceProperty", "FileId"));
3110 }
3111
3112 if (delete)
3113 {
3114 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Delete", "FileId"));
3115 }
3116
3117 if (null == destinationName && null == destinationDirectory && null == destinationProperty)
3118 {
3119 this.Core.Write(WarningMessages.CopyFileFileIdUseless(sourceLineNumbers));
3120 }
3121
3122 if (!this.Core.EncounteredError)
3123 {
3124 this.Core.AddSymbol(new DuplicateFileSymbol(sourceLineNumbers, id)
3125 {
3126 ComponentRef = componentId,
3127 FileRef = fileId,
3128 DestinationName = destinationName,
3129 DestinationShortName = destinationShortName,
3130 DestinationFolder = destinationDirectory ?? destinationProperty,
3131 });
3132 }
3133 }
3134 }
3135
3136 /// <summary>
3137 /// Parses a CustomAction element.
3138 /// </summary>
3139 /// <param name="node">Element to parse.</param>
3140 private void ParseCustomActionElement(XElement node)
3141 {
3142 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
3143 Identifier id = null;
3144 var inlineScript = false;
3145 var suppressModularization = YesNoType.NotSet;
3146 string source = null;
3147 string target = null;
3148 var explicitWin64 = false;
3149
3150 string scriptFile = null;
3151 string subdirectory = null;
3152
3153 CustomActionSourceType? sourceType = null;
3154 CustomActionTargetType? targetType = null;
3155 var executionType = CustomActionExecutionType.Immediate;
3156 var hidden = false;
3157 var impersonate = true;
3158 var patchUninstall = false;
3159 var tsAware = false;
3160 var win64 = false;
3161 var async = false;
3162 var ignoreResult = false;
3163
3164 foreach (var attrib in node.Attributes())
3165 {
3166 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
3167 {
3168 switch (attrib.Name.LocalName)
3169 {
3170 case "Id":
3171 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
3172 break;
3173 case "BinaryRef":
3174 if (null != source)
3175 {
3176 this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryRef", "Directory", "FileRef", "Property", "Script"));
3177 }
3178 source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3179 sourceType = CustomActionSourceType.Binary;
3180 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Binary, source); // add a reference to the appropriate Binary
3181 break;
3182 case "Bitness":
3183 var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3184 switch (bitnessValue)
3185 {
3186 case "always32":
3187 explicitWin64 = true;
3188 win64 = false;
3189 break;
3190 case "always64":
3191 explicitWin64 = true;
3192 win64 = true;
3193 break;
3194 case "default":
3195 case "":
3196 break;
3197 default:
3198 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64"));
3199 break;
3200 }
3201 break;
3202 case "Directory":
3203 if (null != source)
3204 {
3205 this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryKey", "Directory", "FileRef", "Property", "Script"));
3206 }
3207 source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3208 sourceType = CustomActionSourceType.Directory;
3209 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, source);
3210 break;
3211 case "DllEntry":
3212 if (null != target)
3213 {
3214 this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall"));
3215 }
3216 target = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3217 targetType = CustomActionTargetType.Dll;
3218 break;
3219 case "Error":
3220 if (null != target)
3221 {
3222 this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall"));
3223 }
3224 target = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3225 sourceType = CustomActionSourceType.File;
3226 targetType = CustomActionTargetType.TextData;
3227
3228 // The target can be either a formatted error string or a literal
3229 // error number. Try to convert to error number to determine whether
3230 // to add a reference. No need to look at the value.
3231 if (Int32.TryParse(target, out var ignored))
3232 {
3233 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Error, target);
3234 }
3235 break;
3236 case "ExeCommand":
3237 if (null != target)
3238 {
3239 this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall"));
3240 }
3241 target = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); // one of the few cases where an empty string value is valid
3242 targetType = CustomActionTargetType.Exe;
3243 break;
3244 case "Execute":
3245 var execute = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3246 switch (execute)
3247 {
3248 case "commit":
3249 executionType = CustomActionExecutionType.Commit;
3250 break;
3251 case "deferred":
3252 executionType = CustomActionExecutionType.Deferred;
3253 break;
3254 case "firstSequence":
3255 executionType = CustomActionExecutionType.FirstSequence;
3256 break;
3257 case "immediate":
3258 executionType = CustomActionExecutionType.Immediate;
3259 break;
3260 case "oncePerProcess":
3261 executionType = CustomActionExecutionType.OncePerProcess;
3262 break;
3263 case "rollback":
3264 executionType = CustomActionExecutionType.Rollback;
3265 break;
3266 case "secondSequence":
3267 executionType = CustomActionExecutionType.ClientRepeat;
3268 break;
3269 default:
3270 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, execute, "commit", "deferred", "firstSequence", "immediate", "oncePerProcess", "rollback", "secondSequence"));
3271 break;
3272 }
3273 break;
3274 case "FileRef":
3275 if (null != source)
3276 {
3277 this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryRef", "Directory", "FileRef", "Property", "Script"));
3278 }
3279 source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3280 sourceType = CustomActionSourceType.File;
3281 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, source); // add a reference to the appropriate File
3282 break;
3283 case "HideTarget":
3284 hidden = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
3285 break;
3286 case "Impersonate":
3287 impersonate = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
3288 break;
3289 case "JScriptCall":
3290 if (null != target)
3291 {
3292 this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall"));
3293 }
3294 target = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); // one of the few cases where an empty string value is valid
3295 targetType = CustomActionTargetType.JScript;
3296 break;
3297 case "PatchUninstall":
3298 patchUninstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
3299 break;
3300 case "Property":
3301 if (null != source)
3302 {
3303 this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryRef", "Directory", "FileRef", "Property", "Script"));
3304 }
3305 source = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3306 sourceType = CustomActionSourceType.Property;
3307 break;
3308 case "Return":
3309 var returnValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3310 switch (returnValue)
3311 {
3312 case "asyncNoWait":
3313 async = true;
3314 ignoreResult = true;
3315 break;
3316 case "asyncWait":
3317 async = true;
3318 break;
3319 case "check":
3320 break;
3321 case "ignore":
3322 ignoreResult = true;
3323 break;
3324 case "":
3325 break;
3326 default:
3327 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, returnValue, "asyncNoWait", "asyncWait", "check", "ignore"));
3328 break;
3329 }
3330 break;
3331 case "Script":
3332 if (null != source)
3333 {
3334 this.Core.Write(ErrorMessages.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryRef", "Directory", "FileRef", "Property", "Script"));
3335 }
3336
3337 if (null != target)
3338 {
3339 this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall"));
3340 }
3341
3342 // set the source and target to empty string for error messages when the user sets multiple sources or targets
3343 source = String.Empty;
3344 target = String.Empty;
3345
3346 inlineScript = true;
3347
3348 var script = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3349 switch (script)
3350 {
3351 case "jscript":
3352 sourceType = CustomActionSourceType.Directory;
3353 targetType = CustomActionTargetType.JScript;
3354 break;
3355 case "vbscript":
3356 sourceType = CustomActionSourceType.Directory;
3357 targetType = CustomActionTargetType.VBScript;
3358 break;
3359 case "":
3360 break;
3361 default:
3362 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, script, "jscript", "vbscript"));
3363 break;
3364 }
3365 break;
3366 case "ScriptSourceFile":
3367 scriptFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3368 break;
3369 case "Subdirectory":
3370 subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true);
3371 break;
3372 case "SuppressModularization":
3373 suppressModularization = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
3374 break;
3375 case "TerminalServerAware":
3376 tsAware = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
3377 break;
3378 case "Value":
3379 if (null != target)
3380 {
3381 this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall"));
3382 }
3383 target = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); // one of the few cases where an empty string value is valid
3384 targetType = CustomActionTargetType.TextData;
3385 break;
3386 case "VBScriptCall":
3387 if (null != target)
3388 {
3389 this.Core.Write(ErrorMessages.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall"));
3390 }
3391 target = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); // one of the few cases where an empty string value is valid
3392 targetType = CustomActionTargetType.VBScript;
3393 break;
3394 default:
3395 this.Core.UnexpectedAttribute(node, attrib);
3396 break;
3397 }
3398 }
3399 else
3400 {
3401 this.Core.ParseExtensionAttribute(node, attrib);
3402 }
3403 }
3404
3405 if (null == id)
3406 {
3407 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
3408 id = Identifier.Invalid;
3409 }
3410
3411 if (!explicitWin64 && this.Context.IsCurrentPlatform64Bit && (CustomActionTargetType.VBScript == targetType || CustomActionTargetType.JScript == targetType))
3412 {
3413 win64 = true;
3414 }
3415
3416 if (!String.IsNullOrEmpty(subdirectory))
3417 {
3418 if (sourceType == CustomActionSourceType.Directory)
3419 {
3420 source = this.HandleSubdirectory(sourceLineNumbers, node, source, subdirectory, "Directory", "Subdirectory");
3421 }
3422 else
3423 {
3424 this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "Subdirectory", "Directory"));
3425 }
3426 }
3427
3428 // if we have an in-lined Script CustomAction ensure no source or target attributes were provided
3429 if (inlineScript)
3430 {
3431 if (String.IsNullOrEmpty(scriptFile))
3432 {
3433 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ScriptSourceFile", "Script"));
3434 }
3435 }
3436 else if (CustomActionTargetType.VBScript == targetType) // non-inline vbscript
3437 {
3438 if (null == source)
3439 {
3440 this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "VBScriptCall", "BinaryRef", "FileRef", "Property"));
3441 }
3442 else if (CustomActionSourceType.Directory == sourceType)
3443 {
3444 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "VBScriptCall", "Directory"));
3445 }
3446 }
3447 else if (CustomActionTargetType.JScript == targetType) // non-inline jscript
3448 {
3449 if (null == source)
3450 {
3451 this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "JScriptCall", "BinaryRef", "FileRef", "Property"));
3452 }
3453 else if (CustomActionSourceType.Directory == sourceType)
3454 {
3455 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "JScriptCall", "Directory"));
3456 }
3457 }
3458 else if (CustomActionTargetType.Exe == targetType) // exe-command
3459 {
3460 if (null == source)
3461 {
3462 this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "ExeCommand", "BinaryRef", "Directory", "FileRef", "Property"));
3463 }
3464 }
3465 else if (CustomActionTargetType.TextData == targetType && CustomActionSourceType.Directory != sourceType && CustomActionSourceType.Property != sourceType && CustomActionSourceType.File != sourceType)
3466 {
3467 this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "Value", "Directory", "Property", "Error"));
3468 }
3469
3470 if (!inlineScript && !String.IsNullOrEmpty(scriptFile))
3471 {
3472 this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "ScriptSourceFile", "Script"));
3473 }
3474
3475 if (win64 && CustomActionTargetType.VBScript != targetType && CustomActionTargetType.JScript != targetType)
3476 {
3477 this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "Win64", "Script", "VBScriptCall", "JScriptCall"));
3478 }
3479
3480 if (async && ignoreResult && CustomActionTargetType.Exe != targetType)
3481 {
3482 this.Core.Write(ErrorMessages.IllegalAttributeValueWithoutOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Return", "asyncNoWait", "ExeCommand"));
3483 }
3484
3485 // TS-aware CAs are valid only when deferred.
3486 if (tsAware &
3487 CustomActionExecutionType.Deferred != executionType &&
3488 CustomActionExecutionType.Rollback != executionType &&
3489 CustomActionExecutionType.Commit != executionType)
3490 {
3491 this.Core.Write(ErrorMessages.IllegalTerminalServerCustomActionAttributes(sourceLineNumbers));
3492 }
3493
3494 // MSI doesn't support in-script property setting, so disallow it
3495 if (CustomActionSourceType.Property == sourceType &&
3496 CustomActionTargetType.TextData == targetType &&
3497 (CustomActionExecutionType.Deferred == executionType ||
3498 CustomActionExecutionType.Rollback == executionType ||
3499 CustomActionExecutionType.Commit == executionType))
3500 {
3501 this.Core.Write(ErrorMessages.IllegalPropertyCustomActionAttributes(sourceLineNumbers));
3502 }
3503
3504 if (!targetType.HasValue /*0 == targetBits*/)
3505 {
3506 this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall"));
3507 }
3508
3509 this.Core.ParseForExtensionElements(node);
3510
3511 if (!this.Core.EncounteredError)
3512 {
3513 this.Core.AddSymbol(new CustomActionSymbol(sourceLineNumbers, id)
3514 {
3515 ExecutionType = executionType,
3516 Source = source,
3517 SourceType = sourceType.Value,
3518 Target = target,
3519 TargetType = targetType.Value,
3520 Async = async,
3521 IgnoreResult = ignoreResult,
3522 Impersonate = impersonate,
3523 PatchUninstall = patchUninstall,
3524 TSAware = tsAware,
3525 Win64 = win64,
3526 Hidden = hidden,
3527 ScriptFile = new IntermediateFieldPathValue { Path = scriptFile }
3528 });
3529
3530 if (YesNoType.Yes == suppressModularization)
3531 {
3532 this.Core.AddSymbol(new WixSuppressModularizationSymbol(sourceLineNumbers)
3533 {
3534 SuppressIdentifier = id.Id
3535 });
3536 }
3537 }
3538 }
3539
3540 /// <summary>
3541 /// Parses a simple reference element.
3542 /// </summary>
3543 /// <param name="node">Element to parse.</param>
3544 /// <param name="symbolDefinition">Symbol which contains the target of the simple reference.</param>
3545 /// <returns>Id of the referenced element.</returns>
3546 private string ParseSimpleRefElement(XElement node, IntermediateSymbolDefinition symbolDefinition)
3547 {
3548 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
3549 string id = null;
3550
3551 foreach (var attrib in node.Attributes())
3552 {
3553 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
3554 {
3555 switch (attrib.Name.LocalName)
3556 {
3557 case "Id":
3558 id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3559 this.Core.CreateSimpleReference(sourceLineNumbers, symbolDefinition.Name, id);
3560 break;
3561 default:
3562 this.Core.UnexpectedAttribute(node, attrib);
3563 break;
3564 }
3565 }
3566 else
3567 {
3568 this.Core.ParseExtensionAttribute(node, attrib);
3569 }
3570 }
3571
3572 if (null == id)
3573 {
3574 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
3575 }
3576
3577 this.Core.ParseForExtensionElements(node);
3578
3579 return id;
3580 }
3581
3582 /// <summary>
3583 /// Parses a PatchFamilyRef element.
3584 /// </summary>
3585 /// <param name="node">Element to parse.</param>
3586 /// <param name="parentType">The parent type.</param>
3587 /// <param name="parentId">The ID of the parent.</param>
3588 /// <returns>Id of the referenced element.</returns>
3589 private void ParsePatchFamilyRefElement(XElement node, ComplexReferenceParentType parentType, string parentId)
3590 {
3591 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
3592 var primaryKeys = new string[2];
3593
3594 foreach (var attrib in node.Attributes())
3595 {
3596 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
3597 {
3598 switch (attrib.Name.LocalName)
3599 {
3600 case "Id":
3601 primaryKeys[0] = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3602 break;
3603 case "ProductCode":
3604 primaryKeys[1] = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3605 break;
3606 default:
3607 this.Core.UnexpectedAttribute(node, attrib);
3608 break;
3609 }
3610 }
3611 else
3612 {
3613 this.Core.ParseExtensionAttribute(node, attrib);
3614 }
3615 }
3616
3617 if (null == primaryKeys[0])
3618 {
3619 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
3620 }
3621
3622 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.MsiPatchSequence, primaryKeys);
3623
3624 this.Core.ParseForExtensionElements(node);
3625
3626 if (!this.Core.EncounteredError)
3627 {
3628 this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.PatchFamily, primaryKeys[0], true);
3629 }
3630 }
3631
3632 /// <summary>
3633 /// Parses an ensure table element.
3634 /// </summary>
3635 /// <param name="node">Element to parse.</param>
3636 private void ParseEnsureTableElement(XElement node)
3637 {
3638 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
3639 string id = null;
3640
3641 foreach (var attrib in node.Attributes())
3642 {
3643 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
3644 {
3645 switch (attrib.Name.LocalName)
3646 {
3647 case "Id":
3648 id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3649 break;
3650 default:
3651 this.Core.UnexpectedAttribute(node, attrib);
3652 break;
3653 }
3654 }
3655 else
3656 {
3657 this.Core.ParseExtensionAttribute(node, attrib);
3658 }
3659 }
3660
3661 if (null == id)
3662 {
3663 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
3664 }
3665 else if (31 < id.Length)
3666 {
3667 this.Core.Write(ErrorMessages.TableNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", id));
3668 }
3669
3670 this.Core.ParseForExtensionElements(node);
3671
3672 this.Core.EnsureTable(sourceLineNumbers, id);
3673 }
3674
3675 /// <summary>
3676 /// Parses a custom table element.
3677 /// </summary>
3678 /// <param name="node">Element to parse.</param>
3679 /// <remarks>not cleaned</remarks>
3680 private void ParseCustomTableElement(XElement node)
3681 {
3682 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
3683 string tableId = null;
3684 var unreal = false;
3685 var columns = new List<WixCustomTableColumnSymbol>();
3686 var foundColumns = false;
3687
3688 foreach (var attrib in node.Attributes())
3689 {
3690 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
3691 {
3692 switch (attrib.Name.LocalName)
3693 {
3694 case "Id":
3695 tableId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3696 break;
3697 case "Unreal":
3698 unreal = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
3699 break;
3700 default:
3701 this.Core.UnexpectedAttribute(node, attrib);
3702 break;
3703 }
3704 }
3705 else
3706 {
3707 this.Core.ParseExtensionAttribute(node, attrib);
3708 }
3709 }
3710
3711 if (null == tableId)
3712 {
3713 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
3714 }
3715 else if (31 < tableId.Length)
3716 {
3717 this.Core.Write(ErrorMessages.CustomTableNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", tableId));
3718 }
3719
3720 foreach (var child in node.Elements())
3721 {
3722 if (CompilerCore.WixNamespace == child.Name.Namespace)
3723 {
3724 var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
3725 switch (child.Name.LocalName)
3726 {
3727 case "Column":
3728 foundColumns = true;
3729
3730 var column = this.ParseColumnElement(child, childSourceLineNumbers, tableId);
3731 if (column != null)
3732 {
3733 columns.Add(column);
3734 }
3735 break;
3736 case "Row":
3737 this.ParseRowElement(child, childSourceLineNumbers, tableId);
3738 break;
3739 default:
3740 this.Core.UnexpectedElement(node, child);
3741 break;
3742 }
3743 }
3744 else
3745 {
3746 this.Core.ParseExtensionElement(node, child);
3747 }
3748 }
3749
3750 if (columns.Count > 0)
3751 {
3752 if (!columns.Where(c => c.PrimaryKey).Any())
3753 {
3754 this.Core.Write(ErrorMessages.CustomTableMissingPrimaryKey(sourceLineNumbers));
3755 }
3756
3757 if (!this.Core.EncounteredError)
3758 {
3759 var columnNames = String.Join(new string(WixCustomTableSymbol.ColumnNamesSeparator, 1), columns.Select(c => c.Name));
3760
3761 this.Core.AddSymbol(new WixCustomTableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, tableId))
3762 {
3763 ColumnNames = columnNames,
3764 Unreal = unreal,
3765 });
3766 }
3767 else if (!foundColumns)
3768 {
3769 this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "Column"));
3770 }
3771 }
3772 }
3773
3774 /// <summary>
3775 /// Parses a CustomTableRef element.
3776 /// </summary>
3777 /// <param name="node">Element to parse.</param>
3778 private void ParseCustomTableRefElement(XElement node)
3779 {
3780 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
3781 string tableId = null;
3782
3783 foreach (var attrib in node.Attributes())
3784 {
3785 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
3786 {
3787 switch (attrib.Name.LocalName)
3788 {
3789 case "Id":
3790 tableId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3791 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixCustomTable, tableId);
3792 break;
3793 default:
3794 this.Core.UnexpectedAttribute(node, attrib);
3795 break;
3796 }
3797 }
3798 else
3799 {
3800 this.Core.ParseExtensionAttribute(node, attrib);
3801 }
3802 }
3803
3804 if (null == tableId)
3805 {
3806 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
3807 }
3808
3809 foreach (var child in node.Elements())
3810 {
3811 if (CompilerCore.WixNamespace == child.Name.Namespace)
3812 {
3813 var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
3814 switch (child.Name.LocalName)
3815 {
3816 case "Row":
3817 this.ParseRowElement(child, childSourceLineNumbers, tableId);
3818 break;
3819 default:
3820 this.Core.UnexpectedElement(node, child);
3821 break;
3822 }
3823 }
3824 else
3825 {
3826 this.Core.ParseExtensionElement(node, child);
3827 }
3828 }
3829 }
3830
3831 /// <summary>
3832 /// Parses a Column element.
3833 /// </summary>
3834 /// <param name="child">Element to parse.</param>
3835 /// <param name="childSourceLineNumbers">Element's SourceLineNumbers.</param>
3836 /// <param name="tableId">Table Id.</param>
3837 private WixCustomTableColumnSymbol ParseColumnElement(XElement child, SourceLineNumber childSourceLineNumbers, string tableId)
3838 {
3839 string columnName = null;
3840 IntermediateFieldType? columnType = null;
3841 var description = String.Empty;
3842 int? keyColumn = null;
3843 var keyTable = String.Empty;
3844 var localizable = false;
3845 long? maxValue = null;
3846 long? minValue = null;
3847 WixCustomTableColumnCategoryType? category = null;
3848 var modularization = WixCustomTableColumnModularizeType.None;
3849 var nullable = false;
3850 var primaryKey = false;
3851 var setValues = String.Empty;
3852 var columnUnreal = false;
3853 var width = 0;
3854
3855 foreach (var childAttrib in child.Attributes())
3856 {
3857 switch (childAttrib.Name.LocalName)
3858 {
3859 case "Id":
3860 columnName = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, childAttrib);
3861 break;
3862 case "Category":
3863 var categoryValue = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib);
3864 switch (categoryValue)
3865 {
3866 case "text":
3867 category = WixCustomTableColumnCategoryType.Text;
3868 break;
3869 case "upperCase":
3870 category = WixCustomTableColumnCategoryType.UpperCase;
3871 break;
3872 case "lowerCase":
3873 category = WixCustomTableColumnCategoryType.LowerCase;
3874 break;
3875 case "integer":
3876 category = WixCustomTableColumnCategoryType.Integer;
3877 break;
3878 case "doubleInteger":
3879 category = WixCustomTableColumnCategoryType.DoubleInteger;
3880 break;
3881 case "timeDate":
3882 category = WixCustomTableColumnCategoryType.TimeDate;
3883 break;
3884 case "identifier":
3885 category = WixCustomTableColumnCategoryType.Identifier;
3886 break;
3887 case "property":
3888 category = WixCustomTableColumnCategoryType.Property;
3889 break;
3890 case "filename":
3891 category = WixCustomTableColumnCategoryType.Filename;
3892 break;
3893 case "wildCardFilename":
3894 category = WixCustomTableColumnCategoryType.WildCardFilename;
3895 break;
3896 case "path":
3897 category = WixCustomTableColumnCategoryType.Path;
3898 break;
3899 case "paths":
3900 category = WixCustomTableColumnCategoryType.Paths;
3901 break;
3902 case "anyPath":
3903 category = WixCustomTableColumnCategoryType.AnyPath;
3904 break;
3905 case "defaultDir":
3906 category = WixCustomTableColumnCategoryType.DefaultDir;
3907 break;
3908 case "regPath":
3909 category = WixCustomTableColumnCategoryType.RegPath;
3910 break;
3911 case "formatted":
3912 category = WixCustomTableColumnCategoryType.Formatted;
3913 break;
3914 case "formattedSddl":
3915 category = WixCustomTableColumnCategoryType.FormattedSddl;
3916 break;
3917 case "template":
3918 category = WixCustomTableColumnCategoryType.Template;
3919 break;
3920 case "condition":
3921 category = WixCustomTableColumnCategoryType.Condition;
3922 break;
3923 case "guid":
3924 category = WixCustomTableColumnCategoryType.Guid;
3925 break;
3926 case "version":
3927 category = WixCustomTableColumnCategoryType.Version;
3928 break;
3929 case "language":
3930 category = WixCustomTableColumnCategoryType.Language;
3931 break;
3932 case "binary":
3933 category = WixCustomTableColumnCategoryType.Binary;
3934 break;
3935 case "customSource":
3936 category = WixCustomTableColumnCategoryType.CustomSource;
3937 break;
3938 case "cabinet":
3939 category = WixCustomTableColumnCategoryType.Cabinet;
3940 break;
3941 case "shortcut":
3942 category = WixCustomTableColumnCategoryType.Shortcut;
3943 break;
3944 case "":
3945 break;
3946 default:
3947 this.Core.Write(ErrorMessages.IllegalAttributeValue(childSourceLineNumbers, child.Name.LocalName, "Category", categoryValue,
3948 "text", "upperCase", "lowerCase", "integer", "doubleInteger", "timeDate", "identifier", "property", "filename",
3949 "wildCardFilename", "path", "paths", "anyPath", "defaultDir", "regPath", "formatted", "formattedSddl", "template",
3950 "condition", "guid", "version", "language", "binary", "customSource", "cabinet", "shortcut"));
3951 columnType = IntermediateFieldType.String; // set a value to prevent expected attribute error below.
3952 break;
3953 }
3954 break;
3955 case "Description":
3956 description = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib);
3957 break;
3958 case "KeyColumn":
3959 keyColumn = this.Core.GetAttributeIntegerValue(childSourceLineNumbers, childAttrib, 1, 32);
3960 break;
3961 case "KeyTable":
3962 keyTable = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib);
3963 break;
3964 case "Localizable":
3965 localizable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib);
3966 break;
3967 case "MaxValue":
3968 maxValue = this.Core.GetAttributeLongValue(childSourceLineNumbers, childAttrib, Int32.MinValue + 1, Int32.MaxValue);
3969 break;
3970 case "MinValue":
3971 minValue = this.Core.GetAttributeLongValue(childSourceLineNumbers, childAttrib, Int32.MinValue + 1, Int32.MaxValue);
3972 break;
3973 case "Modularize":
3974 var modularizeValue = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib);
3975 switch (modularizeValue)
3976 {
3977 case "column":
3978 modularization = WixCustomTableColumnModularizeType.Column;
3979 break;
3980 case "companionFile":
3981 modularization = WixCustomTableColumnModularizeType.CompanionFile;
3982 break;
3983 case "condition":
3984 modularization = WixCustomTableColumnModularizeType.Condition;
3985 break;
3986 case "controlEventArgument":
3987 modularization = WixCustomTableColumnModularizeType.ControlEventArgument;
3988 break;
3989 case "controlText":
3990 modularization = WixCustomTableColumnModularizeType.ControlText;
3991 break;
3992 case "icon":
3993 modularization = WixCustomTableColumnModularizeType.Icon;
3994 break;
3995 case "none":
3996 modularization = WixCustomTableColumnModularizeType.None;
3997 break;
3998 case "property":
3999 modularization = WixCustomTableColumnModularizeType.Property;
4000 break;
4001 case "semicolonDelimited":
4002 modularization = WixCustomTableColumnModularizeType.SemicolonDelimited;
4003 break;
4004 case "":
4005 break;
4006 default:
4007 this.Core.Write(ErrorMessages.IllegalAttributeValue(childSourceLineNumbers, child.Name.LocalName, "Modularize", modularizeValue, "column", "companionFile", "condition", "controlEventArgument", "controlText", "icon", "property", "semicolonDelimited"));
4008 columnType = IntermediateFieldType.String; // set a value to prevent expected attribute error below.
4009 break;
4010 }
4011 break;
4012 case "Nullable":
4013 nullable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib);
4014 break;
4015 case "PrimaryKey":
4016 primaryKey = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib);
4017 break;
4018 case "Set":
4019 setValues = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib);
4020 break;
4021 case "Type":
4022 var typeValue = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib);
4023 switch (typeValue)
4024 {
4025 case "binary":
4026 columnType = IntermediateFieldType.Path;
4027 break;
4028 case "int":
4029 columnType = IntermediateFieldType.Number;
4030 break;
4031 case "string":
4032 columnType = IntermediateFieldType.String;
4033 break;
4034 case "":
4035 break;
4036 default:
4037 this.Core.Write(ErrorMessages.IllegalAttributeValue(childSourceLineNumbers, child.Name.LocalName, "Type", typeValue, "binary", "int", "string"));
4038 columnType = IntermediateFieldType.String; // set a value to prevent expected attribute error below.
4039 break;
4040 }
4041 break;
4042 case "Width":
4043 width = this.Core.GetAttributeIntegerValue(childSourceLineNumbers, childAttrib, 0, Int32.MaxValue);
4044 break;
4045 case "Unreal":
4046 columnUnreal = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib);
4047 break;
4048 default:
4049 this.Core.UnexpectedAttribute(child, childAttrib);
4050 break;
4051 }
4052 }
4053
4054 if (null == columnName)
4055 {
4056 this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Id"));
4057 }
4058
4059 if (!columnType.HasValue)
4060 {
4061 this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Type"));
4062 }
4063 else if (columnType == IntermediateFieldType.Number)
4064 {
4065 if (2 != width && 4 != width)
4066 {
4067 this.Core.Write(ErrorMessages.CustomTableIllegalColumnWidth(childSourceLineNumbers, child.Name.LocalName, "Width", width));
4068 }
4069 }
4070 else if (columnType == IntermediateFieldType.Path)
4071 {
4072 if (!category.HasValue)
4073 {
4074 category = WixCustomTableColumnCategoryType.Binary;
4075 }
4076 else if (category != WixCustomTableColumnCategoryType.Binary)
4077 {
4078 this.Core.Write(ErrorMessages.ExpectedBinaryCategory(childSourceLineNumbers));
4079 }
4080 }
4081
4082 this.Core.ParseForExtensionElements(child);
4083
4084 if (this.Core.EncounteredError)
4085 {
4086 return null;
4087 }
4088
4089 var attributes = primaryKey ? WixCustomTableColumnSymbolAttributes.PrimaryKey : WixCustomTableColumnSymbolAttributes.None;
4090 attributes |= localizable ? WixCustomTableColumnSymbolAttributes.Localizable : WixCustomTableColumnSymbolAttributes.None;
4091 attributes |= nullable ? WixCustomTableColumnSymbolAttributes.Nullable : WixCustomTableColumnSymbolAttributes.None;
4092 attributes |= columnUnreal ? WixCustomTableColumnSymbolAttributes.Unreal : WixCustomTableColumnSymbolAttributes.None;
4093
4094 var column = this.Core.AddSymbol(new WixCustomTableColumnSymbol(childSourceLineNumbers, new Identifier(AccessModifier.Section, tableId, columnName))
4095 {
4096 TableRef = tableId,
4097 Name = columnName,
4098 Type = columnType.Value,
4099 Attributes = attributes,
4100 Width = width,
4101 Category = category,
4102 Description = description,
4103 KeyColumn = keyColumn,
4104 KeyTable = keyTable,
4105 MaxValue = maxValue,
4106 MinValue = minValue,
4107 Modularize = modularization,
4108 Set = setValues,
4109 });
4110 return column;
4111 }
4112
4113 /// <summary>
4114 /// Parses a Row element.
4115 /// </summary>
4116 /// <param name="node">Element to parse.</param>
4117 /// <param name="sourceLineNumbers">Element's SourceLineNumbers.</param>
4118 /// <param name="tableId">Table Id.</param>
4119 private void ParseRowElement(XElement node, SourceLineNumber sourceLineNumbers, string tableId)
4120 {
4121 var rowId = Guid.NewGuid().ToString("N").ToUpperInvariant();
4122
4123 foreach (var attrib in node.Attributes())
4124 {
4125 this.Core.ParseExtensionAttribute(node, attrib);
4126 }
4127
4128 foreach (var child in node.Elements())
4129 {
4130 var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
4131 switch (child.Name.LocalName)
4132 {
4133 case "Data":
4134 string columnName = null;
4135 string data = null;
4136 foreach (var attrib in child.Attributes())
4137 {
4138 switch (attrib.Name.LocalName)
4139 {
4140 case "Column":
4141 columnName = this.Core.GetAttributeValue(childSourceLineNumbers, attrib);
4142 break;
4143 case "Value":
4144 data = this.Core.GetAttributeValue(childSourceLineNumbers, attrib);
4145 break;
4146 default:
4147 this.Core.ParseExtensionAttribute(child, attrib);
4148 break;
4149 }
4150 }
4151
4152 this.Core.InnerTextDisallowed(node);
4153
4154 if (null == columnName)
4155 {
4156 this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Column"));
4157 }
4158
4159 if (!this.Core.EncounteredError)
4160 {
4161 this.Core.AddSymbol(new WixCustomTableCellSymbol(childSourceLineNumbers, new Identifier(AccessModifier.Section, tableId, rowId, columnName))
4162 {
4163 RowId = rowId,
4164 ColumnRef = columnName,
4165 TableRef = tableId,
4166 Data = data
4167 });
4168 }
4169 break;
4170 default:
4171 this.Core.UnexpectedElement(node, child);
4172 break;
4173 }
4174 }
4175
4176 if (!this.Core.EncounteredError)
4177 {
4178 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixCustomTable, tableId);
4179 }
4180 }
4181
4182 /// <summary>
4183 /// Parses a directory element.
4184 /// </summary>
4185 /// <param name="node">Element to parse.</param>
4186 /// <param name="parentId">Optional identifier of parent directory.</param>
4187 /// <param name="diskId">Disk id inherited from parent directory.</param>
4188 /// <param name="fileSource">Path to source file as of yet.</param>
4189 private void ParseDirectoryElement(XElement node, string parentId, int diskId, string fileSource)
4190 {
4191 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
4192 Identifier id = null;
4193 string componentGuidGenerationSeed = null;
4194 var fileSourceAttribSet = false;
4195 XAttribute nameAttribute = null;
4196 var name = "."; // default to parent directory.
4197 string shortName = null;
4198 string sourceName = null;
4199 string shortSourceName = null;
4200 string symbols = null;
4201
4202 foreach (var attrib in node.Attributes())
4203 {
4204 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
4205 {
4206 switch (attrib.Name.LocalName)
4207 {
4208 case "Id":
4209 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
4210 break;
4211 case "ComponentGuidGenerationSeed":
4212 componentGuidGenerationSeed = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
4213 break;
4214 case "DiskId":
4215 diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue);
4216 break;
4217 case "FileSource":
4218 fileSource = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4219 fileSourceAttribSet = true;
4220 break;
4221 case "Name":
4222 if ("." == attrib.Value)
4223 {
4224 name = attrib.Value;
4225 }
4226 else
4227 {
4228 name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true);
4229 }
4230 nameAttribute = attrib;
4231 break;
4232 case "ShortName":
4233 shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false);
4234 break;
4235 case "ShortSourceName":
4236 shortSourceName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false);
4237 break;
4238 case "SourceName":
4239 if ("." == attrib.Value)
4240 {
4241 sourceName = attrib.Value;
4242 }
4243 else
4244 {
4245 sourceName = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false);
4246 }
4247 break;
4248 default:
4249 this.Core.UnexpectedAttribute(node, attrib);
4250 break;
4251 }
4252 }
4253 else
4254 {
4255 this.Core.ParseExtensionAttribute(node, attrib);
4256 }
4257 }
4258
4259 if (nameAttribute == null)
4260 {
4261 if (!String.IsNullOrEmpty(shortName))
4262 {
4263 this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "ShortName", "Name"));
4264 }
4265 }
4266 else if (!String.IsNullOrEmpty(name))
4267 {
4268 if (String.IsNullOrEmpty(shortName))
4269 {
4270 }
4271 else if (name == ".")
4272 {
4273 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "ShortName", "Name", name));
4274 }
4275 else if (name.Equals(shortName, StringComparison.OrdinalIgnoreCase))
4276 {
4277 this.Core.Write(WarningMessages.DirectoryRedundantNames(sourceLineNumbers, node.Name.LocalName, "Name", "ShortName", name));
4278 }
4279 }
4280
4281 if (String.IsNullOrEmpty(sourceName))
4282 {
4283 if (!String.IsNullOrEmpty(shortSourceName))
4284 {
4285 this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "ShortSourceName", "SourceName"));
4286 }
4287 }
4288 else
4289 {
4290 if (String.IsNullOrEmpty(shortSourceName))
4291 {
4292 }
4293 else if (sourceName == ".")
4294 {
4295 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "ShortSourceName", "SourceName", sourceName));
4296 }
4297 else if (sourceName.Equals(shortSourceName, StringComparison.OrdinalIgnoreCase))
4298 {
4299 this.Core.Write(WarningMessages.DirectoryRedundantNames(sourceLineNumbers, node.Name.LocalName, "SourceName", "ShortSourceName", sourceName));
4300 }
4301 }
4302
4303 if (null == id)
4304 {
4305 id = this.Core.CreateIdentifier("d", parentId, name, shortName, sourceName, shortSourceName);
4306 }
4307 else if (WindowsInstallerStandard.IsStandardDirectory(id.Id))
4308 {
4309 if (String.IsNullOrEmpty(sourceName))
4310 {
4311 this.Core.Write(CompilerWarnings.DefiningStandardDirectoryDeprecated(sourceLineNumbers, id.Id));
4312 }
4313
4314 if (id.Id == "TARGETDIR" && name != "SourceDir" && shortName == null && shortSourceName == null && sourceName == null)
4315 {
4316 this.Core.Write(ErrorMessages.IllegalTargetDirDefaultDir(sourceLineNumbers, name));
4317 }
4318 }
4319
4320 // Update the file source path appropriately.
4321 if (fileSourceAttribSet)
4322 {
4323 if (!fileSource.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal))
4324 {
4325 fileSource = String.Concat(fileSource, Path.DirectorySeparatorChar);
4326 }
4327 }
4328 else // add the appropriate part of this directory element to the file source.
4329 {
4330 string append = String.IsNullOrEmpty(sourceName) ? name : sourceName;
4331
4332 if (!String.IsNullOrEmpty(append))
4333 {
4334 fileSource = String.Concat(fileSource, append, Path.DirectorySeparatorChar);
4335 }
4336 }
4337
4338 foreach (var child in node.Elements())
4339 {
4340 if (CompilerCore.WixNamespace == child.Name.Namespace)
4341 {
4342 switch (child.Name.LocalName)
4343 {
4344 case "Component":
4345 this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, diskId, id.Id, fileSource);
4346 break;
4347 case "Directory":
4348 this.ParseDirectoryElement(child, id.Id, diskId, fileSource);
4349 break;
4350 case "Merge":
4351 this.ParseMergeElement(child, id.Id, diskId);
4352 break;
4353 case "SymbolPath":
4354 if (null != symbols)
4355 {
4356 symbols += ";" + this.ParseSymbolPathElement(child);
4357 }
4358 else
4359 {
4360 symbols = this.ParseSymbolPathElement(child);
4361 }
4362 break;
4363 default:
4364 this.Core.UnexpectedElement(node, child);
4365 break;
4366 }
4367 }
4368 else
4369 {
4370 this.Core.ParseExtensionElement(node, child);
4371 }
4372 }
4373
4374 if (!this.Core.EncounteredError)
4375 {
4376 this.Core.AddSymbol(new DirectorySymbol(sourceLineNumbers, id)
4377 {
4378 ParentDirectoryRef = parentId,
4379 Name = name,
4380 ShortName = shortName,
4381 SourceName = sourceName,
4382 SourceShortName = shortSourceName,
4383 ComponentGuidGenerationSeed = componentGuidGenerationSeed
4384 });
4385
4386 if (null != symbols)
4387 {
4388 this.Core.AddSymbol(new WixDeltaPatchSymbolPathsSymbol(sourceLineNumbers, id)
4389 {
4390 SymbolType = SymbolPathType.Directory,
4391 SymbolId = id.Id,
4392 SymbolPaths = symbols,
4393 });
4394 }
4395 }
4396 }
4397
4398 /// <summary>
4399 /// Parses a directory reference element.
4400 /// </summary>
4401 /// <param name="node">Element to parse.</param>
4402 private void ParseDirectoryRefElement(XElement node)
4403 {
4404 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
4405 string id = null;
4406 var diskId = CompilerConstants.IntegerNotSet;
4407 var fileSource = String.Empty;
4408
4409 foreach (var attrib in node.Attributes())
4410 {
4411 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
4412 {
4413 switch (attrib.Name.LocalName)
4414 {
4415 case "Id":
4416 id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
4417 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, id);
4418 break;
4419 case "DiskId":
4420 diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue);
4421 break;
4422 case "FileSource":
4423 fileSource = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4424 break;
4425 default:
4426 this.Core.UnexpectedAttribute(node, attrib);
4427 break;
4428 }
4429 }
4430 else
4431 {
4432 this.Core.ParseExtensionAttribute(node, attrib);
4433 }
4434 }
4435
4436 if (null == id)
4437 {
4438 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
4439 }
4440 else if (WindowsInstallerStandard.IsStandardDirectory(id))
4441 {
4442 this.Core.Write(CompilerWarnings.DirectoryRefStandardDirectoryDeprecated(sourceLineNumbers, id));
4443 }
4444
4445 if (!String.IsNullOrEmpty(fileSource) && !fileSource.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal))
4446 {
4447 fileSource = String.Concat(fileSource, Path.DirectorySeparatorChar);
4448 }
4449
4450 foreach (var child in node.Elements())
4451 {
4452 if (CompilerCore.WixNamespace == child.Name.Namespace)
4453 {
4454 switch (child.Name.LocalName)
4455 {
4456 case "Component":
4457 this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, diskId, id, fileSource);
4458 break;
4459 case "Directory":
4460 this.ParseDirectoryElement(child, id, diskId, fileSource);
4461 break;
4462 case "Merge":
4463 this.ParseMergeElement(child, id, diskId);
4464 break;
4465 default:
4466 this.Core.UnexpectedElement(node, child);
4467 break;
4468 }
4469 }
4470 else
4471 {
4472 this.Core.ParseExtensionElement(node, child);
4473 }
4474 }
4475 }
4476
4477 /// <summary>
4478 /// Parses a directory search element.
4479 /// </summary>
4480 /// <param name="node">Element to parse.</param>
4481 /// <param name="parentSignature">Signature of parent search element.</param>
4482 /// <returns>Signature of search element.</returns>
4483 private string ParseDirectorySearchElement(XElement node, string parentSignature)
4484 {
4485 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
4486 Identifier id = null;
4487 var depth = CompilerConstants.IntegerNotSet;
4488 string path = null;
4489 var assignToProperty = false;
4490 string signature = null;
4491
4492 foreach (var attrib in node.Attributes())
4493 {
4494 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
4495 {
4496 switch (attrib.Name.LocalName)
4497 {
4498 case "Id":
4499 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
4500 break;
4501 case "Depth":
4502 depth = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue);
4503 break;
4504 case "Path":
4505 path = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4506 break;
4507 case "AssignToProperty":
4508 assignToProperty = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
4509 break;
4510 default:
4511 this.Core.UnexpectedAttribute(node, attrib);
4512 break;
4513 }
4514 }
4515 else
4516 {
4517 this.Core.ParseExtensionAttribute(node, attrib);
4518 }
4519 }
4520
4521 if (null == id)
4522 {
4523 id = this.Core.CreateIdentifier("dir", path, depth.ToString());
4524 }
4525
4526 signature = id.Id;
4527
4528 var oneChild = false;
4529 var hasFileSearch = false;
4530 foreach (var child in node.Elements())
4531 {
4532 if (CompilerCore.WixNamespace == child.Name.Namespace)
4533 {
4534 var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
4535 switch (child.Name.LocalName)
4536 {
4537 case "DirectorySearch":
4538 if (oneChild)
4539 {
4540 this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName));
4541 }
4542 oneChild = true;
4543 signature = this.ParseDirectorySearchElement(child, id.Id);
4544 break;
4545 case "DirectorySearchRef":
4546 if (oneChild)
4547 {
4548 this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName));
4549 }
4550 oneChild = true;
4551 signature = this.ParseDirectorySearchRefElement(child, id.Id);
4552 break;
4553 case "FileSearch":
4554 if (oneChild)
4555 {
4556 this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName));
4557 }
4558 oneChild = true;
4559 hasFileSearch = true;
4560 signature = this.ParseFileSearchElement(child, id.Id, assignToProperty, depth);
4561 break;
4562 case "FileSearchRef":
4563 if (oneChild)
4564 {
4565 this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName));
4566 }
4567 oneChild = true;
4568 signature = this.ParseSimpleRefElement(child, SymbolDefinitions.Signature);
4569 break;
4570 default:
4571 this.Core.UnexpectedElement(node, child);
4572 break;
4573 }
4574
4575 // If AssignToProperty is set, only a FileSearch
4576 // or no child element can be nested.
4577 if (assignToProperty)
4578 {
4579 if (!hasFileSearch)
4580 {
4581 this.Core.Write(ErrorMessages.IllegalParentAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "AssignToProperty", child.Name.LocalName));
4582 }
4583 else if (!oneChild)
4584 {
4585 // This a normal directory search.
4586 assignToProperty = false;
4587 }
4588 }
4589 }
4590 else
4591 {
4592 this.Core.ParseExtensionElement(node, child);
4593 }
4594 }
4595
4596 if (!this.Core.EncounteredError)
4597 {
4598 var access = id.Access;
4599 var rowId = id.Id;
4600
4601 // If AssignToProperty is set, the DrLocator row created by
4602 // ParseFileSearchElement creates the directory entry to return
4603 // and the row created here is for the file search.
4604 if (assignToProperty)
4605 {
4606 access = AccessModifier.Section;
4607 rowId = signature;
4608
4609 // The property should be set to the directory search Id.
4610 signature = id.Id;
4611 }
4612
4613 var symbol = this.Core.AddSymbol(new DrLocatorSymbol(sourceLineNumbers, new Identifier(access, rowId, parentSignature, path))
4614 {
4615 SignatureRef = rowId,
4616 Parent = parentSignature,
4617 Path = path,
4618 });
4619
4620 if (CompilerConstants.IntegerNotSet != depth)
4621 {
4622 symbol.Depth = depth;
4623 }
4624 }
4625
4626 return signature;
4627 }
4628
4629 /// <summary>
4630 /// Parses a directory search reference element.
4631 /// </summary>
4632 /// <param name="node">Element to parse.</param>
4633 /// <param name="parentSignature">Signature of parent search element.</param>
4634 /// <returns>Signature of search element.</returns>
4635 private string ParseDirectorySearchRefElement(XElement node, string parentSignature)
4636 {
4637 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
4638 Identifier id = null;
4639 Identifier parent = null;
4640 string path = null;
4641 string signature = null;
4642
4643 foreach (var attrib in node.Attributes())
4644 {
4645 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
4646 {
4647 switch (attrib.Name.LocalName)
4648 {
4649 case "Id":
4650 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
4651 break;
4652 case "Parent":
4653 parent = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
4654 break;
4655 case "Path":
4656 path = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4657 break;
4658 default:
4659 this.Core.UnexpectedAttribute(node, attrib);
4660 break;
4661 }
4662 }
4663 else
4664 {
4665 this.Core.ParseExtensionAttribute(node, attrib);
4666 }
4667 }
4668
4669 if (null != parent)
4670 {
4671 if (!String.IsNullOrEmpty(parentSignature))
4672 {
4673 this.Core.Write(ErrorMessages.CanNotHaveTwoParents(sourceLineNumbers, id.Id, parent.Id, parentSignature));
4674 }
4675 else
4676 {
4677 parentSignature = parent.Id;
4678 }
4679 }
4680
4681 if (null == id)
4682 {
4683 id = this.Core.CreateIdentifier("dsr", parentSignature, path);
4684 }
4685
4686 signature = id.Id;
4687
4688 var oneChild = false;
4689 foreach (var child in node.Elements())
4690 {
4691 if (CompilerCore.WixNamespace == child.Name.Namespace)
4692 {
4693 var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
4694 switch (child.Name.LocalName)
4695 {
4696 case "DirectorySearch":
4697 if (oneChild)
4698 {
4699 this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName));
4700 }
4701 oneChild = true;
4702 signature = this.ParseDirectorySearchElement(child, id.Id);
4703 break;
4704 case "DirectorySearchRef":
4705 if (oneChild)
4706 {
4707 this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName));
4708 }
4709 oneChild = true;
4710 signature = this.ParseDirectorySearchRefElement(child, id.Id);
4711 break;
4712 case "FileSearch":
4713 if (oneChild)
4714 {
4715 this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName));
4716 }
4717 oneChild = true;
4718 signature = this.ParseFileSearchElement(child, id.Id, false, CompilerConstants.IntegerNotSet);
4719 break;
4720 case "FileSearchRef":
4721 if (oneChild)
4722 {
4723 this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName));
4724 }
4725 oneChild = true;
4726 signature = this.ParseSimpleRefElement(child, SymbolDefinitions.Signature);
4727 break;
4728 default:
4729 this.Core.UnexpectedElement(node, child);
4730 break;
4731 }
4732 }
4733 else
4734 {
4735 this.Core.ParseExtensionElement(node, child);
4736 }
4737 }
4738
4739
4740 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.DrLocator, id.Id, parentSignature, path);
4741
4742 return signature;
4743 }
4744
4745 /// <summary>
4746 /// Parses a feature element.
4747 /// </summary>
4748 /// <param name="node">Element to parse.</param>
4749 /// <param name="parentType">The type of parent.</param>
4750 /// <param name="parentId">Optional identifer for parent feature.</param>
4751 /// <param name="lastDisplay">Display value for last feature used to get the features to display in the same order as specified
4752 /// in the source code.</param>
4753 private void ParseFeatureElement(XElement node, ComplexReferenceParentType parentType, string parentId, ref int lastDisplay)
4754 {
4755 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
4756 Identifier id = null;
4757 string configurableDirectory = null;
4758 string description = null;
4759 var displayValue = "collapse";
4760 var level = 1;
4761 string title = null;
4762
4763 var installDefault = FeatureInstallDefault.Local;
4764 var typicalDefault = FeatureTypicalDefault.Install;
4765 var disallowAbsent = false;
4766 var disallowAdvertise = false;
4767 var display = 0;
4768
4769 foreach (var attrib in node.Attributes())
4770 {
4771 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
4772 {
4773 switch (attrib.Name.LocalName)
4774 {
4775 case "Id":
4776 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
4777 break;
4778 case "AllowAbsent":
4779 disallowAbsent = (this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib) == YesNoType.No);
4780 break;
4781 case "AllowAdvertise":
4782 disallowAdvertise = (this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib) == YesNoType.No);
4783 break;
4784 case "ConfigurableDirectory":
4785 configurableDirectory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
4786 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, configurableDirectory);
4787 break;
4788 case "Description":
4789 description = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4790 break;
4791 case "Display":
4792 displayValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4793 break;
4794 case "InstallDefault":
4795 var installDefaultValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4796 switch (installDefaultValue)
4797 {
4798 case "followParent":
4799 if (ComplexReferenceParentType.Product == parentType)
4800 {
4801 this.Core.Write(ErrorMessages.RootFeatureCannotFollowParent(sourceLineNumbers));
4802 }
4803 //bits = bits | MsiInterop.MsidbFeatureAttributesFollowParent;
4804 installDefault = FeatureInstallDefault.FollowParent;
4805 break;
4806 case "local": // this is the default
4807 installDefault = FeatureInstallDefault.Local;
4808 break;
4809 case "source":
4810 //bits = bits | MsiInterop.MsidbFeatureAttributesFavorSource;
4811 installDefault = FeatureInstallDefault.Source;
4812 break;
4813 case "":
4814 break;
4815 default:
4816 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, installDefaultValue, "followParent", "local", "source"));
4817 break;
4818 }
4819 break;
4820 case "Level":
4821 level = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue);
4822 break;
4823 case "Title":
4824 title = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4825 if ("PUT-FEATURE-TITLE-HERE" == title)
4826 {
4827 this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, title));
4828 }
4829 break;
4830 case "TypicalDefault":
4831 var typicalValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4832 switch (typicalValue)
4833 {
4834 case "advertise":
4835 //bits |= MsiInterop.MsidbFeatureAttributesFavorAdvertise;
4836 typicalDefault = FeatureTypicalDefault.Advertise;
4837 break;
4838 case "install": // this is the default
4839 typicalDefault = FeatureTypicalDefault.Install;
4840 break;
4841 default:
4842 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, typicalValue, "advertise", "install"));
4843 break;
4844 }
4845 break;
4846 default:
4847 this.Core.UnexpectedAttribute(node, attrib);
4848 break;
4849 }
4850 }
4851 else
4852 {
4853 this.Core.ParseExtensionAttribute(node, attrib);
4854 }
4855 }
4856
4857 if (null == id)
4858 {
4859 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
4860 id = Identifier.Invalid;
4861 }
4862 else if (38 < id.Id.Length)
4863 {
4864 this.Core.Write(ErrorMessages.FeatureNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", id.Id));
4865 }
4866
4867 if (null != configurableDirectory && configurableDirectory.ToUpper(CultureInfo.InvariantCulture) != configurableDirectory)
4868 {
4869 this.Core.Write(ErrorMessages.FeatureConfigurableDirectoryNotUppercase(sourceLineNumbers, node.Name.LocalName, "ConfigurableDirectory", configurableDirectory));
4870 }
4871
4872 if (FeatureTypicalDefault.Advertise == typicalDefault && disallowAdvertise)
4873 {
4874 this.Core.Write(ErrorMessages.FeatureCannotFavorAndDisallowAdvertise(sourceLineNumbers, node.Name.LocalName, "TypicalDefault", "advertise", "AllowAdvertise", "no"));
4875 }
4876
4877 var childDisplay = 0;
4878 foreach (var child in node.Elements())
4879 {
4880 if (CompilerCore.WixNamespace == child.Name.Namespace)
4881 {
4882 switch (child.Name.LocalName)
4883 {
4884 case "ComponentGroupRef":
4885 this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.Feature, id.Id, null);
4886 break;
4887 case "ComponentRef":
4888 this.ParseComponentRefElement(child, ComplexReferenceParentType.Feature, id.Id, null);
4889 break;
4890 case "Component":
4891 this.ParseComponentElement(child, ComplexReferenceParentType.Feature, id.Id, null, CompilerConstants.IntegerNotSet, null, null);
4892 break;
4893 case "Feature":
4894 this.ParseFeatureElement(child, ComplexReferenceParentType.Feature, id.Id, ref childDisplay);
4895 break;
4896 case "FeatureGroupRef":
4897 this.ParseFeatureGroupRefElement(child, ComplexReferenceParentType.Feature, id.Id);
4898 break;
4899 case "FeatureRef":
4900 this.ParseFeatureRefElement(child, ComplexReferenceParentType.Feature, id.Id);
4901 break;
4902 case "Level":
4903 this.ParseLevelElement(child, id.Id);
4904 break;
4905 case "MergeRef":
4906 this.ParseMergeRefElement(child, ComplexReferenceParentType.Feature, id.Id);
4907 break;
4908 default:
4909 this.Core.UnexpectedElement(node, child);
4910 break;
4911 }
4912 }
4913 else
4914 {
4915 this.Core.ParseExtensionElement(node, child);
4916 }
4917 }
4918
4919 switch (displayValue)
4920 {
4921 case "collapse":
4922 lastDisplay = (lastDisplay | 1) + 1;
4923 display = lastDisplay;
4924 break;
4925 case "expand":
4926 lastDisplay = (lastDisplay + 1) | 1;
4927 display = lastDisplay;
4928 break;
4929 case "hidden":
4930 display = 0;
4931 break;
4932 default:
4933 if (!Int32.TryParse(displayValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out display))
4934 {
4935 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Display", displayValue, "collapse", "expand", "hidden"));
4936 }
4937 else
4938 {
4939 // Save the display value (if its not hidden) for subsequent rows
4940 if (0 != display)
4941 {
4942 lastDisplay = display;
4943 }
4944 }
4945 break;
4946 }
4947
4948 if (!this.Core.EncounteredError)
4949 {
4950 this.Core.AddSymbol(new FeatureSymbol(sourceLineNumbers, id)
4951 {
4952 ParentFeatureRef = null, // this field is set in the linker
4953 Title = title,
4954 Description = description,
4955 Display = display,
4956 Level = level,
4957 DirectoryRef = configurableDirectory,
4958 DisallowAbsent = disallowAbsent,
4959 DisallowAdvertise = disallowAdvertise,
4960 InstallDefault = installDefault,
4961 TypicalDefault = typicalDefault,
4962 });
4963
4964 if (ComplexReferenceParentType.Unknown != parentType)
4965 {
4966 this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.Feature, id.Id, false);
4967 }
4968 }
4969 }
4970
4971 /// <summary>
4972 /// Parses a feature reference element.
4973 /// </summary>
4974 /// <param name="node">Element to parse.</param>
4975 /// <param name="parentType">The type of parent.</param>
4976 /// <param name="parentId">Optional identifier for parent feature.</param>
4977 private void ParseFeatureRefElement(XElement node, ComplexReferenceParentType parentType, string parentId)
4978 {
4979 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
4980 string id = null;
4981 var ignoreParent = YesNoType.NotSet;
4982
4983 foreach (var attrib in node.Attributes())
4984 {
4985 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
4986 {
4987 switch (attrib.Name.LocalName)
4988 {
4989 case "Id":
4990 id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
4991 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Feature, id);
4992 break;
4993 case "IgnoreParent":
4994 ignoreParent = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
4995 break;
4996 default:
4997 this.Core.UnexpectedAttribute(node, attrib);
4998 break;
4999 }
5000 }
5001 else
5002 {
5003 this.Core.ParseExtensionAttribute(node, attrib);
5004 }
5005 }
5006
5007
5008 if (null == id)
5009 {
5010 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
5011 }
5012
5013 var lastDisplay = 0;
5014 foreach (var child in node.Elements())
5015 {
5016 if (CompilerCore.WixNamespace == child.Name.Namespace)
5017 {
5018 switch (child.Name.LocalName)
5019 {
5020 case "ComponentGroupRef":
5021 this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.Feature, id, null);
5022 break;
5023 case "ComponentRef":
5024 this.ParseComponentRefElement(child, ComplexReferenceParentType.Feature, id, null);
5025 break;
5026 case "Component":
5027 this.ParseComponentElement(child, ComplexReferenceParentType.Feature, id, null, CompilerConstants.IntegerNotSet, null, null);
5028 break;
5029 case "Feature":
5030 this.ParseFeatureElement(child, ComplexReferenceParentType.Feature, id, ref lastDisplay);
5031 break;
5032 case "FeatureGroup":
5033 this.ParseFeatureGroupElement(child, ComplexReferenceParentType.Feature, id);
5034 break;
5035 case "FeatureGroupRef":
5036 this.ParseFeatureGroupRefElement(child, ComplexReferenceParentType.Feature, id);
5037 break;
5038 case "FeatureRef":
5039 this.ParseFeatureRefElement(child, ComplexReferenceParentType.Feature, id);
5040 break;
5041 case "MergeRef":
5042 this.ParseMergeRefElement(child, ComplexReferenceParentType.Feature, id);
5043 break;
5044 default:
5045 this.Core.UnexpectedElement(node, child);
5046 break;
5047 }
5048 }
5049 else
5050 {
5051 this.Core.ParseExtensionElement(node, child);
5052 }
5053 }
5054
5055 if (!this.Core.EncounteredError)
5056 {
5057 if (ComplexReferenceParentType.Unknown != parentType && YesNoType.Yes != ignoreParent)
5058 {
5059 this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.Feature, id, false);
5060 }
5061 }
5062 }
5063
5064 /// <summary>
5065 /// Parses a feature group element.
5066 /// </summary>
5067 /// <param name="node">Element to parse.</param>
5068 /// <param name="parentType"></param>
5069 /// <param name="parentId"></param>
5070 private void ParseFeatureGroupElement(XElement node, ComplexReferenceParentType parentType, string parentId)
5071 {
5072 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
5073 Identifier id = null;
5074
5075 foreach (var attrib in node.Attributes())
5076 {
5077 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
5078 {
5079 switch (attrib.Name.LocalName)
5080 {
5081 case "Id":
5082 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
5083 break;
5084 default:
5085 this.Core.UnexpectedAttribute(node, attrib);
5086 break;
5087 }
5088 }
5089 else
5090 {
5091 this.Core.ParseExtensionAttribute(node, attrib);
5092 }
5093 }
5094
5095 if (null == id)
5096 {
5097 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
5098 id = Identifier.Invalid;
5099 }
5100
5101 var lastDisplay = 0;
5102 foreach (var child in node.Elements())
5103 {
5104 if (CompilerCore.WixNamespace == child.Name.Namespace)
5105 {
5106 switch (child.Name.LocalName)
5107 {
5108 case "ComponentGroupRef":
5109 this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id, null);
5110 break;
5111 case "ComponentRef":
5112 this.ParseComponentRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id, null);
5113 break;
5114 case "Component":
5115 this.ParseComponentElement(child, ComplexReferenceParentType.FeatureGroup, id.Id, null, CompilerConstants.IntegerNotSet, null, null);
5116 break;
5117 case "Feature":
5118 this.ParseFeatureElement(child, ComplexReferenceParentType.FeatureGroup, id.Id, ref lastDisplay);
5119 break;
5120 case "FeatureGroupRef":
5121 this.ParseFeatureGroupRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id);
5122 break;
5123 case "FeatureRef":
5124 this.ParseFeatureRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id);
5125 break;
5126 case "MergeRef":
5127 this.ParseMergeRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id);
5128 break;
5129 default:
5130 this.Core.UnexpectedElement(node, child);
5131 break;
5132 }
5133 }
5134 else
5135 {
5136 this.Core.ParseExtensionElement(node, child);
5137 }
5138 }
5139
5140 if (!this.Core.EncounteredError)
5141 {
5142 this.Core.AddSymbol(new WixFeatureGroupSymbol(sourceLineNumbers, id));
5143
5144 //Add this FeatureGroup and its parent in WixGroup.
5145 this.Core.CreateWixGroupRow(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.FeatureGroup, id.Id);
5146 }
5147 }
5148
5149 /// <summary>
5150 /// Parses a feature group reference element.
5151 /// </summary>
5152 /// <param name="node">Element to parse.</param>
5153 /// <param name="parentType">The type of parent.</param>
5154 /// <param name="parentId">Identifier of parent element.</param>
5155 private void ParseFeatureGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId)
5156 {
5157 Debug.Assert(ComplexReferenceParentType.Feature == parentType || ComplexReferenceParentType.FeatureGroup == parentType || ComplexReferenceParentType.ComponentGroup == parentType || ComplexReferenceParentType.Product == parentType);
5158
5159 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
5160 string id = null;
5161 var ignoreParent = YesNoType.NotSet;
5162 var primary = YesNoType.NotSet;
5163
5164 foreach (var attrib in node.Attributes())
5165 {
5166 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
5167 {
5168 switch (attrib.Name.LocalName)
5169 {
5170 case "Id":
5171 id = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
5172 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixFeatureGroup, id);
5173 break;
5174 case "IgnoreParent":
5175 ignoreParent = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
5176 break;
5177 case "Primary":
5178 primary = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
5179 break;
5180 default:
5181 this.Core.UnexpectedAttribute(node, attrib);
5182 break;
5183 }
5184 }
5185 else
5186 {
5187 this.Core.ParseExtensionAttribute(node, attrib);
5188 }
5189 }
5190
5191 if (null == id)
5192 {
5193 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
5194 }
5195
5196 this.Core.ParseForExtensionElements(node);
5197
5198 if (!this.Core.EncounteredError)
5199 {
5200 if (YesNoType.Yes != ignoreParent)
5201 {
5202 this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.FeatureGroup, id, (YesNoType.Yes == primary));
5203 }
5204 }
5205 }
5206
5207 /// <summary>
5208 /// Parses an environment element.
5209 /// </summary>
5210 /// <param name="node">Element to parse.</param>
5211 /// <param name="componentId">Identifier of parent component.</param>
5212 private void ParseEnvironmentElement(XElement node, string componentId)
5213 {
5214 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
5215 Identifier id = null;
5216 string name = null;
5217 EnvironmentActionType? action = null;
5218 EnvironmentPartType? part = null;
5219 var permanent = false;
5220 var separator = ";"; // default to ';'
5221 var system = false;
5222 string value = null;
5223
5224 foreach (var attrib in node.Attributes())
5225 {
5226 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
5227 {
5228 switch (attrib.Name.LocalName)
5229 {
5230 case "Id":
5231 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
5232 break;
5233 case "Action":
5234 var actionValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
5235 switch (actionValue)
5236 {
5237 case "create":
5238 action = EnvironmentActionType.Create;
5239 break;
5240 case "set":
5241 action = EnvironmentActionType.Set;
5242 break;
5243 case "remove":
5244 action = EnvironmentActionType.Remove;
5245 break;
5246 default:
5247 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, value, "create", "set", "remove"));
5248 break;
5249 }
5250 break;
5251 case "Name":
5252 name = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
5253 break;
5254 case "Part":
5255 var partValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
5256 switch (partValue)
5257 {
5258 case "all":
5259 part = EnvironmentPartType.All;
5260 break;
5261 case "first":
5262 part = EnvironmentPartType.First;
5263 break;
5264 case "last":
5265 part = EnvironmentPartType.Last;
5266 break;
5267 case "":
5268 break;
5269 default:
5270 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Part", partValue, "all", "first", "last"));
5271 break;
5272 }
5273 break;
5274 case "Permanent":
5275 permanent = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
5276 break;
5277 case "Separator":
5278 separator = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
5279 break;
5280 case "System":
5281 system = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
5282 break;
5283 case "Value":
5284 value = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
5285 break;
5286 default:
5287 this.Core.UnexpectedAttribute(node, attrib);
5288 break;
5289 }
5290 }
5291 else
5292 {
5293 this.Core.ParseExtensionAttribute(node, attrib);
5294 }
5295 }
5296
5297 if (null == id)
5298 {
5299 id = this.Core.CreateIdentifier("env", ((int?)action)?.ToString(), name, ((int?)part)?.ToString(), system.ToString());
5300 }
5301
5302 if (null == name)
5303 {
5304 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
5305 }
5306
5307 if (part.HasValue && action == EnvironmentActionType.Create)
5308 {
5309 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Part", "Action", "create"));
5310 }
5311
5312 //if (Wix.Environment.PartType.NotSet != partType)
5313 //{
5314 // if ("+" == action)
5315 // {
5316 // this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Part", "Action", "create"));
5317 // }
5318
5319 // switch (partType)
5320 // {
5321 // case Wix.Environment.PartType.all:
5322 // break;
5323 // case Wix.Environment.PartType.first:
5324 // text = String.Concat(text, separator, "[~]");
5325 // break;
5326 // case Wix.Environment.PartType.last:
5327 // text = String.Concat("[~]", separator, text);
5328 // break;
5329 // }
5330 //}
5331
5332 //if (permanent)
5333 //{
5334 // uninstall = null;
5335 //}
5336
5337 this.Core.ParseForExtensionElements(node);
5338
5339 if (!this.Core.EncounteredError)
5340 {
5341 this.Core.AddSymbol(new EnvironmentSymbol(sourceLineNumbers, id)
5342 {
5343 Name = name,
5344 Value = value,
5345 Separator = separator,
5346 Action = action,
5347 Part = part,
5348 Permanent = permanent,
5349 System = system,
5350 ComponentRef = componentId
5351 });
5352 }
5353 }
5354
5355 /// <summary>
5356 /// Parses an error element.
5357 /// </summary>
5358 /// <param name="node">Element to parse.</param>
5359 private void ParseErrorElement(XElement node)
5360 {
5361 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
5362 var id = CompilerConstants.IntegerNotSet;
5363 string message = null;
5364
5365 foreach (var attrib in node.Attributes())
5366 {
5367 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
5368 {
5369 switch (attrib.Name.LocalName)
5370 {
5371 case "Id":
5372 id = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue);
5373 break;
5374 case "Message":
5375 message = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty);
5376 break;
5377 default:
5378 this.Core.UnexpectedAttribute(node, attrib);
5379 break;
5380 }
5381 }
5382 else
5383 {
5384 this.Core.ParseExtensionAttribute(node, attrib);
5385 }
5386 }
5387
5388 if (CompilerConstants.IntegerNotSet == id)
5389 {
5390 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
5391 id = CompilerConstants.IllegalInteger;
5392 }
5393
5394 this.Core.ParseForExtensionElements(node);
5395
5396 if (!this.Core.EncounteredError)
5397 {
5398 this.Core.AddSymbol(new ErrorSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, id))
5399 {
5400 Message = message
5401 });
5402 }
5403 }
5404
5405 /// <summary>
5406 /// Parses an extension element.
5407 /// </summary>
5408 /// <param name="node">Element to parse.</param>
5409 /// <param name="componentId">Identifier of parent component.</param>
5410 /// <param name="advertise">Flag if this extension is advertised.</param>
5411 /// <param name="progId">ProgId for extension.</param>
5412 private void ParseExtensionElement(XElement node, string componentId, YesNoType advertise, string progId)
5413 {
5414 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
5415 string extension = null;
5416 string mime = null;
5417
5418 foreach (var attrib in node.Attributes())
5419 {
5420 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
5421 {
5422 switch (attrib.Name.LocalName)
5423 {
5424 case "Id":
5425 extension = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
5426 break;
5427 case "Advertise":
5428 var extensionAdvertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
5429 if ((YesNoType.No == advertise && YesNoType.Yes == extensionAdvertise) || (YesNoType.Yes == advertise && YesNoType.No == extensionAdvertise))
5430 {
5431 this.Core.Write(ErrorMessages.AdvertiseStateMustMatch(sourceLineNumbers, extensionAdvertise.ToString(), advertise.ToString()));
5432 }
5433 advertise = extensionAdvertise;
5434 break;
5435 case "ContentType":
5436 mime = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
5437 break;
5438 default:
5439 this.Core.UnexpectedAttribute(node, attrib);
5440 break;
5441 }
5442 }
5443 else
5444 {
5445 var context = new Dictionary<string, string>() { { "ProgId", progId }, { "ComponentId", componentId } };
5446 this.Core.ParseExtensionAttribute(node, attrib, context);
5447 }
5448 }
5449
5450 if (YesNoType.NotSet == advertise)
5451 {
5452 advertise = YesNoType.No;
5453 }
5454
5455 foreach (var child in node.Elements())
5456 {
5457 if (CompilerCore.WixNamespace == child.Name.Namespace)
5458 {
5459 switch (child.Name.LocalName)
5460 {
5461 case "Verb":
5462 this.ParseVerbElement(child, extension, progId, componentId, advertise);
5463 break;
5464 case "MIME":
5465 var newMime = this.ParseMIMEElement(child, extension, componentId, advertise);
5466 if (null != newMime && null == mime)
5467 {
5468 mime = newMime;
5469 }
5470 break;
5471 default:
5472 this.Core.UnexpectedElement(node, child);
5473 break;
5474 }
5475 }
5476 else
5477 {
5478 this.Core.ParseExtensionElement(node, child);
5479 }
5480 }
5481
5482
5483 if (YesNoType.Yes == advertise)
5484 {
5485 if (!this.Core.EncounteredError)
5486 {
5487 this.Core.AddSymbol(new ExtensionSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, extension, componentId))
5488 {
5489 Extension = extension,
5490 ComponentRef = componentId,
5491 ProgIdRef = progId,
5492 MimeRef = mime,
5493 FeatureRef = Guid.Empty.ToString("B"),
5494 });
5495
5496 this.Core.EnsureTable(sourceLineNumbers, WindowsInstallerTableDefinitions.Verb);
5497 }
5498 }
5499 else if (YesNoType.No == advertise)
5500 {
5501 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(".", extension), String.Empty, progId, componentId); // Extension
5502 if (null != mime)
5503 {
5504 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(".", extension), "Content Type", mime, componentId); // Extension's MIME ContentType
5505 }
5506 }
5507 }
5508
5509
5510 /// <summary>
5511 /// Parses a file element.
5512 /// </summary>
5513 /// <param name="node">File element to parse.</param>
5514 /// <param name="componentId">Parent's component id.</param>
5515 /// <param name="directoryId">Ancestor's directory id.</param>
5516 /// <param name="diskId">Disk id inherited from parent component.</param>
5517 /// <param name="sourcePath">Default source path of parent directory.</param>
5518 /// <param name="possibleKeyPath">This will be set with the possible keyPath for the parent component.</param>
5519 /// <param name="win64Component">true if the component is 64-bit.</param>
5520 /// <param name="componentGuid"></param>
5521 /// <returns>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.</returns>
5522 private YesNoType ParseFileElement(XElement node, string componentId, string directoryId, int diskId, string sourcePath, out string possibleKeyPath, bool win64Component, string componentGuid)
5523 {
5524 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
5525 Identifier id = null;
5526 var assemblyType = AssemblyType.NotAnAssembly;
5527 string assemblyApplication = null;
5528 string assemblyManifest = null;
5529 string bindPath = null;
5530
5531 //int bits = MsiInterop.MsidbFileAttributesVital;
5532 var readOnly = false;
5533 var checksum = false;
5534 bool? compressed = null;
5535 var hidden = false;
5536 var system = false;
5537 var vital = true; // assume all files are vital.
5538
5539 string companionFile = null;
5540 string defaultLanguage = null;
5541 var defaultSize = 0;
5542 string defaultVersion = null;
5543 string fontTitle = null;
5544 var keyPath = YesNoType.NotSet;
5545 string name = null;
5546 var patchGroup = CompilerConstants.IntegerNotSet;
5547 var patchIgnore = false;
5548 var patchIncludeWholeFile = false;
5549 var patchAllowIgnoreOnError = false;
5550
5551 string ignoreLengths = null;
5552 string ignoreOffsets = null;
5553 string protectLengths = null;
5554 string protectOffsets = null;
5555 string symbols = null;
5556
5557 string procArch = null;
5558 int? selfRegCost = null;
5559 string shortName = null;
5560 var source = sourcePath; // assume we'll use the parents as the source for this file
5561 var sourceSet = false;
5562
5563 foreach (var attrib in node.Attributes())
5564 {
5565 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
5566 {
5567 switch (attrib.Name.LocalName)
5568 {
5569 case "Id":
5570 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
5571 break;
5572 case "Assembly":
5573 var assemblyValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
5574 switch (assemblyValue)
5575 {
5576 case ".net":
5577 assemblyType = AssemblyType.DotNetAssembly;
5578 break;
5579 case "no":
5580 assemblyType = AssemblyType.NotAnAssembly;
5581 break;
5582 case "win32":
5583 assemblyType = AssemblyType.Win32Assembly;
5584 break;
5585 default:
5586 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, "File", "Assembly", assemblyValue, "no", "win32", ".net"));
5587 break;
5588 }
5589 break;
5590 case "AssemblyApplication":
5591 assemblyApplication = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
5592 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, assemblyApplication);
5593 break;
5594 case "AssemblyManifest":
5595 assemblyManifest = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
5596 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, assemblyManifest);
5597 break;
5598 case "BindPath":
5599 bindPath = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty);
5600 break;
5601 case "Checksum":
5602 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
5603 {
5604 checksum = true;
5605 //bits |= MsiInterop.MsidbFileAttributesChecksum;
5606 }
5607 break;
5608 case "CompanionFile":
5609 companionFile = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
5610 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, companionFile);
5611 break;
5612 case "Compressed":
5613 var compressedValue = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib);
5614 if (YesNoDefaultType.Yes == compressedValue)
5615 {
5616 compressed = true;
5617 //bits |= MsiInterop.MsidbFileAttributesCompressed;
5618 }
5619 else if (YesNoDefaultType.No == compressedValue)
5620 {
5621 compressed = false;
5622 //bits |= MsiInterop.MsidbFileAttributesNoncompressed;
5623 }
5624 break;
5625 case "DefaultLanguage":
5626 defaultLanguage = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
5627 break;
5628 case "DefaultSize":
5629 defaultSize = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue);
5630 break;
5631 case "DefaultVersion":
5632 defaultVersion = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
5633 break;
5634 case "DiskId":
5635 diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue);
5636 break;
5637 case "FontTitle":
5638 fontTitle = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
5639 break;
5640 case "Hidden":
5641 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
5642 {
5643 hidden = true;
5644 //bits |= MsiInterop.MsidbFileAttributesHidden;
5645 }
5646 break;
5647 case "KeyPath":
5648 keyPath = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
5649 break;
5650 case "Name":
5651 name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false);
5652 break;
5653 case "PatchGroup":
5654 patchGroup = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int32.MaxValue);
5655 break;
5656 case "PatchIgnore":
5657 patchIgnore = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
5658 break;
5659 case "PatchWholeFile":
5660 patchIncludeWholeFile = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
5661 break;
5662 case "PatchAllowIgnoreOnError":
5663 patchAllowIgnoreOnError = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
5664 break;
5665 case "ProcessorArchitecture":
5666 var procArchValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
5667 switch (procArchValue)
5668 {
5669 case "msil":
5670 procArch = "MSIL";
5671 break;
5672 case "x86":
5673 procArch = "x86";
5674 break;
5675 case "x64":
5676 procArch = "amd64";
5677 break;
5678 case "arm64":
5679 procArch = "arm64";
5680 break;
5681 case "":
5682 break;
5683 default:
5684 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, "File", "ProcessorArchitecture", procArchValue, "msil", "x86", "x64"));
5685 break;
5686 }
5687 break;
5688 case "ReadOnly":
5689 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
5690 {
5691 readOnly = true;
5692 //bits |= MsiInterop.MsidbFileAttributesReadOnly;
5693 }
5694 break;
5695 case "SelfRegCost":
5696 selfRegCost = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue);
5697 break;
5698 case "ShortName":
5699 shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false);
5700 break;
5701 case "Source":
5702 source = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
5703 sourceSet = true;
5704 break;
5705 case "System":
5706 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
5707 {
5708 system = true;
5709 //bits |= MsiInterop.MsidbFileAttributesSystem;
5710 }
5711 break;
5712 case "TrueType":
5713 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
5714 {
5715 fontTitle = String.Empty;
5716 }
5717 break;
5718 case "Vital":
5719 var isVital = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
5720 if (YesNoType.Yes == isVital)
5721 {
5722 vital = true;
5723 //bits |= MsiInterop.MsidbFileAttributesVital;
5724 }
5725 else if (YesNoType.No == isVital)
5726 {
5727 vital = false;
5728 //bits &= ~MsiInterop.MsidbFileAttributesVital;
5729 }
5730 break;
5731 default:
5732 this.Core.UnexpectedAttribute(node, attrib);
5733 break;
5734 }
5735 }
5736 else
5737 {
5738 this.Core.ParseExtensionAttribute(node, attrib);
5739 }
5740 }
5741
5742 if (null != companionFile)
5743 {
5744 // the companion file cannot be the key path of a component
5745 if (YesNoType.Yes == keyPath)
5746 {
5747 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "CompanionFile", "KeyPath", "yes"));
5748 }
5749 }
5750
5751 if (sourceSet && !source.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal) && null == name)
5752 {
5753 name = Path.GetFileName(source);
5754 if (!this.Core.IsValidLongFilename(name, false))
5755 {
5756 this.Core.Write(ErrorMessages.IllegalLongFilename(sourceLineNumbers, node.Name.LocalName, "Source", name));
5757 }
5758 }
5759
5760 if (name == null)
5761 {
5762 if (shortName == null)
5763 {
5764 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
5765 }
5766 else
5767 {
5768 name = shortName;
5769 shortName = null;
5770 }
5771 }
5772
5773 if (null == id)
5774 {
5775 id = this.Core.CreateIdentifier("fil", directoryId, name);
5776 }
5777
5778 if (null != defaultVersion && null != companionFile)
5779 {
5780 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DefaultVersion", "CompanionFile", companionFile));
5781 }
5782
5783 if (AssemblyType.NotAnAssembly == assemblyType)
5784 {
5785 if (null != assemblyManifest)
5786 {
5787 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Assembly", "AssemblyManifest"));
5788 }
5789
5790 if (null != assemblyApplication)
5791 {
5792 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Assembly", "AssemblyApplication"));
5793 }
5794 }
5795 else
5796 {
5797 if (AssemblyType.Win32Assembly == assemblyType && null == assemblyManifest)
5798 {
5799 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "AssemblyManifest", "Assembly", "win32"));
5800 }
5801
5802 // allow "*" guid components to omit explicit KeyPath as they can have only one file and therefore this file is the keypath
5803 if (YesNoType.Yes != keyPath && "*" != componentGuid)
5804 {
5805 this.Core.Write(ErrorMessages.IllegalAttributeValueWithoutOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Assembly", (AssemblyType.DotNetAssembly == assemblyType ? ".net" : "win32"), "KeyPath", "yes"));
5806 }
5807 }
5808
5809 foreach (var child in node.Elements())
5810 {
5811 if (CompilerCore.WixNamespace == child.Name.Namespace)
5812 {
5813 switch (child.Name.LocalName)
5814 {
5815 case "AppId":
5816 this.ParseAppIdElement(child, componentId, YesNoType.NotSet, id.Id, null, null);
5817 break;
5818 case "AssemblyName":
5819 this.ParseAssemblyName(child, componentId);
5820 break;
5821 case "Class":
5822 this.ParseClassElement(child, componentId, YesNoType.NotSet, id.Id, null, null, null);
5823 break;
5824 case "CopyFile":
5825 this.ParseCopyFileElement(child, componentId, id.Id);
5826 break;
5827 case "IgnoreRange":
5828 this.ParseRangeElement(child, ref ignoreOffsets, ref ignoreLengths);
5829 break;
5830 case "ODBCDriver":
5831 this.ParseODBCDriverOrTranslator(child, componentId, id.Id, SymbolDefinitionType.ODBCDriver);
5832 break;
5833 case "ODBCTranslator":
5834 this.ParseODBCDriverOrTranslator(child, componentId, id.Id, SymbolDefinitionType.ODBCTranslator);
5835 break;
5836 case "Permission":
5837 this.ParsePermissionElement(child, id.Id, "File");
5838 break;
5839 case "PermissionEx":
5840 this.ParsePermissionExElement(child, id.Id, "File");
5841 break;
5842 case "ProtectRange":
5843 this.ParseRangeElement(child, ref protectOffsets, ref protectLengths);
5844 break;
5845 case "Shortcut":
5846 this.ParseShortcutElement(child, componentId, node.Name.LocalName, id.Id, keyPath);
5847 break;
5848 case "SymbolPath":
5849 if (null != symbols)
5850 {
5851 symbols += ";" + this.ParseSymbolPathElement(child);
5852 }
5853 else
5854 {
5855 symbols = this.ParseSymbolPathElement(child);
5856 }
5857 break;
5858 case "TypeLib":
5859 this.ParseTypeLibElement(child, componentId, id.Id, win64Component);
5860 break;
5861 default:
5862 this.Core.UnexpectedElement(node, child);
5863 break;
5864 }
5865 }
5866 else
5867 {
5868 var context = new Dictionary<string, string>() { { "FileId", id?.Id }, { "ComponentId", componentId }, { "DirectoryId", directoryId }, { "Win64", win64Component.ToString() } };
5869 this.Core.ParseExtensionElement(node, child, context);
5870 }
5871 }
5872
5873 if (!this.Core.EncounteredError)
5874 {
5875 var patchAttributes = PatchAttributeType.None;
5876 if (patchIgnore)
5877 {
5878 patchAttributes |= PatchAttributeType.Ignore;
5879 }
5880 if (patchIncludeWholeFile)
5881 {
5882 patchAttributes |= PatchAttributeType.IncludeWholeFile;
5883 }
5884 if (patchAllowIgnoreOnError)
5885 {
5886 patchAttributes |= PatchAttributeType.AllowIgnoreOnError;
5887 }
5888
5889 if (String.IsNullOrEmpty(source))
5890 {
5891 source = name;
5892 }
5893 else if (source.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) // if source relies on parent directories, append the file name
5894 {
5895 source = Path.Combine(source, name);
5896 }
5897
5898 var attributes = FileSymbolAttributes.None;
5899 attributes |= readOnly ? FileSymbolAttributes.ReadOnly : 0;
5900 attributes |= hidden ? FileSymbolAttributes.Hidden : 0;
5901 attributes |= system ? FileSymbolAttributes.System : 0;
5902 attributes |= vital ? FileSymbolAttributes.Vital : 0;
5903 attributes |= checksum ? FileSymbolAttributes.Checksum : 0;
5904 attributes |= compressed.HasValue && compressed == true ? FileSymbolAttributes.Compressed : 0;
5905 attributes |= compressed.HasValue && compressed == false ? FileSymbolAttributes.Uncompressed : 0;
5906
5907 this.Core.AddSymbol(new FileSymbol(sourceLineNumbers, id)
5908 {
5909 ComponentRef = componentId,
5910 Name = name,
5911 ShortName = shortName,
5912 FileSize = defaultSize,
5913 Version = companionFile ?? defaultVersion,
5914 Language = defaultLanguage,
5915 Attributes = attributes,
5916
5917 DirectoryRef = directoryId,
5918 DiskId = (CompilerConstants.IntegerNotSet == diskId) ? null : (int?)diskId,
5919 Source = new IntermediateFieldPathValue { Path = source },
5920
5921 FontTitle = fontTitle,
5922 SelfRegCost = selfRegCost,
5923 BindPath = bindPath,
5924
5925 PatchGroup = (CompilerConstants.IntegerNotSet == patchGroup) ? null : (int?)patchGroup,
5926 PatchAttributes = patchAttributes,
5927
5928 // Delta patching information
5929 RetainLengths = protectLengths,
5930 IgnoreOffsets = ignoreOffsets,
5931 IgnoreLengths = ignoreLengths,
5932 RetainOffsets = protectOffsets,
5933 SymbolPaths = symbols,
5934 });
5935
5936 if (AssemblyType.NotAnAssembly != assemblyType)
5937 {
5938 this.Core.AddSymbol(new AssemblySymbol(sourceLineNumbers, id)
5939 {
5940 ComponentRef = componentId,
5941 FeatureRef = Guid.Empty.ToString("B"),
5942 ManifestFileRef = assemblyManifest,
5943 ApplicationFileRef = assemblyApplication,
5944 Type = assemblyType,
5945 ProcessorArchitecture = procArch,
5946 });
5947 }
5948 }
5949
5950 if (CompilerConstants.IntegerNotSet != diskId)
5951 {
5952 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Media, diskId.ToString(CultureInfo.InvariantCulture.NumberFormat));
5953 }
5954
5955 // If this component does not have a companion file this file is a possible keypath.
5956 possibleKeyPath = null;
5957 if (null == companionFile)
5958 {
5959 possibleKeyPath = id.Id;
5960 }
5961
5962 return keyPath;
5963 }
5964
5965 /// <summary>
5966 /// Parses a file search element.
5967 /// </summary>
5968 /// <param name="node">Element to parse.</param>
5969 /// <param name="parentSignature">Signature of parent search element.</param>
5970 /// <param name="parentDirectorySearch">Whether this search element is used to search for the parent directory.</param>
5971 /// <param name="parentDepth">The depth specified by the parent search element.</param>
5972 /// <returns>Signature of search element.</returns>
5973 private string ParseFileSearchElement(XElement node, string parentSignature, bool parentDirectorySearch, int parentDepth)
5974 {
5975 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
5976 Identifier id = null;
5977 string languages = null;
5978 var minDate = CompilerConstants.IntegerNotSet;
5979 var maxDate = CompilerConstants.IntegerNotSet;
5980 var maxSize = CompilerConstants.IntegerNotSet;
5981 var minSize = CompilerConstants.IntegerNotSet;
5982 string maxVersion = null;
5983 string minVersion = null;
5984 string name = null;
5985 string shortName = null;
5986
5987 foreach (var attrib in node.Attributes())
5988 {
5989 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
5990 {
5991 switch (attrib.Name.LocalName)
5992 {
5993 case "Id":
5994 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
5995 break;
5996 case "Name":
5997 name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false);
5998 break;
5999 case "MinVersion":
6000 minVersion = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
6001 break;
6002 case "MaxVersion":
6003 maxVersion = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
6004 break;
6005 case "MinSize":
6006 minSize = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue);
6007 break;
6008 case "MaxSize":
6009 maxSize = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue);
6010 break;
6011 case "MinDate":
6012 minDate = this.Core.GetAttributeDateTimeValue(sourceLineNumbers, attrib);
6013 break;
6014 case "MaxDate":
6015 maxDate = this.Core.GetAttributeDateTimeValue(sourceLineNumbers, attrib);
6016 break;
6017 case "Languages":
6018 languages = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
6019 break;
6020 case "ShortName":
6021 shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false);
6022 break;
6023 default:
6024 this.Core.UnexpectedAttribute(node, attrib);
6025 break;
6026 }
6027 }
6028 else
6029 {
6030 this.Core.ParseExtensionAttribute(node, attrib);
6031 }
6032 }
6033
6034 // Using both ShortName and Name will not always work due to a Windows Installer bug.
6035 if (null != shortName && null != name)
6036 {
6037 this.Core.Write(WarningMessages.FileSearchFileNameIssue(sourceLineNumbers, node.Name.LocalName, "ShortName", "Name"));
6038 }
6039 else if (null == shortName && null == name) // at least one name must be specified.
6040 {
6041 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
6042 }
6043
6044 if (this.Core.IsValidShortFilename(name, false))
6045 {
6046 if (null == shortName)
6047 {
6048 shortName = name;
6049 name = null;
6050 }
6051 else
6052 {
6053 this.Core.Write(ErrorMessages.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Name", name, "ShortName"));
6054 }
6055 }
6056
6057 if (null == id)
6058 {
6059 if (String.IsNullOrEmpty(parentSignature))
6060 {
6061 id = this.Core.CreateIdentifier("fs", name ?? shortName);
6062 }
6063 else // reuse parent signature in the Signature table
6064 {
6065 id = new Identifier(AccessModifier.Section, parentSignature);
6066 }
6067 }
6068
6069 var isSameId = String.Equals(id.Id, parentSignature, StringComparison.Ordinal);
6070 if (parentDirectorySearch)
6071 {
6072 // If searching for the parent directory, the Id attribute
6073 // value must be specified and unique.
6074 if (isSameId)
6075 {
6076 this.Core.Write(ErrorMessages.UniqueFileSearchIdRequired(sourceLineNumbers, parentSignature, node.Name.LocalName));
6077 }
6078 }
6079 else if (parentDepth > 1)
6080 {
6081 // Otherwise, if the depth > 1 the Id must be absent or the same
6082 // as the parent DirectorySearch if AssignToProperty is not set.
6083 if (!isSameId)
6084 {
6085 this.Core.Write(ErrorMessages.IllegalSearchIdForParentDepth(sourceLineNumbers, id.Id, parentSignature));
6086 }
6087 }
6088
6089 this.Core.ParseForExtensionElements(node);
6090
6091 if (!this.Core.EncounteredError)
6092 {
6093 var symbol = this.Core.AddSymbol(new SignatureSymbol(sourceLineNumbers, id)
6094 {
6095 FileName = name ?? shortName,
6096 MinVersion = minVersion,
6097 MaxVersion = maxVersion,
6098 Languages = languages
6099 });
6100
6101 if (CompilerConstants.IntegerNotSet != minSize)
6102 {
6103 symbol.MinSize = minSize;
6104 }
6105
6106 if (CompilerConstants.IntegerNotSet != maxSize)
6107 {
6108 symbol.MaxSize = maxSize;
6109 }
6110
6111 if (CompilerConstants.IntegerNotSet != minDate)
6112 {
6113 symbol.MinDate = minDate;
6114 }
6115
6116 if (CompilerConstants.IntegerNotSet != maxDate)
6117 {
6118 symbol.MaxDate = maxDate;
6119 }
6120
6121 // Create a DrLocator row to associate the file with a directory
6122 // when a different identifier is specified for the FileSearch.
6123 if (!isSameId)
6124 {
6125 if (parentDirectorySearch)
6126 {
6127 // Creates the DrLocator row for the directory search while
6128 // the parent DirectorySearch creates the file locator row.
6129 this.Core.AddSymbol(new DrLocatorSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, parentSignature, id.Id, String.Empty))
6130 {
6131 SignatureRef = parentSignature,
6132 Parent = id.Id
6133 });
6134 }
6135 else
6136 {
6137 this.Core.AddSymbol(new DrLocatorSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, id.Id, parentSignature, String.Empty))
6138 {
6139 SignatureRef = id.Id,
6140 Parent = parentSignature
6141 });
6142 }
6143 }
6144 }
6145
6146 return id.Id; // the id of the FileSearch element is its signature
6147 }
6148
6149
6150 /// <summary>
6151 /// Parses a fragment element.
6152 /// </summary>
6153 /// <param name="node">Element to parse.</param>
6154 private void ParseFragmentElement(XElement node)
6155 {
6156 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
6157 Identifier id = null;
6158
6159 this.activeName = null;
6160 this.activeLanguage = null;
6161
6162 foreach (var attrib in node.Attributes())
6163 {
6164 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
6165 {
6166 switch (attrib.Name.LocalName)
6167 {
6168 case "Id":
6169 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
6170 break;
6171 default:
6172 this.Core.UnexpectedAttribute(node, attrib);
6173 break;
6174 }
6175 }
6176 else
6177 {
6178 this.Core.ParseExtensionAttribute(node, attrib);
6179 }
6180 }
6181
6182 // NOTE: Id is not required for Fragments, this is a departure from the normal run of the mill processing.
6183
6184 this.Core.CreateActiveSection(id?.Id, SectionType.Fragment, this.Context.CompilationId);
6185
6186 var featureDisplay = 0;
6187 foreach (var child in node.Elements())
6188 {
6189 if (CompilerCore.WixNamespace == child.Name.Namespace)
6190 {
6191 switch (child.Name.LocalName)
6192 {
6193 case "_locDefinition":
6194 break;
6195 case "AdminExecuteSequence":
6196 this.ParseSequenceElement(child, SequenceTable.AdminExecuteSequence);
6197 break;
6198 case "AdminUISequence":
6199 this.ParseSequenceElement(child, SequenceTable.AdminUISequence);
6200 break;
6201 case "AdvertiseExecuteSequence":
6202 this.ParseSequenceElement(child, SequenceTable.AdvertiseExecuteSequence);
6203 break;
6204 case "InstallExecuteSequence":
6205 this.ParseSequenceElement(child, SequenceTable.InstallExecuteSequence);
6206 break;
6207 case "InstallUISequence":
6208 this.ParseSequenceElement(child, SequenceTable.InstallUISequence);
6209 break;
6210 case "AppId":
6211 this.ParseAppIdElement(child, null, YesNoType.Yes, null, null, null);
6212 break;
6213 case "Binary":
6214 this.ParseBinaryElement(child);
6215 break;
6216 case "BootstrapperApplication":
6217 this.ParseBootstrapperApplicationElement(child);
6218 break;
6219 case "BootstrapperApplicationRef":
6220 this.ParseBootstrapperApplicationRefElement(child);
6221 break;
6222 case "BundleCustomData":
6223 this.ParseBundleCustomDataElement(child);
6224 break;
6225 case "BundleCustomDataRef":
6226 this.ParseBundleCustomDataRefElement(child);
6227 break;
6228 case "BundleExtension":
6229 this.ParseBundleExtensionElement(child);
6230 break;
6231 case "BundleExtensionRef":
6232 this.ParseSimpleRefElement(child, SymbolDefinitions.WixBundleExtension);
6233 break;
6234 case "ComplianceCheck":
6235 this.ParseComplianceCheckElement(child);
6236 break;
6237 case "Component":
6238 this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, CompilerConstants.IntegerNotSet, null, null);
6239 break;
6240 case "ComponentGroup":
6241 this.ParseComponentGroupElement(child, ComplexReferenceParentType.Unknown, id?.Id);
6242 break;
6243 case "Container":
6244 this.ParseContainerElement(child);
6245 break;
6246 case "CustomAction":
6247 this.ParseCustomActionElement(child);
6248 break;
6249 case "CustomActionRef":
6250 this.ParseSimpleRefElement(child, SymbolDefinitions.CustomAction);
6251 break;
6252 case "CustomTable":
6253 this.ParseCustomTableElement(child);
6254 break;
6255 case "CustomTableRef":
6256 this.ParseCustomTableRefElement(child);
6257 break;
6258 case "Directory":
6259 this.ParseDirectoryElement(child, null, CompilerConstants.IntegerNotSet, String.Empty);
6260 break;
6261 case "DirectoryRef":
6262 this.ParseDirectoryRefElement(child);
6263 break;
6264 case "EmbeddedChainer":
6265 this.ParseEmbeddedChainerElement(child);
6266 break;
6267 case "EmbeddedChainerRef":
6268 this.ParseSimpleRefElement(child, SymbolDefinitions.MsiEmbeddedChainer);
6269 break;
6270 case "EnsureTable":
6271 this.ParseEnsureTableElement(child);
6272 break;
6273 case "Feature":
6274 this.ParseFeatureElement(child, ComplexReferenceParentType.Unknown, null, ref featureDisplay);
6275 break;
6276 case "FeatureGroup":
6277 this.ParseFeatureGroupElement(child, ComplexReferenceParentType.Unknown, id?.Id);
6278 break;
6279 case "FeatureRef":
6280 this.ParseFeatureRefElement(child, ComplexReferenceParentType.Unknown, null);
6281 break;
6282 case "Icon":
6283 this.ParseIconElement(child);
6284 break;
6285 case "Media":
6286 this.ParseMediaElement(child, null);
6287 break;
6288 case "MediaTemplate":
6289 this.ParseMediaTemplateElement(child, null);
6290 break;
6291 case "Launch":
6292 this.ParseLaunchElement(child);
6293 break;
6294 case "PackageGroup":
6295 this.ParsePackageGroupElement(child);
6296 break;
6297 case "PackageCertificates":
6298 case "PatchCertificates":
6299 this.ParseCertificatesElement(child);
6300 break;
6301 case "PatchFamily":
6302 this.ParsePatchFamilyElement(child, ComplexReferenceParentType.Unknown, id.Id);
6303 break;
6304 case "PatchFamilyGroup":
6305 this.ParsePatchFamilyGroupElement(child, ComplexReferenceParentType.Unknown, id.Id);
6306 break;
6307 case "PatchFamilyGroupRef":
6308 this.ParsePatchFamilyGroupRefElement(child, ComplexReferenceParentType.Unknown, id.Id);
6309 break;
6310 case "PayloadGroup":
6311 this.ParsePayloadGroupElement(child, ComplexReferenceParentType.Unknown, null);
6312 break;
6313 case "Property":
6314 this.ParsePropertyElement(child);
6315 break;
6316 case "PropertyRef":
6317 this.ParseSimpleRefElement(child, SymbolDefinitions.Property);
6318 break;
6319 case "RelatedBundle":
6320 this.ParseRelatedBundleElement(child);
6321 break;
6322 case "Requires":
6323 this.ParseRequiresElement(child, null);
6324 break;
6325 case "SetDirectory":
6326 this.ParseSetDirectoryElement(child);
6327 break;
6328 case "SetProperty":
6329 this.ParseSetPropertyElement(child);
6330 break;
6331 case "SetVariable":
6332 this.ParseSetVariableElement(child);
6333 break;
6334 case "SetVariableRef":
6335 this.ParseSimpleRefElement(child, SymbolDefinitions.WixSetVariable);
6336 break;
6337 case "SFPCatalog":
6338 string parentName = null;
6339 this.ParseSFPCatalogElement(child, ref parentName);
6340 break;
6341 case "StandardDirectory":
6342 this.ParseStandardDirectoryElement(child);
6343 break;
6344 case "UI":
6345 this.ParseUIElement(child);
6346 break;
6347 case "UIRef":
6348 this.ParseSimpleRefElement(child, SymbolDefinitions.WixUI);
6349 break;
6350 case "Upgrade":
6351 this.ParseUpgradeElement(child);
6352 break;
6353 case "Variable":
6354 this.ParseVariableElement(child);
6355 break;
6356 case "WixVariable":
6357 this.ParseWixVariableElement(child);
6358 break;
6359 default:
6360 this.Core.UnexpectedElement(node, child);
6361 break;
6362 }
6363 }
6364 else
6365 {
6366 this.Core.ParseExtensionElement(node, child);
6367 }
6368 }
6369
6370 if (!this.Core.EncounteredError && null != id)
6371 {
6372 this.Core.AddSymbol(new WixFragmentSymbol(sourceLineNumbers, id));
6373 }
6374 }
6375
6376 /// <summary>
6377 /// Parses a launch condition element.
6378 /// </summary>
6379 /// <param name="node">Element to parse.</param>
6380 private void ParseLaunchElement(XElement node)
6381 {
6382 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
6383 string condition = null;
6384 string message = null;
6385
6386 foreach (var attrib in node.Attributes())
6387 {
6388 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
6389 {
6390 switch (attrib.Name.LocalName)
6391 {
6392 case "Condition":
6393 condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
6394 break;
6395 case "Message":
6396 message = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
6397 break;
6398 default:
6399 this.Core.UnexpectedAttribute(node, attrib);
6400 break;
6401 }
6402 }
6403 else
6404 {
6405 this.Core.ParseExtensionAttribute(node, attrib);
6406 }
6407 }
6408
6409 if (String.IsNullOrEmpty(condition))
6410 {
6411 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Condition"));
6412 }
6413
6414 if (String.IsNullOrEmpty(message))
6415 {
6416 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Message"));
6417 }
6418
6419
6420 this.Core.ParseForExtensionElements(node);
6421
6422 if (!this.Core.EncounteredError)
6423 {
6424 this.Core.AddSymbol(new LaunchConditionSymbol(sourceLineNumbers)
6425 {
6426 Condition = condition,
6427 Description = message
6428 });
6429 }
6430 }
6431
6432 /// <summary>
6433 /// Parses a IniFile element.
6434 /// </summary>
6435 /// <param name="node">Element to parse.</param>
6436 /// <param name="componentId">Identifier of the parent component.</param>
6437 private void ParseIniFileElement(XElement node, string componentId)
6438 {
6439 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
6440 Identifier id = null;
6441 IniFileActionType? action = null;
6442 string directory = null;
6443 string key = null;
6444 string name = null;
6445 string section = null;
6446 string shortName = null;
6447 string value = null;
6448
6449 foreach (var attrib in node.Attributes())
6450 {
6451 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
6452 {
6453 switch (attrib.Name.LocalName)
6454 {
6455 case "Id":
6456 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
6457 break;
6458 case "Action":
6459 var actionValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
6460 switch (actionValue)
6461 {
6462 case "addLine":
6463 action = IniFileActionType.AddLine;
6464 break;
6465 case "addTag":
6466 action = IniFileActionType.AddTag;
6467 break;
6468 case "removeLine":
6469 action = IniFileActionType.RemoveLine;
6470 break;
6471 case "removeTag":
6472 action = IniFileActionType.RemoveTag;
6473 break;
6474 case "": // error case handled by GetAttributeValue()
6475 break;
6476 default:
6477 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Action", actionValue, "addLine", "addTag", "createLine", "removeLine", "removeTag"));
6478 break;
6479 }
6480 break;
6481 case "Directory":
6482 directory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
6483 break;
6484 case "Key":
6485 key = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
6486 break;
6487 case "Name":
6488 name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false);
6489 break;
6490 case "Section":
6491 section = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
6492 break;
6493 case "ShortName":
6494 shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false);
6495 break;
6496 case "Value":
6497 value = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
6498 break;
6499 default:
6500 this.Core.UnexpectedAttribute(node, attrib);
6501 break;
6502 }
6503 }
6504 else
6505 {
6506 this.Core.ParseExtensionAttribute(node, attrib);
6507 }
6508 }
6509
6510 if (!action.HasValue)
6511 {
6512 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Action"));
6513 }
6514 else if (IniFileActionType.AddLine == action || IniFileActionType.AddTag == action || IniFileActionType.CreateLine == action)
6515 {
6516 if (null == value)
6517 {
6518 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value"));
6519 }
6520 }
6521
6522 if (null == key)
6523 {
6524 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key"));
6525 }
6526
6527 if (null == name)
6528 {
6529 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
6530 }
6531
6532 if (null == section)
6533 {
6534 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Section"));
6535 }
6536
6537 if (null == id)
6538 {
6539 id = this.Core.CreateIdentifier("ini", directory, name ?? shortName, section, key, name);
6540 }
6541
6542 this.Core.ParseForExtensionElements(node);
6543
6544 if (!this.Core.EncounteredError)
6545 {
6546 this.Core.AddSymbol(new IniFileSymbol(sourceLineNumbers, id)
6547 {
6548 FileName = name,
6549 ShortFileName = shortName,
6550 DirProperty = directory,
6551 Section = section,
6552 Key = key,
6553 Value = value,
6554 Action = action.Value,
6555 ComponentRef = componentId
6556 });
6557 }
6558 }
6559
6560 /// <summary>
6561 /// Parses an IniFile search element.
6562 /// </summary>
6563 /// <param name="node">Element to parse.</param>
6564 /// <returns>Signature for search element.</returns>
6565 private string ParseIniFileSearchElement(XElement node)
6566 {
6567 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
6568 Identifier id = null;
6569 var field = CompilerConstants.IntegerNotSet;
6570 string key = null;
6571 string name = null;
6572 string section = null;
6573 string shortName = null;
6574 string signature = null;
6575 var type = 1; // default is file
6576
6577 foreach (var attrib in node.Attributes())
6578 {
6579 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
6580 {
6581 switch (attrib.Name.LocalName)
6582 {
6583 case "Id":
6584 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
6585 break;
6586 case "Field":
6587 field = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue);
6588 break;
6589 case "Key":
6590 key = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
6591 break;
6592 case "Name":
6593 name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false);
6594 break;
6595 case "Section":
6596 section = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
6597 break;
6598 case "ShortName":
6599 shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false);
6600 break;
6601 case "Type":
6602 var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
6603 switch (typeValue)
6604 {
6605 case "directory":
6606 type = 0;
6607 break;
6608 case "file":
6609 type = 1;
6610 break;
6611 case "raw":
6612 type = 2;
6613 break;
6614 case "":
6615 break;
6616 default:
6617 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Type", typeValue, "directory", "file", "registry"));
6618 break;
6619 }
6620 break;
6621 default:
6622 this.Core.UnexpectedAttribute(node, attrib);
6623 break;
6624 }
6625 }
6626 else
6627 {
6628 this.Core.ParseExtensionAttribute(node, attrib);
6629 }
6630 }
6631
6632 if (null == key)
6633 {
6634 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key"));
6635 }
6636
6637 if (null == name)
6638 {
6639 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
6640 }
6641
6642 if (null == section)
6643 {
6644 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Section"));
6645 }
6646
6647 if (null == id)
6648 {
6649 id = this.Core.CreateIdentifier("ini", name, section, key, field.ToString(), type.ToString());
6650 }
6651
6652 signature = id.Id;
6653
6654 var oneChild = false;
6655 foreach (var child in node.Elements())
6656 {
6657 if (CompilerCore.WixNamespace == child.Name.Namespace)
6658 {
6659 var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
6660 switch (child.Name.LocalName)
6661 {
6662 case "DirectorySearch":
6663 if (oneChild)
6664 {
6665 this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName));
6666 }
6667 oneChild = true;
6668
6669 // directorysearch parentage should work like directory element, not the rest of the signature type because of the DrLocator.Parent column
6670 signature = this.ParseDirectorySearchElement(child, id.Id);
6671 break;
6672 case "DirectorySearchRef":
6673 if (oneChild)
6674 {
6675 this.Core.Write(ErrorMessages.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName));
6676 }
6677 oneChild = true;
6678 signature = this.ParseDirectorySearchRefElement(child, id.Id);
6679 break;
6680 case "FileSearch":
6681 if (oneChild)
6682 {
6683 this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName));
6684 }
6685 oneChild = true;
6686 signature = this.ParseFileSearchElement(child, id.Id, false, CompilerConstants.IntegerNotSet);
6687 id = new Identifier(AccessModifier.Section, signature); // FileSearch signatures override parent signatures
6688 break;
6689 case "FileSearchRef":
6690 if (oneChild)
6691 {
6692 this.Core.Write(ErrorMessages.TooManySearchElements(sourceLineNumbers, node.Name.LocalName));
6693 }
6694 oneChild = true;
6695 var newId = this.ParseSimpleRefElement(child, SymbolDefinitions.Signature); // FileSearch signatures override parent signatures
6696 id = new Identifier(AccessModifier.Section, newId);
6697 signature = null;
6698 break;
6699 default:
6700 this.Core.UnexpectedElement(node, child);
6701 break;
6702 }
6703 }
6704 else
6705 {
6706 this.Core.ParseExtensionElement(node, child);
6707 }
6708 }
6709
6710 if (!this.Core.EncounteredError)
6711 {
6712 var symbol = this.Core.AddSymbol(new IniLocatorSymbol(sourceLineNumbers, id)
6713 {
6714 FileName = name,
6715 ShortFileName = shortName,
6716 Section = section,
6717 Key = key,
6718 Type = type
6719 });
6720
6721 if (CompilerConstants.IntegerNotSet != field)
6722 {
6723 symbol.Field = field;
6724 }
6725 }
6726
6727 return signature;
6728 }
6729
6730 /// <summary>
6731 /// Parses an isolated component element.
6732 /// </summary>
6733 /// <param name="node">Element to parse.</param>
6734 /// <param name="componentId">Identifier of parent component.</param>
6735 private void ParseIsolateComponentElement(XElement node, string componentId)
6736 {
6737 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
6738 string shared = null;
6739
6740 foreach (var attrib in node.Attributes())
6741 {
6742 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
6743 {
6744 switch (attrib.Name.LocalName)
6745 {
6746 case "Shared":
6747 shared = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
6748 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Component, shared);
6749 break;
6750 default:
6751 this.Core.UnexpectedAttribute(node, attrib);
6752 break;
6753 }
6754 }
6755 else
6756 {
6757 this.Core.ParseExtensionAttribute(node, attrib);
6758 }
6759 }
6760
6761 if (null == shared)
6762 {
6763 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Shared"));
6764 }
6765
6766 this.Core.ParseForExtensionElements(node);
6767
6768 if (!this.Core.EncounteredError)
6769 {
6770 this.Core.AddSymbol(new IsolatedComponentSymbol(sourceLineNumbers)
6771 {
6772 SharedComponentRef = shared,
6773 ApplicationComponentRef = componentId
6774 });
6775 }
6776 }
6777
6778 /// <summary>
6779 /// Parses a PatchCertificates or PackageCertificates element.
6780 /// </summary>
6781 /// <param name="node">The element to parse.</param>
6782 private void ParseCertificatesElement(XElement node)
6783 {
6784 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
6785
6786 // no attributes are supported for this element
6787 foreach (var attrib in node.Attributes())
6788 {
6789 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
6790 {
6791 this.Core.UnexpectedAttribute(node, attrib);
6792 }
6793 else
6794 {
6795 this.Core.ParseExtensionAttribute(node, attrib);
6796 }
6797 }
6798
6799 foreach (var child in node.Elements())
6800 {
6801 if (CompilerCore.WixNamespace == child.Name.Namespace)
6802 {
6803 switch (child.Name.LocalName)
6804 {
6805 case "DigitalCertificate":
6806 var name = this.ParseDigitalCertificateElement(child);
6807
6808 if (!this.Core.EncounteredError)
6809 {
6810 if ("PatchCertificates" == node.Name.LocalName)
6811 {
6812 this.Core.AddSymbol(new MsiPatchCertificateSymbol(sourceLineNumbers)
6813 {
6814 PatchCertificate = name,
6815 DigitalCertificateRef = name,
6816 });
6817 }
6818 else
6819 {
6820 this.Core.AddSymbol(new MsiPackageCertificateSymbol(sourceLineNumbers)
6821 {
6822 PackageCertificate = name,
6823 DigitalCertificateRef = name,
6824 });
6825 }
6826 }
6827 break;
6828 default:
6829 this.Core.UnexpectedElement(node, child);
6830 break;
6831 }
6832 }
6833 else
6834 {
6835 this.Core.ParseExtensionElement(node, child);
6836 }
6837 }
6838 }
6839
6840 /// <summary>
6841 /// Parses an digital certificate element.
6842 /// </summary>
6843 /// <param name="node">Element to parse.</param>
6844 /// <returns>The identifier of the certificate.</returns>
6845 private string ParseDigitalCertificateElement(XElement node)
6846 {
6847 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
6848 Identifier id = null;
6849 string sourceFile = null;
6850
6851 foreach (var attrib in node.Attributes())
6852 {
6853 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
6854 {
6855 switch (attrib.Name.LocalName)
6856 {
6857 case "Id":
6858 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
6859 break;
6860 case "SourceFile":
6861 sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
6862 break;
6863 default:
6864 this.Core.UnexpectedAttribute(node, attrib);
6865 break;
6866 }
6867 }
6868 else
6869 {
6870 this.Core.ParseExtensionAttribute(node, attrib);
6871 }
6872 }
6873
6874 if (null == id)
6875 {
6876 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
6877 id = Identifier.Invalid;
6878 }
6879 else if (40 < id.Id.Length)
6880 {
6881 this.Core.Write(ErrorMessages.StreamNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, id.Id.Length, 40));
6882
6883 // No need to check for modularization problems since DigitalSignature and thus DigitalCertificate
6884 // currently have no usage in merge modules.
6885 }
6886
6887 if (null == sourceFile)
6888 {
6889 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile"));
6890 }
6891
6892 this.Core.ParseForExtensionElements(node);
6893
6894 if (!this.Core.EncounteredError)
6895 {
6896 this.Core.AddSymbol(new MsiDigitalCertificateSymbol(sourceLineNumbers, id)
6897 {
6898 CertData = sourceFile
6899 });
6900 }
6901
6902 return id.Id;
6903 }
6904
6905 /// <summary>
6906 /// Parses an digital signature element.
6907 /// </summary>
6908 /// <param name="node">Element to parse.</param>
6909 /// <param name="diskId">Disk id inherited from parent media.</param>
6910 private void ParseDigitalSignatureElement(XElement node, string diskId)
6911 {
6912 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
6913 string certificateId = null;
6914 string sourceFile = null;
6915
6916 foreach (var attrib in node.Attributes())
6917 {
6918 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
6919 {
6920 switch (attrib.Name.LocalName)
6921 {
6922 case "SourceFile":
6923 sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
6924 break;
6925 default:
6926 this.Core.UnexpectedAttribute(node, attrib);
6927 break;
6928 }
6929 }
6930 else
6931 {
6932 this.Core.ParseExtensionAttribute(node, attrib);
6933 }
6934 }
6935
6936 // sanity check for debug to ensure the stream name will not be a problem
6937 if (null != sourceFile)
6938 {
6939 Debug.Assert(62 >= "MsiDigitalSignature.Media.".Length + diskId.Length);
6940 }
6941
6942 foreach (var child in node.Elements())
6943 {
6944 if (CompilerCore.WixNamespace == child.Name.Namespace)
6945 {
6946 switch (child.Name.LocalName)
6947 {
6948 case "DigitalCertificate":
6949 certificateId = this.ParseDigitalCertificateElement(child);
6950 break;
6951 default:
6952 this.Core.UnexpectedElement(node, child);
6953 break;
6954 }
6955 }
6956 else
6957 {
6958 this.Core.ParseExtensionElement(node, child);
6959 }
6960 }
6961
6962 if (null == certificateId)
6963 {
6964 this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "DigitalCertificate"));
6965 }
6966
6967 if (!this.Core.EncounteredError)
6968 {
6969 this.Core.AddSymbol(new MsiDigitalSignatureSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, "Media", diskId))
6970 {
6971 Table = "Media",
6972 SignObject = diskId,
6973 DigitalCertificateRef = certificateId,
6974 Hash = sourceFile
6975 });
6976 }
6977 }
6978
6979 /// <summary>
6980 /// Parses a MajorUpgrade element.
6981 /// </summary>
6982 /// <param name="node">The element to parse.</param>
6983 /// <param name="contextValues">The current context.</param>
6984 private void ParseMajorUpgradeElement(XElement node, IDictionary<string, string> contextValues)
6985 {
6986 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
6987 var migrateFeatures = true;
6988 var ignoreRemoveFailure = false;
6989 var allowDowngrades = false;
6990 var allowSameVersionUpgrades = false;
6991 var blockUpgrades = false;
6992 string downgradeErrorMessage = null;
6993 string disallowUpgradeErrorMessage = null;
6994 string removeFeatures = null;
6995 string schedule = null;
6996
6997 var upgradeCode = contextValues["UpgradeCode"];
6998 if (String.IsNullOrEmpty(upgradeCode))
6999 {
7000 this.Core.Write(ErrorMessages.ParentElementAttributeRequired(sourceLineNumbers, "Package", "UpgradeCode", node.Name.LocalName));
7001 }
7002
7003 var productVersion = contextValues["ProductVersion"];
7004 if (String.IsNullOrEmpty(productVersion))
7005 {
7006 this.Core.Write(ErrorMessages.ParentElementAttributeRequired(sourceLineNumbers, "Package", "Version", node.Name.LocalName));
7007 }
7008
7009 var productLanguage = contextValues["ProductLanguage"];
7010
7011 foreach (var attrib in node.Attributes())
7012 {
7013 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
7014 {
7015 switch (attrib.Name.LocalName)
7016 {
7017 case "AllowDowngrades":
7018 allowDowngrades = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
7019 break;
7020 case "AllowSameVersionUpgrades":
7021 allowSameVersionUpgrades = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
7022 break;
7023 case "Disallow":
7024 blockUpgrades = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
7025 break;
7026 case "DowngradeErrorMessage":
7027 downgradeErrorMessage = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
7028 break;
7029 case "DisallowUpgradeErrorMessage":
7030 disallowUpgradeErrorMessage = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
7031 break;
7032 case "MigrateFeatures":
7033 migrateFeatures = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib));
7034 break;
7035 case "IgnoreLanguage":
7036 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
7037 {
7038 productLanguage = null;
7039 }
7040 break;
7041 case "IgnoreRemoveFailure":
7042 ignoreRemoveFailure = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib));
7043 break;
7044 case "RemoveFeatures":
7045 removeFeatures = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
7046 break;
7047 case "Schedule":
7048 schedule = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
7049 break;
7050 default:
7051 this.Core.UnexpectedAttribute(node, attrib);
7052 break;
7053 }
7054 }
7055 else
7056 {
7057 this.Core.ParseExtensionAttribute(node, attrib);
7058 }
7059 }
7060
7061 this.Core.ParseForExtensionElements(node);
7062
7063 if (!allowDowngrades && String.IsNullOrEmpty(downgradeErrorMessage))
7064 {
7065 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DowngradeErrorMessage", "AllowDowngrades", "yes", true));
7066 }
7067
7068 if (allowDowngrades && !String.IsNullOrEmpty(downgradeErrorMessage))
7069 {
7070 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DowngradeErrorMessage", "AllowDowngrades", "yes"));
7071 }
7072
7073 if (allowDowngrades && allowSameVersionUpgrades)
7074 {
7075 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "AllowSameVersionUpgrades", "AllowDowngrades", "yes"));
7076 }
7077
7078 if (blockUpgrades && String.IsNullOrEmpty(disallowUpgradeErrorMessage))
7079 {
7080 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisallowUpgradeErrorMessage", "Disallow", "yes", true));
7081 }
7082
7083 if (!blockUpgrades && !String.IsNullOrEmpty(disallowUpgradeErrorMessage))
7084 {
7085 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DisallowUpgradeErrorMessage", "Disallow", "yes"));
7086 }
7087
7088 if (!this.Core.EncounteredError)
7089 {
7090 // create the row that performs the upgrade (or downgrade)
7091 var symbol = this.Core.AddSymbol(new UpgradeSymbol(sourceLineNumbers)
7092 {
7093 UpgradeCode = upgradeCode,
7094 Remove = removeFeatures,
7095 MigrateFeatures = migrateFeatures,
7096 IgnoreRemoveFailures = ignoreRemoveFailure,
7097 ActionProperty = WixUpgradeConstants.UpgradeDetectedProperty
7098 });
7099
7100 if (allowDowngrades)
7101 {
7102 symbol.VersionMin = "0";
7103 symbol.Language = productLanguage;
7104 symbol.VersionMinInclusive = true;
7105 }
7106 else
7107 {
7108 symbol.VersionMax = productVersion;
7109 symbol.Language = productLanguage;
7110 symbol.VersionMaxInclusive = allowSameVersionUpgrades;
7111 }
7112
7113 // Add launch condition that blocks upgrades
7114 if (blockUpgrades)
7115 {
7116 this.Core.AddSymbol(new LaunchConditionSymbol(sourceLineNumbers)
7117 {
7118 Condition = WixUpgradeConstants.UpgradePreventedCondition,
7119 Description = downgradeErrorMessage
7120 });
7121 }
7122
7123 // now create the Upgrade row and launch conditions to prevent downgrades (unless explicitly permitted)
7124 if (!allowDowngrades)
7125 {
7126 this.Core.AddSymbol(new UpgradeSymbol(sourceLineNumbers)
7127 {
7128 UpgradeCode = upgradeCode,
7129 VersionMin = productVersion,
7130 Language = productLanguage,
7131 OnlyDetect = true,
7132 IgnoreRemoveFailures = ignoreRemoveFailure,
7133 ActionProperty = WixUpgradeConstants.DowngradeDetectedProperty
7134 });
7135
7136 this.Core.AddSymbol(new LaunchConditionSymbol(sourceLineNumbers)
7137 {
7138 Condition = WixUpgradeConstants.DowngradePreventedCondition,
7139 Description = downgradeErrorMessage
7140 });
7141 }
7142
7143 // finally, schedule RemoveExistingProducts
7144 string after = null;
7145 switch (schedule)
7146 {
7147 case null:
7148 case "afterInstallValidate":
7149 after = "InstallValidate";
7150 break;
7151 case "afterInstallInitialize":
7152 after = "InstallInitialize";
7153 break;
7154 case "afterInstallExecute":
7155 after = "InstallExecute";
7156 break;
7157 case "afterInstallExecuteAgain":
7158 after = "InstallExecuteAgain";
7159 break;
7160 case "afterInstallFinalize":
7161 after = "InstallFinalize";
7162 break;
7163 }
7164
7165 this.Core.ScheduleActionSymbol(sourceLineNumbers, AccessModifier.Global, SequenceTable.InstallExecuteSequence, "RemoveExistingProducts", afterAction: after);
7166 }
7167 }
7168
7169 /// <summary>
7170 /// Parses a media element.
7171 /// </summary>
7172 /// <param name="node">Element to parse.</param>
7173 /// <param name="patchId">Set to the PatchId if parsing Patch/Media element otherwise null.</param>
7174 private void ParseMediaElement(XElement node, string patchId)
7175 {
7176 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
7177 var id = CompilerConstants.IntegerNotSet;
7178 string cabinet = null;
7179 CompressionLevel? compressionLevel = null;
7180 string diskPrompt = null;
7181 string layout = null;
7182 var patch = null != patchId;
7183 string volumeLabel = null;
7184 string source = null;
7185 string symbols = null;
7186
7187 var embedCab = patch ? YesNoType.Yes : YesNoType.NotSet;
7188
7189 foreach (var attrib in node.Attributes())
7190 {
7191 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
7192 {
7193 switch (attrib.Name.LocalName)
7194 {
7195 case "Id":
7196 id = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue);
7197 break;
7198 case "Cabinet":
7199 cabinet = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
7200 break;
7201 case "CompressionLevel":
7202 compressionLevel = this.ParseCompressionLevel(sourceLineNumbers, attrib);
7203 break;
7204 case "DiskPrompt":
7205 diskPrompt = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
7206 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, "DiskPrompt"); // ensure the output has a DiskPrompt Property defined
7207 break;
7208 case "EmbedCab":
7209 embedCab = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
7210 break;
7211 case "Layout":
7212 layout = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
7213 break;
7214 case "VolumeLabel":
7215 volumeLabel = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
7216 break;
7217 case "Source":
7218 source = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
7219 break;
7220 default:
7221 this.Core.UnexpectedAttribute(node, attrib);
7222 break;
7223 }
7224 }
7225 else
7226 {
7227 this.Core.ParseExtensionAttribute(node, attrib);
7228 }
7229 }
7230
7231 if (CompilerConstants.IntegerNotSet == id)
7232 {
7233 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
7234 id = CompilerConstants.IllegalInteger;
7235 }
7236
7237 if (YesNoType.IllegalValue != embedCab)
7238 {
7239 if (YesNoType.Yes == embedCab)
7240 {
7241 if (null == cabinet)
7242 {
7243 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Cabinet", "EmbedCab", "yes"));
7244 }
7245 else
7246 {
7247 if (62 < cabinet.Length)
7248 {
7249 this.Core.Write(ErrorMessages.MediaEmbeddedCabinetNameTooLong(sourceLineNumbers, node.Name.LocalName, "Cabinet", cabinet, cabinet.Length));
7250 }
7251
7252 cabinet = String.Concat("#", cabinet);
7253 }
7254 }
7255 else // external cabinet file
7256 {
7257 // external cabinet files must use 8.3 filenames
7258 if (!String.IsNullOrEmpty(cabinet) && !this.Core.IsValidLongFilename(cabinet) && !Common.ContainsValidBinderVariable(cabinet))
7259 {
7260 this.Core.Write(WarningMessages.MediaExternalCabinetFilenameIllegal(sourceLineNumbers, node.Name.LocalName, "Cabinet", cabinet));
7261 }
7262 }
7263 }
7264
7265 if (compressionLevel.HasValue && String.IsNullOrEmpty(cabinet))
7266 {
7267 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Cabinet", "CompressionLevel"));
7268 }
7269
7270 if (patch)
7271 {
7272 // Default Source to a form of the Patch Id if none is specified.
7273 if (null == source)
7274 {
7275 source = String.Concat("_", new Guid(patchId).ToString("N", CultureInfo.InvariantCulture).ToUpper(CultureInfo.InvariantCulture));
7276 }
7277 }
7278
7279 foreach (var child in node.Elements())
7280 {
7281 var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
7282 if (CompilerCore.WixNamespace == child.Name.Namespace)
7283 {
7284 switch (child.Name.LocalName)
7285 {
7286 case "DigitalSignature":
7287 if (YesNoType.Yes == embedCab)
7288 {
7289 this.Core.Write(ErrorMessages.SignedEmbeddedCabinet(childSourceLineNumbers));
7290 }
7291 else if (null == cabinet)
7292 {
7293 this.Core.Write(ErrorMessages.ExpectedSignedCabinetName(childSourceLineNumbers));
7294 }
7295 else
7296 {
7297 this.ParseDigitalSignatureElement(child, id.ToString(CultureInfo.InvariantCulture.NumberFormat));
7298 }
7299 break;
7300 case "PatchBaseline":
7301 if (patch)
7302 {
7303 this.ParsePatchBaselineElement(child, id);
7304 }
7305 else
7306 {
7307 this.Core.UnexpectedElement(node, child);
7308 }
7309 break;
7310 case "SymbolPath":
7311 if (null != symbols)
7312 {
7313 symbols += "" + this.ParseSymbolPathElement(child);
7314 }
7315 else
7316 {
7317 symbols = this.ParseSymbolPathElement(child);
7318 }
7319 break;
7320 default:
7321 this.Core.UnexpectedElement(node, child);
7322 break;
7323 }
7324 }
7325 else
7326 {
7327 this.Core.ParseExtensionElement(node, child);
7328 }
7329 }
7330
7331 // add the row to the section
7332 if (!this.Core.EncounteredError)
7333 {
7334 this.Core.AddSymbol(new MediaSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, id))
7335 {
7336 DiskId = id,
7337 DiskPrompt = diskPrompt,
7338 Cabinet = cabinet,
7339 VolumeLabel = volumeLabel,
7340 Source = source, // the Source column is only set when creating a patch
7341 CompressionLevel = compressionLevel,
7342 Layout = layout
7343 });
7344
7345 if (null != symbols)
7346 {
7347 this.Core.AddSymbol(new WixDeltaPatchSymbolPathsSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, SymbolPathType.Media, id))
7348 {
7349 SymbolType = SymbolPathType.Media,
7350 SymbolId = id.ToString(CultureInfo.InvariantCulture),
7351 SymbolPaths = symbols
7352 });
7353 }
7354 }
7355 }
7356
7357 /// <summary>
7358 /// Parses a media template element.
7359 /// </summary>
7360 /// <param name="node">Element to parse.</param>
7361 /// <param name="patchId">Set to the PatchId if parsing Patch/Media element otherwise null.</param>
7362 private void ParseMediaTemplateElement(XElement node, string patchId)
7363 {
7364 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
7365 var cabinetTemplate = "cab{0}.cab";
7366 string diskPrompt = null;
7367 var patch = null != patchId;
7368 string volumeLabel = null;
7369 int? maximumUncompressedMediaSize = null;
7370 int? maximumCabinetSizeForLargeFileSplitting = null;
7371 CompressionLevel? compressionLevel = null; // this defaults to 'medium' in the MSI and Burn backends
7372
7373 var embedCab = patch ? YesNoType.Yes : YesNoType.NotSet;
7374
7375 foreach (var attrib in node.Attributes())
7376 {
7377 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
7378 {
7379 switch (attrib.Name.LocalName)
7380 {
7381 case "CabinetTemplate":
7382 var authoredCabinetTemplateValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty);
7383 if (!String.IsNullOrEmpty(authoredCabinetTemplateValue))
7384 {
7385 cabinetTemplate = authoredCabinetTemplateValue;
7386 }
7387
7388 // Create an example cabinet name using the maximum number of cabinets supported, 999.
7389 var exampleCabinetName = String.Format(cabinetTemplate, "###");
7390 if (!this.Core.IsValidLocIdentifier(exampleCabinetName))
7391 {
7392 // The example name should not match the authored template since that would nullify the
7393 // reason for having multiple cabinets. External cabinet files must also be valid file names.
7394 if (exampleCabinetName.Equals(authoredCabinetTemplateValue, StringComparison.OrdinalIgnoreCase) || !this.Core.IsValidLongFilename(exampleCabinetName, false))
7395 {
7396 this.Core.Write(ErrorMessages.InvalidCabinetTemplate(sourceLineNumbers, cabinetTemplate));
7397 }
7398 else if (!this.Core.IsValidLongFilename(exampleCabinetName) && !Common.ContainsValidBinderVariable(exampleCabinetName)) // ignore short names with wix variables because it rarely works out.
7399 {
7400 this.Core.Write(WarningMessages.MediaExternalCabinetFilenameIllegal(sourceLineNumbers, node.Name.LocalName, "CabinetTemplate", cabinetTemplate));
7401 }
7402 }
7403 break;
7404 case "CompressionLevel":
7405 compressionLevel = this.ParseCompressionLevel(sourceLineNumbers, attrib);
7406 break;
7407 case "DiskPrompt":
7408 diskPrompt = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
7409 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, "DiskPrompt"); // ensure the output has a DiskPrompt Property defined
7410 this.Core.Write(WarningMessages.ReservedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName));
7411 break;
7412 case "EmbedCab":
7413 embedCab = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
7414 break;
7415 case "VolumeLabel":
7416 volumeLabel = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
7417 this.Core.Write(WarningMessages.ReservedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName));
7418 break;
7419 case "MaximumUncompressedMediaSize":
7420 maximumUncompressedMediaSize = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int32.MaxValue);
7421 break;
7422 case "MaximumCabinetSizeForLargeFileSplitting":
7423 maximumCabinetSizeForLargeFileSplitting = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Compiler.MinValueOfMaxCabSizeForLargeFileSplitting, Compiler.MaxValueOfMaxCabSizeForLargeFileSplitting);
7424 break;
7425 default:
7426 this.Core.UnexpectedAttribute(node, attrib);
7427 break;
7428 }
7429 }
7430 else
7431 {
7432 this.Core.ParseExtensionAttribute(node, attrib);
7433 }
7434 }
7435
7436 if (YesNoType.Yes == embedCab)
7437 {
7438 cabinetTemplate = String.Concat("#", cabinetTemplate);
7439 }
7440
7441 if (!this.Core.EncounteredError)
7442 {
7443 this.Core.AddSymbol(new MediaSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, 1))
7444 {
7445 DiskId = 1
7446 });
7447
7448 this.Core.AddSymbol(new WixMediaTemplateSymbol(sourceLineNumbers)
7449 {
7450 CabinetTemplate = cabinetTemplate,
7451 VolumeLabel = volumeLabel,
7452 DiskPrompt = diskPrompt,
7453 MaximumUncompressedMediaSize = maximumUncompressedMediaSize,
7454 MaximumCabinetSizeForLargeFileSplitting = maximumCabinetSizeForLargeFileSplitting,
7455 CompressionLevel = compressionLevel
7456 });
7457
7458 //else
7459 //{
7460 // mediaTemplateRow.MaximumUncompressedMediaSize = CompilerCore.DefaultMaximumUncompressedMediaSize;
7461 //}
7462
7463 //else
7464 //{
7465 // mediaTemplateRow.MaximumCabinetSizeForLargeFileSplitting = 0; // Default value of 0 corresponds to max size of 2048 MB (i.e. 2 GB)
7466 //}
7467 }
7468 }
7469
7470 /// <summary>
7471 /// Parses a merge element.
7472 /// </summary>
7473 /// <param name="node">Element to parse.</param>
7474 /// <param name="directoryId">Identifier for parent directory.</param>
7475 /// <param name="diskId">Disk id inherited from parent directory.</param>
7476 private void ParseMergeElement(XElement node, string directoryId, int diskId)
7477 {
7478 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
7479 Identifier id = null;
7480 var configData = String.Empty;
7481 FileSymbolAttributes attributes = 0;
7482 string language = null;
7483 string sourceFile = null;
7484
7485 foreach (var attrib in node.Attributes())
7486 {
7487 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
7488 {
7489 switch (attrib.Name.LocalName)
7490 {
7491 case "Id":
7492 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
7493 break;
7494 case "DiskId":
7495 diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue);
7496 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Media, diskId.ToString(CultureInfo.InvariantCulture.NumberFormat));
7497 break;
7498 case "FileCompression":
7499 var compress = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
7500 attributes |= compress == YesNoType.Yes ? FileSymbolAttributes.Compressed : 0;
7501 attributes |= compress == YesNoType.No ? FileSymbolAttributes.Uncompressed : 0;
7502 break;
7503 case "Language":
7504 language = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue);
7505 break;
7506 case "SourceFile":
7507 sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
7508 break;
7509 default:
7510 this.Core.UnexpectedAttribute(node, attrib);
7511 break;
7512 }
7513 }
7514 else
7515 {
7516 this.Core.ParseExtensionAttribute(node, attrib);
7517 }
7518 }
7519
7520 if (null == id)
7521 {
7522 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
7523 }
7524
7525 if (null == language)
7526 {
7527 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Language"));
7528 }
7529
7530 if (null == sourceFile)
7531 {
7532 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile"));
7533 }
7534
7535 foreach (var child in node.Elements())
7536 {
7537 if (CompilerCore.WixNamespace == child.Name.Namespace)
7538 {
7539 switch (child.Name.LocalName)
7540 {
7541 case "ConfigurationData":
7542 if (0 == configData.Length)
7543 {
7544 configData = this.ParseConfigurationDataElement(child);
7545 }
7546 else
7547 {
7548 configData = String.Concat(configData, ",", this.ParseConfigurationDataElement(child));
7549 }
7550 break;
7551 default:
7552 this.Core.UnexpectedElement(node, child);
7553 break;
7554 }
7555 }
7556 else
7557 {
7558 this.Core.ParseExtensionElement(node, child);
7559 }
7560 }
7561
7562 if (!this.Core.EncounteredError)
7563 {
7564 var symbol = this.Core.AddSymbol(new WixMergeSymbol(sourceLineNumbers, id)
7565 {
7566 DirectoryRef = directoryId,
7567 SourceFile = sourceFile,
7568 DiskId = diskId,
7569 ConfigurationData = configData,
7570 FileAttributes = attributes,
7571 FeatureRef = Guid.Empty.ToString("B")
7572 });
7573
7574 symbol.Set((int)WixMergeSymbolFields.Language, language);
7575 }
7576 }
7577
7578 /// <summary>
7579 /// Parses a standard directory element.
7580 /// </summary>
7581 /// <param name="node">Element to parse.</param>
7582 private void ParseStandardDirectoryElement(XElement node)
7583 {
7584 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
7585 string id = null;
7586
7587 foreach (var attrib in node.Attributes())
7588 {
7589 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
7590 {
7591 switch (attrib.Name.LocalName)
7592 {
7593 case "Id":
7594 id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
7595 break;
7596 default:
7597 this.Core.UnexpectedAttribute(node, attrib);
7598 break;
7599 }
7600 }
7601 else
7602 {
7603 this.Core.ParseExtensionAttribute(node, attrib);
7604 }
7605 }
7606
7607 if (String.IsNullOrEmpty(id))
7608 {
7609 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
7610 }
7611 else if (!WindowsInstallerStandard.IsStandardDirectory(id))
7612 {
7613 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Id", id, String.Join(", \"", WindowsInstallerStandard.StandardDirectories().Select(d => d.Id.Id))));
7614 }
7615
7616 foreach (var child in node.Elements())
7617 {
7618 if (CompilerCore.WixNamespace == child.Name.Namespace)
7619 {
7620 switch (child.Name.LocalName)
7621 {
7622 case "Component":
7623 this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, diskId: CompilerConstants.IntegerNotSet, id, srcPath: String.Empty);
7624 break;
7625 case "Directory":
7626 this.ParseDirectoryElement(child, id, diskId: CompilerConstants.IntegerNotSet, fileSource: String.Empty);
7627 break;
7628 case "Merge":
7629 this.ParseMergeElement(child, id, diskId: CompilerConstants.IntegerNotSet);
7630 break;
7631 default:
7632 this.Core.UnexpectedElement(node, child);
7633 break;
7634 }
7635 }
7636 else
7637 {
7638 this.Core.ParseExtensionElement(node, child);
7639 }
7640 }
7641 }
7642
7643 /// <summary>
7644 /// Parses a configuration data element.
7645 /// </summary>
7646 /// <param name="node">Element to parse.</param>
7647 /// <returns>String in format "name=value" with '%', ',' and '=' hex encoded.</returns>
7648 private string ParseConfigurationDataElement(XElement node)
7649 {
7650 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
7651 string name = null;
7652 string value = null;
7653
7654 foreach (var attrib in node.Attributes())
7655 {
7656 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
7657 {
7658 switch (attrib.Name.LocalName)
7659 {
7660 case "Name":
7661 name = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
7662 break;
7663 case "Value":
7664 value = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
7665 break;
7666 default:
7667 this.Core.UnexpectedAttribute(node, attrib);
7668 break;
7669 }
7670 }
7671 else
7672 {
7673 this.Core.ParseExtensionAttribute(node, attrib);
7674 }
7675 }
7676
7677 if (null == name)
7678 {
7679 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
7680 }
7681 else // need to hex encode these characters
7682 {
7683 name = name.Replace("%", "%25");
7684 name = name.Replace("=", "%3D");
7685 name = name.Replace(",", "%2C");
7686 }
7687
7688 if (null == value)
7689 {
7690 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value"));
7691 }
7692 else // need to hex encode these characters
7693 {
7694 value = value.Replace("%", "%25");
7695 value = value.Replace("=", "%3D");
7696 value = value.Replace(",", "%2C");
7697 }
7698
7699 this.Core.ParseForExtensionElements(node);
7700
7701 return String.Concat(name, "=", value);
7702 }
7703
7704 /// <summary>
7705 /// Parses a Level element.
7706 /// </summary>
7707 /// <param name="node">Element to parse.</param>
7708 /// <param name="featureId">Id of the parent Feature element.</param>
7709 private void ParseLevelElement(XElement node, string featureId)
7710 {
7711 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
7712 string condition = null;
7713 int? level = null;
7714
7715 foreach (var attrib in node.Attributes())
7716 {
7717 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
7718 {
7719 switch (attrib.Name.LocalName)
7720 {
7721 case "Condition":
7722 condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
7723 break;
7724 case "Value":
7725 level = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue);
7726 break;
7727 default:
7728 this.Core.UnexpectedAttribute(node, attrib);
7729 break;
7730 }
7731 }
7732 else
7733 {
7734 this.Core.ParseExtensionAttribute(node, attrib);
7735 }
7736 }
7737
7738 if (!level.HasValue)
7739 {
7740 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Level"));
7741 }
7742
7743 if (String.IsNullOrEmpty(condition))
7744 {
7745 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Condition"));
7746 }
7747
7748 this.Core.ParseForExtensionElements(node);
7749
7750 if (!this.Core.EncounteredError)
7751 {
7752 if (CompilerConstants.IntegerNotSet == level)
7753 {
7754 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Level"));
7755 level = CompilerConstants.IllegalInteger;
7756 }
7757
7758 if (!this.Core.EncounteredError)
7759 {
7760 this.Core.AddSymbol(new ConditionSymbol(sourceLineNumbers)
7761 {
7762 FeatureRef = featureId,
7763 Level = level.Value,
7764 Condition = condition
7765 });
7766 }
7767 }
7768 }
7769
7770 /// <summary>
7771 /// Parses a merge reference element.
7772 /// </summary>
7773 /// <param name="node">Element to parse.</param>
7774 /// <param name="parentType">Parents complex reference type.</param>
7775 /// <param name="parentId">Identifier for parent feature or feature group.</param>
7776 private void ParseMergeRefElement(XElement node, ComplexReferenceParentType parentType, string parentId)
7777 {
7778 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
7779 string id = null;
7780 var primary = YesNoType.NotSet;
7781
7782 foreach (var attrib in node.Attributes())
7783 {
7784 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
7785 {
7786 switch (attrib.Name.LocalName)
7787 {
7788 case "Id":
7789 id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
7790 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixMerge, id);
7791 break;
7792 case "Primary":
7793 primary = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
7794 break;
7795 default:
7796 this.Core.UnexpectedAttribute(node, attrib);
7797 break;
7798 }
7799 }
7800 else
7801 {
7802 this.Core.ParseExtensionAttribute(node, attrib);
7803 }
7804 }
7805
7806 if (null == id)
7807 {
7808 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
7809 }
7810
7811 this.Core.ParseForExtensionElements(node);
7812
7813 this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.Module, id, (YesNoType.Yes == primary));
7814 }
7815
7816 /// <summary>
7817 /// Parses a mime element.
7818 /// </summary>
7819 /// <param name="node">Element to parse.</param>
7820 /// <param name="extension">Identifier for parent extension.</param>
7821 /// <param name="componentId">Identifier for parent component.</param>
7822 /// <param name="parentAdvertised">Flag if the parent element is advertised.</param>
7823 /// <returns>Content type if this is the default for the MIME type.</returns>
7824 private string ParseMIMEElement(XElement node, string extension, string componentId, YesNoType parentAdvertised)
7825 {
7826 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
7827 string classId = null;
7828 string contentType = null;
7829 var advertise = parentAdvertised;
7830 var returnContentType = YesNoType.NotSet;
7831
7832 foreach (var attrib in node.Attributes())
7833 {
7834 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
7835 {
7836 switch (attrib.Name.LocalName)
7837 {
7838 case "Advertise":
7839 advertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
7840 break;
7841 case "Class":
7842 classId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
7843 break;
7844 case "ContentType":
7845 contentType = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
7846 break;
7847 case "Default":
7848 returnContentType = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
7849 break;
7850 default:
7851 this.Core.UnexpectedAttribute(node, attrib);
7852 break;
7853 }
7854 }
7855 else
7856 {
7857 this.Core.ParseExtensionAttribute(node, attrib);
7858 }
7859 }
7860
7861 if (null == contentType)
7862 {
7863 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ContentType"));
7864 }
7865
7866 // if the advertise state has not been set, default to non-advertised
7867 if (YesNoType.NotSet == advertise)
7868 {
7869 advertise = YesNoType.No;
7870 }
7871
7872 this.Core.ParseForExtensionElements(node);
7873
7874 if (YesNoType.Yes == advertise)
7875 {
7876 if (YesNoType.Yes != parentAdvertised)
7877 {
7878 this.Core.Write(ErrorMessages.AdvertiseStateMustMatch(sourceLineNumbers, advertise.ToString(), parentAdvertised.ToString()));
7879 }
7880
7881 if (!this.Core.EncounteredError)
7882 {
7883 this.Core.AddSymbol(new MIMESymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, contentType))
7884 {
7885 ContentType = contentType,
7886 ExtensionRef = extension,
7887 CLSID = classId
7888 });
7889 }
7890 }
7891 else if (YesNoType.No == advertise)
7892 {
7893 if (YesNoType.Yes == returnContentType && YesNoType.Yes == parentAdvertised)
7894 {
7895 this.Core.Write(ErrorMessages.CannotDefaultMismatchedAdvertiseStates(sourceLineNumbers));
7896 }
7897
7898 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("MIME\\Database\\Content Type\\", contentType), "Extension", String.Concat(".", extension), componentId);
7899 if (null != classId)
7900 {
7901 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("MIME\\Database\\Content Type\\", contentType), "CLSID", classId, componentId);
7902 }
7903 }
7904
7905 return YesNoType.Yes == returnContentType ? contentType : null;
7906 }
7907
7908 /// <summary>
7909 /// Parses a patch property element.
7910 /// </summary>
7911 /// <param name="node">The element to parse.</param>
7912 /// <param name="patch">True if parsing an patch element.</param>
7913 private void ParsePatchPropertyElement(XElement node, bool patch)
7914 {
7915 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
7916 string name = null;
7917 string company = null;
7918 string value = null;
7919
7920 foreach (var attrib in node.Attributes())
7921 {
7922 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
7923 {
7924 switch (attrib.Name.LocalName)
7925 {
7926 case "Id":
7927 case "Name":
7928 name = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
7929 break;
7930 case "Company":
7931 company = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
7932 break;
7933 case "Value":
7934 value = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
7935 break;
7936 default:
7937 this.Core.UnexpectedAttribute(node, attrib);
7938 break;
7939 }
7940 }
7941 else
7942 {
7943 this.Core.ParseExtensionAttribute(node, attrib);
7944 }
7945 }
7946
7947 if (null == name)
7948 {
7949 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
7950 }
7951
7952 if (null == value)
7953 {
7954 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value"));
7955 }
7956
7957 this.Core.ParseForExtensionElements(node);
7958
7959 if (patch)
7960 {
7961 // /Patch/PatchProperty goes directly into MsiPatchMetadata table
7962 this.Core.AddSymbol(new MsiPatchMetadataSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, company, name))
7963 {
7964 Company = company,
7965 Property = name,
7966 Value = value
7967 });
7968 }
7969 else
7970 {
7971 if (null != company)
7972 {
7973 this.Core.Write(ErrorMessages.UnexpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Company"));
7974 }
7975 this.AddPrivateProperty(sourceLineNumbers, name, value);
7976 }
7977 }
7978
7979 /// <summary>
7980 /// Adds a row to the properties table.
7981 /// </summary>
7982 /// <param name="sourceLineNumbers">Source line numbers.</param>
7983 /// <param name="name">Name of the property.</param>
7984 /// <param name="value">Value of the property.</param>
7985 private void AddPrivateProperty(SourceLineNumber sourceLineNumbers, string name, string value)
7986 {
7987 if (!this.Core.EncounteredError)
7988 {
7989 this.Core.AddSymbol(new PropertySymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, name))
7990 {
7991 Value = value
7992 });
7993 }
7994 }
7995
7996 /// <summary>
7997 /// Parses a TargetProductCode element.
7998 /// </summary>
7999 /// <param name="node">The element to parse.</param>
8000 /// <returns>The id from the node.</returns>
8001 private string ParseTargetProductCodeElement(XElement node)
8002 {
8003 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
8004 string id = null;
8005
8006 foreach (var attrib in node.Attributes())
8007 {
8008 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
8009 {
8010 switch (attrib.Name.LocalName)
8011 {
8012 case "Id":
8013 id = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
8014 if (id.Length > 0 && "*" != id)
8015 {
8016 id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
8017 }
8018 break;
8019 default:
8020 this.Core.UnexpectedAttribute(node, attrib);
8021 break;
8022 }
8023 }
8024 else
8025 {
8026 this.Core.ParseExtensionAttribute(node, attrib);
8027 }
8028 }
8029
8030 if (null == id)
8031 {
8032 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
8033 }
8034
8035 this.Core.ParseForExtensionElements(node);
8036
8037 return id;
8038 }
8039
8040 /// <summary>
8041 /// Parses a ReplacePatch element.
8042 /// </summary>
8043 /// <param name="node">The element to parse.</param>
8044 /// <returns>The id from the node.</returns>
8045 private string ParseReplacePatchElement(XElement node)
8046 {
8047 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
8048 string id = null;
8049
8050 foreach (var attrib in node.Attributes())
8051 {
8052 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
8053 {
8054 switch (attrib.Name.LocalName)
8055 {
8056 case "Id":
8057 id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
8058 break;
8059 default:
8060 this.Core.UnexpectedAttribute(node, attrib);
8061 break;
8062 }
8063 }
8064 else
8065 {
8066 this.Core.ParseExtensionAttribute(node, attrib);
8067 }
8068 }
8069
8070 if (null == id)
8071 {
8072 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
8073 }
8074
8075 this.Core.ParseForExtensionElements(node);
8076
8077 return id;
8078 }
8079
8080 /// <summary>
8081 /// Parses a symbol path element.
8082 /// </summary>
8083 /// <param name="node">The element to parse.</param>
8084 /// <returns>The path from the node.</returns>
8085 private string ParseSymbolPathElement(XElement node)
8086 {
8087 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
8088 string path = null;
8089
8090 foreach (var attrib in node.Attributes())
8091 {
8092 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
8093 {
8094 switch (attrib.Name.LocalName)
8095 {
8096 case "Path":
8097 path = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
8098 break;
8099 default:
8100 this.Core.UnexpectedAttribute(node, attrib);
8101 break;
8102 }
8103 }
8104 else
8105 {
8106 this.Core.ParseExtensionAttribute(node, attrib);
8107 }
8108 }
8109
8110 if (null == path)
8111 {
8112 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Path"));
8113 }
8114
8115 this.Core.ParseForExtensionElements(node);
8116
8117 return path;
8118 }
8119
8120 /// <summary>
8121 /// Parses the All element under a PatchFamily.
8122 /// </summary>
8123 /// <param name="node">The element to parse.</param>
8124 private void ParseAllElement(XElement node)
8125 {
8126 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
8127
8128 // find unexpected attributes
8129 foreach (var attrib in node.Attributes())
8130 {
8131 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
8132 {
8133 this.Core.UnexpectedAttribute(node, attrib);
8134 }
8135 else
8136 {
8137 this.Core.ParseExtensionAttribute(node, attrib);
8138 }
8139 }
8140
8141 this.Core.ParseForExtensionElements(node);
8142
8143 // Always warn when using the All element.
8144 this.Core.Write(WarningMessages.AllChangesIncludedInPatch(sourceLineNumbers));
8145
8146 if (!this.Core.EncounteredError)
8147 {
8148 this.Core.AddSymbol(new WixPatchRefSymbol(sourceLineNumbers)
8149 {
8150 Table = "*",
8151 PrimaryKeys = "*",
8152 });
8153 }
8154 }
8155
8156 /// <summary>
8157 /// Parses all reference elements under a PatchFamily.
8158 /// </summary>
8159 /// <param name="node">The element to parse.</param>
8160 /// <param name="tableName">Table that reference was made to.</param>
8161 private void ParsePatchChildRefElement(XElement node, string tableName)
8162 {
8163 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
8164 string id = null;
8165
8166 foreach (var attrib in node.Attributes())
8167 {
8168 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
8169 {
8170 switch (attrib.Name.LocalName)
8171 {
8172 case "Id":
8173 id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
8174 break;
8175 default:
8176 this.Core.UnexpectedAttribute(node, attrib);
8177 break;
8178 }
8179 }
8180 else
8181 {
8182 this.Core.ParseExtensionAttribute(node, attrib);
8183 }
8184 }
8185
8186 if (null == id)
8187 {
8188 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
8189 }
8190
8191 this.Core.ParseForExtensionElements(node);
8192
8193 if (!this.Core.EncounteredError)
8194 {
8195 this.Core.AddSymbol(new WixPatchRefSymbol(sourceLineNumbers)
8196 {
8197 Table = tableName,
8198 PrimaryKeys = id
8199 });
8200 }
8201 }
8202
8203 /// <summary>
8204 /// Parses a PatchBaseline element.
8205 /// </summary>
8206 /// <param name="node">The element to parse.</param>
8207 /// <param name="diskId">Media index from parent element.</param>
8208 private void ParsePatchBaselineElement(XElement node, int? diskId)
8209 {
8210 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
8211 Identifier id = null;
8212 var parsedValidate = false;
8213 var validationFlags = TransformFlags.PatchTransformDefault;
8214 string baselineFile = null;
8215 string updateFile = null;
8216 string transformFile = null;
8217
8218 foreach (var attrib in node.Attributes())
8219 {
8220 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
8221 {
8222 switch (attrib.Name.LocalName)
8223 {
8224 case "Id":
8225 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
8226 break;
8227 case "DiskId":
8228 diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue);
8229 break;
8230 case "BaselineFile":
8231 baselineFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
8232 break;
8233 case "UpdateFile":
8234 updateFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
8235 break;
8236 case "TransformFile":
8237 transformFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
8238 break;
8239 default:
8240 this.Core.UnexpectedAttribute(node, attrib);
8241 break;
8242 }
8243 }
8244 else
8245 {
8246 this.Core.ParseExtensionAttribute(node, attrib);
8247 }
8248 }
8249
8250 if (null == id)
8251 {
8252 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
8253 id = Identifier.Invalid;
8254 }
8255 else if (27 < id.Id.Length)
8256 {
8257 this.Core.Write(ErrorMessages.IdentifierTooLongError(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, 27));
8258 }
8259
8260 if (!String.IsNullOrEmpty(baselineFile) || !String.IsNullOrEmpty(updateFile))
8261 {
8262 if (String.IsNullOrEmpty(baselineFile))
8263 {
8264 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "BaselineFile", "UpdateFile"));
8265 }
8266
8267 if (String.IsNullOrEmpty(updateFile))
8268 {
8269 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "UpdateFile", "BaselineFile"));
8270 }
8271
8272 if (!String.IsNullOrEmpty(transformFile))
8273 {
8274 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "TransformFile", !String.IsNullOrEmpty(baselineFile) ? "BaselineFile" : "UpdateFile"));
8275 }
8276 }
8277 else if (String.IsNullOrEmpty(transformFile))
8278 {
8279 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "BaselineFile", "TransformFile", true));
8280 }
8281
8282 foreach (var child in node.Elements())
8283 {
8284 if (CompilerCore.WixNamespace == child.Name.Namespace)
8285 {
8286 switch (child.Name.LocalName)
8287 {
8288 case "Validate":
8289 if (parsedValidate)
8290 {
8291 var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
8292 this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, child.Name.LocalName));
8293 }
8294 else
8295 {
8296 this.ParseValidateElement(child, ref validationFlags);
8297 parsedValidate = true;
8298 }
8299 break;
8300 default:
8301 this.Core.UnexpectedElement(node, child);
8302 break;
8303 }
8304 }
8305 else
8306 {
8307 this.Core.ParseExtensionElement(node, child);
8308 }
8309 }
8310
8311 if (!this.Core.EncounteredError)
8312 {
8313 this.Core.AddSymbol(new WixPatchBaselineSymbol(sourceLineNumbers, id)
8314 {
8315 DiskId = diskId ?? 1,
8316 ValidationFlags = validationFlags,
8317 BaselineFile = new IntermediateFieldPathValue { Path = baselineFile },
8318 UpdateFile = new IntermediateFieldPathValue { Path = updateFile },
8319 TransformFile = new IntermediateFieldPathValue { Path = transformFile },
8320 });
8321 }
8322 }
8323
8324 /// <summary>
8325 /// Parses a Validate element.
8326 /// </summary>
8327 /// <param name="node">The element to parse.</param>
8328 /// <param name="validationFlags">TransformValidation flags to use when creating the authoring patch transform.</param>
8329 private void ParseValidateElement(XElement node, ref TransformFlags validationFlags)
8330 {
8331 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
8332
8333 foreach (var attrib in node.Attributes())
8334 {
8335 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
8336 {
8337 switch (attrib.Name.LocalName)
8338 {
8339 case "ProductId":
8340 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
8341 {
8342 validationFlags |= TransformFlags.ValidateProduct;
8343 }
8344 else
8345 {
8346 validationFlags &= ~TransformFlags.ValidateProduct;
8347 }
8348 break;
8349 case "ProductLanguage":
8350 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
8351 {
8352 validationFlags |= TransformFlags.ValidateLanguage;
8353 }
8354 else
8355 {
8356 validationFlags &= ~TransformFlags.ValidateLanguage;
8357 }
8358 break;
8359 case "ProductVersion":
8360 var check = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
8361 validationFlags &= ~TransformFlags.ProductVersionMask;
8362 switch (check)
8363 {
8364 case "Major":
8365 case "major":
8366 validationFlags |= TransformFlags.ValidateMajorVersion;
8367 break;
8368 case "Minor":
8369 case "minor":
8370 validationFlags |= TransformFlags.ValidateMinorVersion;
8371 break;
8372 case "Update":
8373 case "update":
8374 validationFlags |= TransformFlags.ValidateUpdateVersion;
8375 break;
8376 case "":
8377 break;
8378 default:
8379 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Version", check, "Major", "Minor", "Update"));
8380 break;
8381 }
8382 break;
8383 case "ProductVersionOperator":
8384 var op = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
8385 validationFlags &= ~TransformFlags.ProductVersionOperatorMask;
8386 switch (op)
8387 {
8388 case "Lesser":
8389 case "lesser":
8390 validationFlags |= TransformFlags.ValidateNewLessBaseVersion;
8391 break;
8392 case "LesserOrEqual":
8393 case "lesserOrEqual":
8394 validationFlags |= TransformFlags.ValidateNewLessEqualBaseVersion;
8395 break;
8396 case "Equal":
8397 case "equal":
8398 validationFlags |= TransformFlags.ValidateNewEqualBaseVersion;
8399 break;
8400 case "GreaterOrEqual":
8401 case "greaterOrEqual":
8402 validationFlags |= TransformFlags.ValidateNewGreaterEqualBaseVersion;
8403 break;
8404 case "Greater":
8405 case "greater":
8406 validationFlags |= TransformFlags.ValidateNewGreaterBaseVersion;
8407 break;
8408 case "":
8409 break;
8410 default:
8411 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Operator", op, "Lesser", "LesserOrEqual", "Equal", "GreaterOrEqual", "Greater"));
8412 break;
8413 }
8414 break;
8415 case "UpgradeCode":
8416 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
8417 {
8418 validationFlags |= TransformFlags.ValidateUpgradeCode;
8419 }
8420 else
8421 {
8422 validationFlags &= ~TransformFlags.ValidateUpgradeCode;
8423 }
8424 break;
8425 case "IgnoreAddExistingRow":
8426 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
8427 {
8428 validationFlags |= TransformFlags.ErrorAddExistingRow;
8429 }
8430 else
8431 {
8432 validationFlags &= ~TransformFlags.ErrorAddExistingRow;
8433 }
8434 break;
8435 case "IgnoreAddExistingTable":
8436 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
8437 {
8438 validationFlags |= TransformFlags.ErrorAddExistingTable;
8439 }
8440 else
8441 {
8442 validationFlags &= ~TransformFlags.ErrorAddExistingTable;
8443 }
8444 break;
8445 case "IgnoreDeleteMissingRow":
8446 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
8447 {
8448 validationFlags |= TransformFlags.ErrorDeleteMissingRow;
8449 }
8450 else
8451 {
8452 validationFlags &= ~TransformFlags.ErrorDeleteMissingRow;
8453 }
8454 break;
8455 case "IgnoreDeleteMissingTable":
8456 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
8457 {
8458 validationFlags |= TransformFlags.ErrorDeleteMissingTable;
8459 }
8460 else
8461 {
8462 validationFlags &= ~TransformFlags.ErrorDeleteMissingTable;
8463 }
8464 break;
8465 case "IgnoreUpdateMissingRow":
8466 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
8467 {
8468 validationFlags |= TransformFlags.ErrorUpdateMissingRow;
8469 }
8470 else
8471 {
8472 validationFlags &= ~TransformFlags.ErrorUpdateMissingRow;
8473 }
8474 break;
8475 case "IgnoreChangingCodePage":
8476 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
8477 {
8478 validationFlags |= TransformFlags.ErrorChangeCodePage;
8479 }
8480 else
8481 {
8482 validationFlags &= ~TransformFlags.ErrorChangeCodePage;
8483 }
8484 break;
8485 default:
8486 this.Core.UnexpectedAttribute(node, attrib);
8487 break;
8488 }
8489 }
8490 else
8491 {
8492 this.Core.ParseExtensionAttribute(node, attrib);
8493 }
8494 }
8495 }
8496
8497 private string HandleSubdirectory(SourceLineNumber sourceLineNumbers, XElement element, string directoryId, string subdirectory, string directoryAttributeName, string subdirectoryAttributename)
8498 {
8499 if (!String.IsNullOrEmpty(subdirectory))
8500 {
8501 if (String.IsNullOrEmpty(directoryId))
8502 {
8503 this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, element.Name.LocalName, subdirectoryAttributename, directoryAttributeName));
8504 }
8505 else
8506 {
8507 directoryId = this.Core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, directoryId, subdirectory);
8508 }
8509 }
8510
8511 return directoryId;
8512 }
8513 }
8514}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System;
6 using System.Collections;
7 using System.Collections.Generic;
8 using System.Diagnostics;
9 using System.Globalization;
10 using System.Reflection;
11 using System.Text;
12 using System.Xml.Linq;
13 using WixToolset.Data;
14 using WixToolset.Data.Symbols;
15 using WixToolset.Data.WindowsInstaller;
16 using WixToolset.Extensibility;
17 using WixToolset.Extensibility.Data;
18 using WixToolset.Extensibility.Services;
19
20 internal enum ValueListKind
21 {
22 /// <summary>
23 /// A list of values with nothing before the final value.
24 /// </summary>
25 None,
26
27 /// <summary>
28 /// A list of values with 'and' before the final value.
29 /// </summary>
30 And,
31
32 /// <summary>
33 /// A list of values with 'or' before the final value.
34 /// </summary>
35 Or
36 }
37
38 /// <summary>
39 /// Core class for the compiler.
40 /// </summary>
41 internal class CompilerCore
42 {
43 internal static readonly XNamespace W3SchemaPrefix = "http://www.w3.org/";
44 internal static readonly XNamespace WixNamespace = "http://wixtoolset.org/schemas/v4/wxs";
45
46 // Built-in variables (from burn\engine\variable.cpp, "vrgBuiltInVariables", around line 113)
47 private static readonly List<string> BuiltinBundleVariables = new List<string>(
48 new string[] {
49 "AdminToolsFolder",
50 "AppDataFolder",
51 "CommonAppDataFolder",
52 "CommonFiles64Folder",
53 "CommonFilesFolder",
54 "CompatibilityMode",
55 "Date",
56 "DesktopFolder",
57 "FavoritesFolder",
58 "FontsFolder",
59 "InstallerName",
60 "InstallerVersion",
61 "LocalAppDataFolder",
62 "LogonUser",
63 "MyPicturesFolder",
64 "NTProductType",
65 "NTSuiteBackOffice",
66 "NTSuiteDataCenter",
67 "NTSuiteEnterprise",
68 "NTSuitePersonal",
69 "NTSuiteSmallBusiness",
70 "NTSuiteSmallBusinessRestricted",
71 "NTSuiteWebServer",
72 "PersonalFolder",
73 "Privileged",
74 "ProgramFiles64Folder",
75 "ProgramFiles6432Folder",
76 "ProgramFilesFolder",
77 "ProgramMenuFolder",
78 "RebootPending",
79 "SendToFolder",
80 "ServicePackLevel",
81 "StartMenuFolder",
82 "StartupFolder",
83 "System64Folder",
84 "SystemFolder",
85 "TempFolder",
86 "TemplateFolder",
87 "TerminalServer",
88 "UserLanguageID",
89 "UserUILanguageID",
90 "VersionMsi",
91 "VersionNT",
92 "VersionNT64",
93 "WindowsFolder",
94 "WindowsVolume",
95 "WixBundleAction",
96 "WixBundleForcedRestartPackage",
97 "WixBundleElevated",
98 "WixBundleInstalled",
99 "WixBundleProviderKey",
100 "WixBundleTag",
101 "WixBundleVersion",
102 });
103
104 private static readonly List<string> DisallowedMsiProperties = new List<string>(
105 new string[] {
106 "ACTION",
107 "ADDLOCAL",
108 "ADDSOURCE",
109 "ADDDEFAULT",
110 "ADVERTISE",
111 "ALLUSERS",
112 "REBOOT",
113 "REINSTALL",
114 "REINSTALLMODE",
115 "REMOVE"
116 });
117
118 private readonly Dictionary<XNamespace, ICompilerExtension> extensions;
119 private readonly IParseHelper parseHelper;
120 private readonly Intermediate intermediate;
121 private readonly IMessaging messaging;
122 private Dictionary<string, string> activeSectionCachedInlinedDirectoryIds;
123 private HashSet<string> activeSectionSimpleReferences;
124
125 /// <summary>
126 /// Constructor for all compiler core.
127 /// </summary>
128 /// <param name="intermediate">The Intermediate object representing compiled source document.</param>
129 /// <param name="messaging"></param>
130 /// <param name="parseHelper"></param>
131 /// <param name="extensions">The WiX extensions collection.</param>
132 internal CompilerCore(Intermediate intermediate, IMessaging messaging, IParseHelper parseHelper, Dictionary<XNamespace, ICompilerExtension> extensions)
133 {
134 this.extensions = extensions;
135 this.parseHelper = parseHelper;
136 this.intermediate = intermediate;
137 this.messaging = messaging;
138 }
139
140 /// <summary>
141 /// Gets the section the compiler is currently emitting symbols into.
142 /// </summary>
143 /// <value>The section the compiler is currently emitting symbols into.</value>
144 public IntermediateSection ActiveSection { get; private set; }
145
146 /// <summary>
147 /// Gets whether the compiler core encountered an error while processing.
148 /// </summary>
149 /// <value>Flag if core encountered an error during processing.</value>
150 public bool EncounteredError => this.messaging.EncounteredError;
151
152 /// <summary>
153 /// Gets or sets the option to show pedantic messages.
154 /// </summary>
155 /// <value>The option to show pedantic messages.</value>
156 public bool ShowPedanticMessages { get; set; }
157
158 /// <summary>
159 /// Add a symbol to the active section.
160 /// </summary>
161 /// <param name="symbol">Symbol to add.</param>
162 public T AddSymbol<T>(T symbol)
163 where T : IntermediateSymbol
164 {
165 return this.ActiveSection.AddSymbol(symbol);
166 }
167
168 /// <summary>
169 /// Convert a bit array into an int value.
170 /// </summary>
171 /// <param name="bits">The bit array to convert.</param>
172 /// <returns>The converted int value.</returns>
173 public int CreateIntegerFromBitArray(BitArray bits)
174 {
175 if (32 != bits.Length)
176 {
177 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");
178 }
179
180 int[] intArray = new int[1];
181 bits.CopyTo(intArray, 0);
182
183 return intArray[0];
184 }
185
186 /// <summary>
187 /// Sets a bit in a bit array based on the index at which an attribute name was found in a string array.
188 /// </summary>
189 /// <param name="attributeNames">Array of attributes that map to bits.</param>
190 /// <param name="attributeName">Name of attribute to check.</param>
191 /// <param name="attributeValue">Value of attribute to check.</param>
192 /// <param name="bits">The bit array in which the bit will be set if found.</param>
193 /// <param name="offset">The offset into the bit array.</param>
194 /// <returns>true if the bit was set; false otherwise.</returns>
195 public bool TrySetBitFromName(string[] attributeNames, string attributeName, YesNoType attributeValue, BitArray bits, int offset)
196 {
197 for (int i = 0; i < attributeNames.Length; i++)
198 {
199 if (attributeName.Equals(attributeNames[i], StringComparison.Ordinal))
200 {
201 bits.Set(i + offset, YesNoType.Yes == attributeValue);
202 return true;
203 }
204 }
205
206 return false;
207 }
208
209 internal void InnerTextDisallowed(XElement element)
210 {
211 this.parseHelper.InnerTextDisallowed(element);
212 }
213
214 /// <summary>
215 /// Verifies that a filename is ambiguous.
216 /// </summary>
217 /// <param name="filename">Filename to verify.</param>
218 /// <returns>true if the filename is ambiguous; false otherwise.</returns>
219 public static bool IsAmbiguousFilename(string filename)
220 {
221 if (String.IsNullOrEmpty(filename))
222 {
223 return false;
224 }
225
226 var tilde = filename.IndexOf('~');
227 return (tilde > 0 && tilde < filename.Length) && Char.IsNumber(filename[tilde + 1]);
228 }
229
230 /// <summary>
231 /// Verifies that a value is a legal identifier.
232 /// </summary>
233 /// <param name="value">The value to verify.</param>
234 /// <returns>true if the value is an identifier; false otherwise.</returns>
235 public bool IsValidIdentifier(string value)
236 {
237 return this.parseHelper.IsValidIdentifier(value);
238 }
239
240 /// <summary>
241 /// Verifies if an identifier is a valid loc identifier.
242 /// </summary>
243 /// <param name="identifier">Identifier to verify.</param>
244 /// <returns>True if the identifier is a valid loc identifier.</returns>
245 public bool IsValidLocIdentifier(string identifier)
246 {
247 return this.parseHelper.IsValidLocIdentifier(identifier);
248 }
249
250 /// <summary>
251 /// Verifies if a filename is a valid long filename.
252 /// </summary>
253 /// <param name="filename">Filename to verify.</param>
254 /// <param name="allowWildcards">true if wildcards are allowed in the filename.</param>
255 /// <param name="allowRelative">true if relative paths are allowed in the filename.</param>
256 /// <returns>True if the filename is a valid long filename</returns>
257 public bool IsValidLongFilename(string filename, bool allowWildcards = false, bool allowRelative = false)
258 {
259 return this.parseHelper.IsValidLongFilename(filename, allowWildcards, allowRelative);
260 }
261
262 /// <summary>
263 /// Verifies if a filename is a valid short filename.
264 /// </summary>
265 /// <param name="filename">Filename to verify.</param>
266 /// <param name="allowWildcards">true if wildcards are allowed in the filename.</param>
267 /// <returns>True if the filename is a valid short filename</returns>
268 public bool IsValidShortFilename(string filename, bool allowWildcards)
269 {
270 return this.parseHelper.IsValidShortFilename(filename, allowWildcards);
271 }
272
273 /// <summary>
274 /// Replaces the illegal filename characters to create a legal name.
275 /// </summary>
276 /// <param name="filename">Filename to make valid.</param>
277 /// <param name="replace">Replacement string for invalid characters in filename.</param>
278 /// <returns>Valid filename.</returns>
279 public static string MakeValidLongFileName(string filename, char replace)
280 {
281 if (String.IsNullOrEmpty(filename))
282 {
283 return filename;
284 }
285
286 StringBuilder sb = null;
287
288 var found = filename.IndexOfAny(Common.IllegalLongFilenameCharacters);
289 while (found != -1)
290 {
291 if (sb == null)
292 {
293 sb = new StringBuilder(filename);
294 }
295
296 sb[found] = replace;
297
298 found = (found + 1 < filename.Length) ? filename.IndexOfAny(Common.IllegalLongFilenameCharacters, found + 1) : -1;
299 }
300
301 return sb?.ToString() ?? filename;
302 }
303
304 /// <summary>
305 /// Verifies the given string is a valid product version.
306 /// </summary>
307 /// <param name="version">The product version to verify.</param>
308 /// <returns>True if version is a valid product version</returns>
309 public static bool IsValidProductVersion(string version)
310 {
311 if (!Common.IsValidBinderVariable(version))
312 {
313 Version ver = new Version(version);
314
315 if (255 < ver.Major || 255 < ver.Minor || 65535 < ver.Build)
316 {
317 return false;
318 }
319 }
320
321 return true;
322 }
323
324 /// <summary>
325 /// Verifies the given string is a valid module or bundle version.
326 /// </summary>
327 /// <param name="version">The version to verify.</param>
328 /// <returns>True if version is a valid module or bundle version.</returns>
329 public static bool IsValidModuleOrBundleVersion(string version)
330 {
331 return Common.IsValidFourPartVersion(version);
332 }
333
334 /// <summary>
335 /// Creates group and ordering information.
336 /// </summary>
337 /// <param name="sourceLineNumbers">Source line numbers.</param>
338 /// <param name="parentType">Type of parent group, if known.</param>
339 /// <param name="parentId">Identifier of parent group, if known.</param>
340 /// <param name="type">Type of this item.</param>
341 /// <param name="id">Identifier for this item.</param>
342 /// <param name="previousType">Type of previous item, if known.</param>
343 /// <param name="previousId">Identifier of previous item, if known</param>
344 public void CreateGroupAndOrderingRows(SourceLineNumber sourceLineNumbers,
345 ComplexReferenceParentType parentType, string parentId,
346 ComplexReferenceChildType type, string id,
347 ComplexReferenceChildType previousType, string previousId)
348 {
349 if (this.EncounteredError)
350 {
351 return;
352 }
353
354 if (parentType != ComplexReferenceParentType.Unknown && parentId != null)
355 {
356 this.CreateWixGroupRow(sourceLineNumbers, parentType, parentId, type, id);
357 }
358
359 if (previousType != ComplexReferenceChildType.Unknown && previousId != null)
360 {
361 // TODO: Should we define our own enum for this, just to ensure there's no "cross-contamination"?
362 // TODO: Also, we could potentially include an 'Attributes' field to track things like
363 // 'before' vs. 'after', and explicit vs. inferred dependencies.
364 this.AddSymbol(new WixOrderingSymbol(sourceLineNumbers)
365 {
366 ItemType = type,
367 ItemIdRef = id,
368 DependsOnType = previousType,
369 DependsOnIdRef = previousId,
370 });
371 }
372 }
373
374 /// <summary>
375 /// Creates a version 3 name-based UUID.
376 /// </summary>
377 /// <param name="namespaceGuid">The namespace UUID.</param>
378 /// <param name="value">The value.</param>
379 /// <returns>The generated GUID for the given namespace and value.</returns>
380 public string CreateGuid(Guid namespaceGuid, string value)
381 {
382 return this.parseHelper.CreateGuid(namespaceGuid, value);
383 }
384
385 /// <summary>
386 /// Creates directories using the inline directory syntax.
387 /// </summary>
388 /// <param name="sourceLineNumbers">Source line information.</param>
389 /// <param name="parentId">Optional identifier of parent directory.</param>
390 /// <param name="inlineSyntax">Optional inline syntax to override attribute's value.</param>
391 /// <returns>Identifier of the leaf directory created.</returns>
392 public string CreateDirectoryReferenceFromInlineSyntax(SourceLineNumber sourceLineNumbers, string parentId, string inlineSyntax = null)
393 {
394 return this.parseHelper.CreateDirectoryReferenceFromInlineSyntax(this.ActiveSection, sourceLineNumbers, attribute: null, parentId, inlineSyntax, this.activeSectionCachedInlinedDirectoryIds);
395 }
396
397 /// <summary>
398 /// Creates a Registry row in the active section.
399 /// </summary>
400 /// <param name="sourceLineNumbers">Source and line number of the current row.</param>
401 /// <param name="root">The registry entry root.</param>
402 /// <param name="key">The registry entry key.</param>
403 /// <param name="name">The registry entry name.</param>
404 /// <param name="value">The registry entry value.</param>
405 /// <param name="componentId">The component which will control installation/uninstallation of the registry entry.</param>
406 public Identifier CreateRegistryRow(SourceLineNumber sourceLineNumbers, RegistryRootType root, string key, string name, string value, string componentId)
407 {
408 return this.parseHelper.CreateRegistrySymbol(this.ActiveSection, sourceLineNumbers, root, key, name, value, componentId, true);
409 }
410
411 /// <summary>
412 /// Create a WixSimpleReferenceSymbol in the active section.
413 /// </summary>
414 /// <param name="sourceLineNumbers">Source line information for the row.</param>
415 /// <param name="symbolName">The symbol name of the simple reference.</param>
416 /// <param name="primaryKey">The primary key of the simple reference.</param>
417 public void CreateSimpleReference(SourceLineNumber sourceLineNumbers, string symbolName, string primaryKey)
418 {
419 if (!this.EncounteredError)
420 {
421 var id = String.Concat(symbolName, ":", primaryKey);
422
423 // If this simple reference hasn't been added to the active section already, add it.
424 if (this.activeSectionSimpleReferences.Add(id))
425 {
426 this.parseHelper.CreateSimpleReference(this.ActiveSection, sourceLineNumbers, symbolName, primaryKey);
427 }
428 }
429 }
430
431 /// <summary>
432 /// Create a WixSimpleReferenceSymbol in the active section.
433 /// </summary>
434 /// <param name="sourceLineNumbers">Source line information for the row.</param>
435 /// <param name="symbolName">The symbol name of the simple reference.</param>
436 /// <param name="primaryKeys">The primary keys of the simple reference.</param>
437 public void CreateSimpleReference(SourceLineNumber sourceLineNumbers, string symbolName, params string[] primaryKeys)
438 {
439 if (!this.EncounteredError)
440 {
441 var joinedKeys = String.Join("/", primaryKeys);
442 var id = String.Concat(symbolName, ":", joinedKeys);
443
444 // If this simple reference hasn't been added to the active section already, add it.
445 if (this.activeSectionSimpleReferences.Add(id))
446 {
447 this.parseHelper.CreateSimpleReference(this.ActiveSection, sourceLineNumbers, symbolName, primaryKeys);
448 }
449 }
450 }
451
452 /// <summary>
453 /// Create a WixSimpleReferenceSymbol in the active section.
454 /// </summary>
455 /// <param name="sourceLineNumbers">Source line information for the row.</param>
456 /// <param name="symbolDefinition">The symbol definition of the simple reference.</param>
457 /// <param name="primaryKey">The primary key of the simple reference.</param>
458 public void CreateSimpleReference(SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, string primaryKey)
459 {
460 this.CreateSimpleReference(sourceLineNumbers, symbolDefinition.Name, primaryKey);
461 }
462
463 /// <summary>
464 /// Create a WixSimpleReferenceSymbol in the active section.
465 /// </summary>
466 /// <param name="sourceLineNumbers">Source line information for the row.</param>
467 /// <param name="symbolDefinition">The symbol definition of the simple reference.</param>
468 /// <param name="primaryKeys">The primary keys of the simple reference.</param>
469 public void CreateSimpleReference(SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, params string[] primaryKeys)
470 {
471 this.CreateSimpleReference(sourceLineNumbers, symbolDefinition.Name, primaryKeys);
472 }
473
474 /// <summary>
475 /// A row in the WixGroup table is added for this child node and its parent node.
476 /// </summary>
477 /// <param name="sourceLineNumbers">Source line information for the row.</param>
478 /// <param name="parentType">Type of child's complex reference parent.</param>
479 /// <param name="parentId">Id of the parenet node.</param>
480 /// <param name="childType">Complex reference type of child</param>
481 /// <param name="childId">Id of the Child Node.</param>
482 public void CreateWixGroupRow(SourceLineNumber sourceLineNumbers, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType childType, string childId)
483 {
484 if (!this.EncounteredError)
485 {
486 this.parseHelper.CreateWixGroupSymbol(this.ActiveSection, sourceLineNumbers, parentType, parentId, childType, childId);
487 }
488 }
489
490 /// <summary>
491 /// Add the appropriate symbols to make sure that the given table shows up
492 /// in the resulting output.
493 /// </summary>
494 /// <param name="sourceLineNumbers">Source line numbers.</param>
495 /// <param name="tableName">Name of the table to ensure existance of.</param>
496 public void EnsureTable(SourceLineNumber sourceLineNumbers, string tableName)
497 {
498 if (!this.EncounteredError)
499 {
500 this.parseHelper.EnsureTable(this.ActiveSection, sourceLineNumbers, tableName);
501 }
502 }
503
504 /// <summary>
505 /// Add the appropriate symbols to make sure that the given table shows up
506 /// in the resulting output.
507 /// </summary>
508 /// <param name="sourceLineNumbers">Source line numbers.</param>
509 /// <param name="tableDefinition">Definition of the table to ensure existance of.</param>
510 public void EnsureTable(SourceLineNumber sourceLineNumbers, TableDefinition tableDefinition)
511 {
512 if (!this.EncounteredError)
513 {
514 this.parseHelper.EnsureTable(this.ActiveSection, sourceLineNumbers, tableDefinition);
515 }
516 }
517
518 /// <summary>
519 /// Get an attribute value.
520 /// </summary>
521 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
522 /// <param name="attribute">The attribute containing the value to get.</param>
523 /// <param name="emptyRule">A rule for the contents of the value. If the contents do not follow the rule, an error is thrown.</param>
524 /// <returns>The attribute's value.</returns>
525 public string GetAttributeValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, EmptyRule emptyRule = EmptyRule.CanBeWhitespaceOnly)
526 {
527 return this.parseHelper.GetAttributeValue(sourceLineNumbers, attribute, emptyRule);
528 }
529
530 /// <summary>
531 /// Get a valid code page by web name or number from a string attribute.
532 /// </summary>
533 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
534 /// <param name="attribute">The attribute containing the value to get.</param>
535 /// <returns>A valid code page integer value.</returns>
536 public int GetAttributeCodePageValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
537 {
538 if (null == attribute)
539 {
540 throw new ArgumentNullException(nameof(attribute));
541 }
542
543 var value = this.GetAttributeValue(sourceLineNumbers, attribute);
544
545 try
546 {
547 return Common.GetValidCodePage(value);
548 }
549 catch (NotSupportedException)
550 {
551 this.Write(ErrorMessages.IllegalCodepageAttribute(sourceLineNumbers, value, attribute.Parent.Name.LocalName, attribute.Name.LocalName));
552 }
553
554 return CompilerConstants.IllegalInteger;
555 }
556
557 /// <summary>
558 /// Get a valid code page by web name or number from a string attribute.
559 /// </summary>
560 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
561 /// <param name="attribute">The attribute containing the value to get.</param>
562 /// <param name="onlyAnsi">Whether to allow Unicode (UCS) or UTF code pages.</param>
563 /// <returns>A valid code page integer value or variable expression.</returns>
564 public string GetAttributeLocalizableCodePageValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool onlyAnsi = false)
565 {
566 if (null == attribute)
567 {
568 throw new ArgumentNullException(nameof(attribute));
569 }
570
571 var value = this.GetAttributeValue(sourceLineNumbers, attribute);
572
573 // Allow for localization of code page names and values.
574 if (this.IsValidLocIdentifier(value))
575 {
576 return value;
577 }
578
579 try
580 {
581 var codePage = Common.GetValidCodePage(value, false, onlyAnsi, sourceLineNumbers);
582 return codePage.ToString(CultureInfo.InvariantCulture);
583 }
584 catch (NotSupportedException)
585 {
586 // Not a valid windows code page.
587 this.messaging.Write(ErrorMessages.IllegalCodepageAttribute(sourceLineNumbers, value, attribute.Parent.Name.LocalName, attribute.Name.LocalName));
588 }
589 catch (WixException e)
590 {
591 this.messaging.Write(e.Error);
592 }
593
594 return null;
595 }
596
597 /// <summary>
598 /// Get an integer attribute value and displays an error for an illegal integer value.
599 /// </summary>
600 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
601 /// <param name="attribute">The attribute containing the value to get.</param>
602 /// <param name="minimum">The minimum legal value.</param>
603 /// <param name="maximum">The maximum legal value.</param>
604 /// <returns>The attribute's integer value or a special value if an error occurred during conversion.</returns>
605 public int GetAttributeIntegerValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, int minimum, int maximum)
606 {
607 return this.parseHelper.GetAttributeIntegerValue(sourceLineNumbers, attribute, minimum, maximum);
608 }
609
610 /// <summary>
611 /// Get a long integral attribute value and displays an error for an illegal long value.
612 /// </summary>
613 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
614 /// <param name="attribute">The attribute containing the value to get.</param>
615 /// <param name="minimum">The minimum legal value.</param>
616 /// <param name="maximum">The maximum legal value.</param>
617 /// <returns>The attribute's long value or a special value if an error occurred during conversion.</returns>
618 public long GetAttributeLongValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, long minimum, long maximum)
619 {
620 return this.parseHelper.GetAttributeLongValue(sourceLineNumbers, attribute, minimum, maximum);
621 }
622
623 /// <summary>
624 /// Get a date time attribute value and display errors for illegal values.
625 /// </summary>
626 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
627 /// <param name="attribute">The attribute containing the value to get.</param>
628 /// <returns>Int representation of the date time.</returns>
629 public int GetAttributeDateTimeValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
630 {
631 if (null == attribute)
632 {
633 throw new ArgumentNullException("attribute");
634 }
635
636 string value = this.GetAttributeValue(sourceLineNumbers, attribute);
637
638 if (0 < value.Length)
639 {
640 try
641 {
642 DateTime date = DateTime.Parse(value, CultureInfo.InvariantCulture.DateTimeFormat);
643
644 return ((((date.Year - 1980) * 512) + (date.Month * 32 + date.Day)) * 65536) +
645 (date.Hour * 2048) + (date.Minute * 32) + (date.Second / 2);
646 }
647 catch (ArgumentOutOfRangeException)
648 {
649 this.Write(ErrorMessages.InvalidDateTimeFormat(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
650 }
651 catch (FormatException)
652 {
653 this.Write(ErrorMessages.InvalidDateTimeFormat(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
654 }
655 catch (OverflowException)
656 {
657 this.Write(ErrorMessages.InvalidDateTimeFormat(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
658 }
659 }
660
661 return CompilerConstants.IllegalInteger;
662 }
663
664 /// <summary>
665 /// Get an integer attribute value or localize variable and displays an error for
666 /// an illegal value.
667 /// </summary>
668 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
669 /// <param name="attribute">The attribute containing the value to get.</param>
670 /// <param name="minimum">The minimum legal value.</param>
671 /// <param name="maximum">The maximum legal value.</param>
672 /// <returns>The attribute's integer value or localize variable as a string or a special value if an error occurred during conversion.</returns>
673 public string GetAttributeLocalizableIntegerValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, int minimum, int maximum)
674 {
675 if (null == attribute)
676 {
677 throw new ArgumentNullException("attribute");
678 }
679
680 Debug.Assert(minimum > CompilerConstants.IntegerNotSet && minimum > CompilerConstants.IllegalInteger, "The legal values for this attribute collide with at least one sentinel used during parsing.");
681
682 var value = this.GetAttributeValue(sourceLineNumbers, attribute);
683
684 if (0 < value.Length)
685 {
686 if (this.IsValidLocIdentifier(value) || Common.IsValidBinderVariable(value))
687 {
688 return value;
689 }
690 else
691 {
692 try
693 {
694 var integer = Convert.ToInt32(value, CultureInfo.InvariantCulture.NumberFormat);
695
696 if (CompilerConstants.IntegerNotSet == integer || CompilerConstants.IllegalInteger == integer)
697 {
698 this.Write(ErrorMessages.IntegralValueSentinelCollision(sourceLineNumbers, integer));
699 }
700 else if (minimum > integer || maximum < integer)
701 {
702 this.Write(ErrorMessages.IntegralValueOutOfRange(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, integer, minimum, maximum));
703 integer = CompilerConstants.IllegalInteger;
704 }
705
706 return value;
707 }
708 catch (FormatException)
709 {
710 this.Write(ErrorMessages.IllegalIntegerValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
711 }
712 catch (OverflowException)
713 {
714 this.Write(ErrorMessages.IllegalIntegerValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
715 }
716 }
717 }
718
719 return null;
720 }
721
722 /// <summary>
723 /// Get a guid attribute value and displays an error for an illegal guid value.
724 /// </summary>
725 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
726 /// <param name="attribute">The attribute containing the value to get.</param>
727 /// <param name="generatable">Determines whether the guid can be automatically generated.</param>
728 /// <param name="canBeEmpty">If true, no error is raised on empty value. If false, an error is raised.</param>
729 /// <returns>The attribute's guid value or a special value if an error occurred.</returns>
730 public string GetAttributeGuidValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool generatable = false, bool canBeEmpty = false)
731 {
732 return this.parseHelper.GetAttributeGuidValue(sourceLineNumbers, attribute, generatable, canBeEmpty);
733 }
734
735 /// <summary>
736 /// Get an identifier attribute value and displays an error for an illegal identifier value.
737 /// </summary>
738 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
739 /// <param name="attribute">The attribute containing the value to get.</param>
740 /// <returns>The attribute's identifier value or a special value if an error occurred.</returns>
741 public Identifier GetAttributeIdentifier(SourceLineNumber sourceLineNumbers, XAttribute attribute)
742 {
743 return this.parseHelper.GetAttributeIdentifier(sourceLineNumbers, attribute);
744 }
745
746 /// <summary>
747 /// Get an identifier attribute value and displays an error for an illegal identifier value.
748 /// </summary>
749 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
750 /// <param name="attribute">The attribute containing the value to get.</param>
751 /// <returns>The attribute's identifier value or a special value if an error occurred.</returns>
752 public string GetAttributeIdentifierValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
753 {
754 return this.parseHelper.GetAttributeIdentifierValue(sourceLineNumbers, attribute);
755 }
756
757 /// <summary>
758 /// Gets a yes/no value and displays an error for an illegal yes/no value.
759 /// </summary>
760 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
761 /// <param name="attribute">The attribute containing the value to get.</param>
762 /// <returns>The attribute's YesNoType value.</returns>
763 public YesNoType GetAttributeYesNoValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
764 {
765 return this.parseHelper.GetAttributeYesNoValue(sourceLineNumbers, attribute);
766 }
767
768 /// <summary>
769 /// Gets a yes/no/default value and displays an error for an illegal yes/no value.
770 /// </summary>
771 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
772 /// <param name="attribute">The attribute containing the value to get.</param>
773 /// <returns>The attribute's YesNoDefaultType value.</returns>
774 public YesNoDefaultType GetAttributeYesNoDefaultValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
775 {
776 return this.parseHelper.GetAttributeYesNoDefaultValue(sourceLineNumbers, attribute);
777 }
778
779 /// <summary>
780 /// Gets a short filename value and displays an error for an illegal short filename value.
781 /// </summary>
782 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
783 /// <param name="attribute">The attribute containing the value to get.</param>
784 /// <param name="allowWildcards">true if wildcards are allowed in the filename.</param>
785 /// <returns>The attribute's short filename value.</returns>
786 public string GetAttributeShortFilename(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowWildcards = false)
787 {
788 if (null == attribute)
789 {
790 throw new ArgumentNullException("attribute");
791 }
792
793 var value = this.GetAttributeValue(sourceLineNumbers, attribute);
794
795 if (0 < value.Length)
796 {
797 if (!this.parseHelper.IsValidShortFilename(value, allowWildcards) && !Common.ContainsValidBinderVariable(value))
798 {
799 this.Write(ErrorMessages.IllegalShortFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
800 }
801 else if (CompilerCore.IsAmbiguousFilename(value))
802 {
803 this.Write(WarningMessages.AmbiguousFileOrDirectoryName(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
804 }
805 }
806
807 return value;
808 }
809
810 /// <summary>
811 /// Gets a long filename value and displays an error for an illegal long filename value.
812 /// </summary>
813 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
814 /// <param name="attribute">The attribute containing the value to get.</param>
815 /// <param name="allowWildcards">true if wildcards are allowed in the filename.</param>
816 /// <param name="allowRelative">true if relative paths are allowed in the filename.</param>
817 /// <returns>The attribute's long filename value.</returns>
818 public string GetAttributeLongFilename(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowWildcards = false, bool allowRelative = false)
819 {
820 return this.parseHelper.GetAttributeLongFilename(sourceLineNumbers, attribute, allowWildcards, allowRelative);
821 }
822
823 /// <summary>
824 /// Gets a version value or possibly a binder variable and displays an error for an illegal version value.
825 /// </summary>
826 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
827 /// <param name="attribute">The attribute containing the value to get.</param>
828 /// <returns>The attribute's version value.</returns>
829 public string GetAttributeVersionValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
830 {
831 return this.parseHelper.GetAttributeVersionValue(sourceLineNumbers, attribute);
832 }
833
834 /// <summary>
835 /// Gets a RegistryRoot as a MsiInterop.MsidbRegistryRoot value and displays an error for an illegal value.
836 /// </summary>
837 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
838 /// <param name="attribute">The attribute containing the value to get.</param>
839 /// <param name="allowHkmu">Whether HKMU is returned as -1 (true), or treated as an error (false).</param>
840 /// <returns>The attribute's RegisitryRootType value.</returns>
841 public RegistryRootType? GetAttributeRegistryRootValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowHkmu)
842 {
843 return this.parseHelper.GetAttributeRegistryRootValue(sourceLineNumbers, attribute, allowHkmu);
844 }
845
846 /// <summary>
847 /// Gets a Bundle variable value and displays an error for an illegal value.
848 /// </summary>
849 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
850 /// <param name="attribute">The attribute containing the value to get.</param>
851 /// <returns>The attribute's value.</returns>
852 public string GetAttributeBundleVariableValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
853 {
854 string value = this.GetAttributeValue(sourceLineNumbers, attribute);
855
856 if (!String.IsNullOrEmpty(value))
857 {
858 if (CompilerCore.BuiltinBundleVariables.Contains(value))
859 {
860 string illegalValues = CompilerCore.CreateValueList(ValueListKind.Or, CompilerCore.BuiltinBundleVariables);
861 this.Write(ErrorMessages.IllegalAttributeValueWithIllegalList(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, illegalValues));
862 }
863 }
864
865 return value;
866 }
867
868 /// <summary>
869 /// Gets an MsiProperty name value and displays an error for an illegal value.
870 /// </summary>
871 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
872 /// <param name="attribute">The attribute containing the value to get.</param>
873 /// <returns>The attribute's value.</returns>
874 public string GetAttributeMsiPropertyNameValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
875 {
876 string value = this.GetAttributeValue(sourceLineNumbers, attribute);
877
878 if (0 < value.Length)
879 {
880 if (CompilerCore.DisallowedMsiProperties.Contains(value))
881 {
882 string illegalValues = CompilerCore.CreateValueList(ValueListKind.Or, CompilerCore.DisallowedMsiProperties);
883 this.Write(ErrorMessages.DisallowedMsiProperty(sourceLineNumbers, value, illegalValues));
884 }
885 }
886
887 return value;
888 }
889
890 /// <summary>
891 /// Checks if the string contains a property (i.e. "foo[Property]bar")
892 /// </summary>
893 /// <param name="possibleProperty">String to evaluate for properties.</param>
894 /// <returns>True if a property is found in the string.</returns>
895 public bool ContainsProperty(string possibleProperty)
896 {
897 return this.parseHelper.ContainsProperty(possibleProperty);
898 }
899
900 /// <summary>
901 /// Generate an identifier by hashing data from the row.
902 /// </summary>
903 /// <param name="prefix">Three letter or less prefix for generated row identifier.</param>
904 /// <param name="args">Information to hash.</param>
905 /// <returns>The generated identifier.</returns>
906 public Identifier CreateIdentifier(string prefix, params string[] args)
907 {
908 return this.parseHelper.CreateIdentifier(prefix, args);
909 }
910
911 /// <summary>
912 /// Create an identifier based on passed file name
913 /// </summary>
914 /// <param name="filename">File name to generate identifer from</param>
915 /// <returns></returns>
916 public Identifier CreateIdentifierFromFilename(string filename)
917 {
918 return this.parseHelper.CreateIdentifierFromFilename(filename);
919 }
920
921 /// <summary>
922 /// Attempts to use an extension to parse the attribute.
923 /// </summary>
924 /// <param name="element">Element containing attribute to be parsed.</param>
925 /// <param name="attribute">Attribute to be parsed.</param>
926 /// <param name="context">Extra information about the context in which this element is being parsed.</param>
927 public void ParseExtensionAttribute(XElement element, XAttribute attribute, IDictionary<string, string> context = null)
928 {
929 this.parseHelper.ParseExtensionAttribute(this.extensions.Values, this.intermediate, this.ActiveSection, element, attribute, context);
930 }
931
932 /// <summary>
933 /// Attempts to use an extension to parse the element.
934 /// </summary>
935 /// <param name="parentElement">Element containing element to be parsed.</param>
936 /// <param name="element">Element to be parsed.</param>
937 /// <param name="context">Extra information about the context in which this element is being parsed.</param>
938 public void ParseExtensionElement(XElement parentElement, XElement element, IDictionary<string, string> context = null)
939 {
940 this.parseHelper.ParseExtensionElement(this.extensions.Values, this.intermediate, this.ActiveSection, parentElement, element, context);
941 }
942
943 /// <summary>
944 /// Process all children of the element looking for extensions and erroring on the unexpected.
945 /// </summary>
946 /// <param name="element">Element to parse children.</param>
947 public void ParseForExtensionElements(XElement element)
948 {
949 this.parseHelper.ParseForExtensionElements(this.extensions.Values, this.intermediate, this.ActiveSection, element);
950 }
951
952 /// <summary>
953 /// Attempts to use an extension to parse the element, with support for setting component keypath.
954 /// </summary>
955 /// <param name="parentElement">Element containing element to be parsed.</param>
956 /// <param name="element">Element to be parsed.</param>
957 /// <param name="context">Extra information about the context in which this element is being parsed.</param>
958 public IComponentKeyPath ParsePossibleKeyPathExtensionElement(XElement parentElement, XElement element, IDictionary<string, string> context)
959 {
960 return this.parseHelper.ParsePossibleKeyPathExtensionElement(this.extensions.Values, this.intermediate, this.ActiveSection, parentElement, element, context);
961 }
962
963 /// <summary>
964 /// Displays an unexpected attribute error if the attribute is not the namespace attribute.
965 /// </summary>
966 /// <param name="element">Element containing unexpected attribute.</param>
967 /// <param name="attribute">The unexpected attribute.</param>
968 public void UnexpectedAttribute(XElement element, XAttribute attribute)
969 {
970 this.parseHelper.UnexpectedAttribute(element, attribute);
971 }
972
973 /// <summary>
974 /// Display an unexepected element error.
975 /// </summary>
976 /// <param name="parentElement">The parent element.</param>
977 /// <param name="childElement">The unexpected child element.</param>
978 public void UnexpectedElement(XElement parentElement, XElement childElement)
979 {
980 this.parseHelper.UnexpectedElement(parentElement, childElement);
981 }
982
983 /// <summary>
984 /// Sends a message.
985 /// </summary>
986 /// <param name="message">Message to write.</param>
987 public void Write(Message message)
988 {
989 this.messaging.Write(message);
990 }
991
992 /// <summary>
993 /// Verifies that the calling assembly version is equal to or newer than the given <paramref name="requiredVersion"/>.
994 /// </summary>
995 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
996 /// <param name="requiredVersion">The version required of the calling assembly.</param>
997 internal void VerifyRequiredVersion(SourceLineNumber sourceLineNumbers, string requiredVersion)
998 {
999 // an null or empty string means any version will work
1000 if (!String.IsNullOrEmpty(requiredVersion))
1001 {
1002 Assembly caller = Assembly.GetCallingAssembly();
1003 AssemblyName name = caller.GetName();
1004 FileVersionInfo fv = FileVersionInfo.GetVersionInfo(caller.Location);
1005
1006 Version versionRequired = new Version(requiredVersion);
1007 Version versionCurrent = new Version(fv.FileVersion);
1008
1009 if (versionRequired > versionCurrent)
1010 {
1011 if (this.GetType().Assembly.Equals(caller))
1012 {
1013 this.Write(ErrorMessages.InsufficientVersion(sourceLineNumbers, versionCurrent, versionRequired));
1014 }
1015 else
1016 {
1017 this.Write(ErrorMessages.InsufficientVersion(sourceLineNumbers, versionCurrent, versionRequired, name.Name));
1018 }
1019 }
1020 }
1021 }
1022
1023 /// <summary>
1024 /// Creates a new section and makes it the active section in the core.
1025 /// </summary>
1026 /// <param name="id">Unique identifier for the section.</param>
1027 /// <param name="type">Type of section to create.</param>
1028 /// <param name="compilationId">Unique identifier for the compilation.</param>
1029 /// <returns>New section.</returns>
1030 internal IntermediateSection CreateActiveSection(string id, SectionType type, string compilationId)
1031 {
1032 this.ActiveSection = this.CreateSection(id, type, compilationId);
1033
1034 this.activeSectionCachedInlinedDirectoryIds = new Dictionary<string, string>();
1035 this.activeSectionSimpleReferences = new HashSet<string>();
1036
1037 return this.ActiveSection;
1038 }
1039
1040 /// <summary>
1041 /// Creates a new section.
1042 /// </summary>
1043 /// <param name="id">Unique identifier for the section.</param>
1044 /// <param name="type">Type of section to create.</param>
1045 /// <param name="compilationId">Unique identifier for the compilation.</param>
1046 /// <returns>New section.</returns>
1047 internal IntermediateSection CreateSection(string id, SectionType type, string compilationId)
1048 {
1049 var section = new IntermediateSection(id, type, compilationId);
1050
1051 this.intermediate.AddSection(section);
1052
1053 return section;
1054 }
1055
1056 /// <summary>
1057 /// Creates WixComplexReference and WixGroup rows in the active section.
1058 /// </summary>
1059 /// <param name="sourceLineNumbers">Source line information.</param>
1060 /// <param name="parentType">The parent type.</param>
1061 /// <param name="parentId">The parent id.</param>
1062 /// <param name="parentLanguage">The parent language.</param>
1063 /// <param name="childType">The child type.</param>
1064 /// <param name="childId">The child id.</param>
1065 /// <param name="isPrimary">Whether the child is primary.</param>
1066 public void CreateComplexReference(SourceLineNumber sourceLineNumbers, ComplexReferenceParentType parentType, string parentId, string parentLanguage, ComplexReferenceChildType childType, string childId, bool isPrimary)
1067 {
1068 this.parseHelper.CreateComplexReference(this.ActiveSection, sourceLineNumbers, parentType, parentId, parentLanguage, childType, childId, isPrimary);
1069 }
1070
1071 /// <summary>
1072 /// Creates a directory row from a name.
1073 /// </summary>
1074 /// <param name="sourceLineNumbers">Source line information.</param>
1075 /// <param name="id">Optional identifier for the new row.</param>
1076 /// <param name="parentId">Optional identifier for the parent row.</param>
1077 /// <param name="name">Long name of the directory.</param>
1078 /// <param name="shortName">Optional short name of the directory.</param>
1079 /// <param name="sourceName">Optional source name for the directory.</param>
1080 /// <param name="shortSourceName">Optional short source name for the directory.</param>
1081 /// <returns>Identifier for the newly created row.</returns>
1082 internal Identifier CreateDirectorySymbol(SourceLineNumber sourceLineNumbers, Identifier id, string parentId, string name, string shortName = null, string sourceName = null, string shortSourceName = null)
1083 {
1084 return this.parseHelper.CreateDirectorySymbol(this.ActiveSection, sourceLineNumbers, id, parentId, name, shortName, sourceName, shortSourceName);
1085 }
1086
1087 public void CreateWixSearchSymbol(SourceLineNumber sourceLineNumbers, string elementName, Identifier id, string variable, string condition, string after)
1088 {
1089 this.parseHelper.CreateWixSearchSymbol(this.ActiveSection, sourceLineNumbers, elementName, id, variable, condition, after, null);
1090 }
1091
1092 internal WixActionSymbol ScheduleActionSymbol(SourceLineNumber sourceLineNumbers, AccessModifier access, SequenceTable sequence, string actionName, string condition = null, string beforeAction = null, string afterAction = null, bool overridable = false)
1093 {
1094 return this.parseHelper.ScheduleActionSymbol(this.ActiveSection, sourceLineNumbers, access, sequence, actionName, condition, beforeAction, afterAction, overridable);
1095 }
1096
1097 private static string CreateValueList(ValueListKind kind, IEnumerable<string> values)
1098 {
1099 // Ideally, we could denote the list kind (and the list itself) directly in the
1100 // message XML, and detect and expand in the MessageHandler.GenerateMessageString()
1101 // method. Doing so would make vararg-style messages much easier, but impacts
1102 // every single message we format. For now, callers just have to know when a
1103 // message takes a list of values in a single string argument, the caller will
1104 // have to do the expansion themselves. (And, unfortunately, hard-code the knowledge
1105 // that the list is an 'and' or 'or' list.)
1106
1107 // For a localizable solution, we need to be able to get the list format string
1108 // from resources. We aren't currently localized right now, so the values are
1109 // just hard-coded.
1110 const string valueFormat = "'{0}'";
1111 const string valueSeparator = ", ";
1112 string terminalTerm = String.Empty;
1113
1114 switch (kind)
1115 {
1116 case ValueListKind.None:
1117 terminalTerm = "";
1118 break;
1119 case ValueListKind.And:
1120 terminalTerm = "and ";
1121 break;
1122 case ValueListKind.Or:
1123 terminalTerm = "or ";
1124 break;
1125 }
1126
1127 StringBuilder list = new StringBuilder();
1128
1129 // This weird construction helps us determine when we're adding the last value
1130 // to the list. Instead of adding them as we encounter them, we cache the current
1131 // value and append the *previous* one.
1132 string previousValue = null;
1133 bool haveValues = false;
1134 foreach (string value in values)
1135 {
1136 if (null != previousValue)
1137 {
1138 if (haveValues)
1139 {
1140 list.Append(valueSeparator);
1141 }
1142 list.AppendFormat(valueFormat, previousValue);
1143 haveValues = true;
1144 }
1145
1146 previousValue = value;
1147 }
1148
1149 // If we have no previous value, that means that the list contained no values, and
1150 // something has gone very wrong.
1151 Debug.Assert(null != previousValue);
1152 if (null != previousValue)
1153 {
1154 if (haveValues)
1155 {
1156 list.Append(valueSeparator);
1157 list.Append(terminalTerm);
1158 }
1159 list.AppendFormat(valueFormat, previousValue);
1160 haveValues = true;
1161 }
1162
1163 return list.ToString();
1164 }
1165 }
1166}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using WixToolset.Data;
6
7 internal static class CompilerErrors
8 {
9 public static Message IllegalCharactersInProvider(SourceLineNumber sourceLineNumbers, string attributeName, char illegalChar, string illegalChars)
10 {
11 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);
12 }
13
14 public static Message ReservedValue(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string attributeValue)
15 {
16 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);
17 }
18
19 public static Message IllegalName(SourceLineNumber sourceLineNumbers, string parentElement, string name)
20 {
21 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);
22 }
23
24 public static Message ExampleRegid(SourceLineNumber sourceLineNumbers, string regid)
25 {
26 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);
27 }
28
29 private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args)
30 {
31 return new Message(sourceLineNumber, MessageLevel.Error, (int)id, format, args);
32 }
33
34 public enum Ids
35 {
36 IllegalCharactersInProvider = 5400,
37 ReservedValue = 5401,
38
39 IllegalName = 6601,
40 ExampleRegid = 6602,
41 } // 5400-5499 and 6600-6699 were the ranges for Dependency and Tag which are now in Core between CompilerWarnings and CompilerErrors.
42 }
43}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using WixToolset.Data;
6
7 internal static class CompilerWarnings
8 {
9 public static Message DirectoryRefStandardDirectoryDeprecated(SourceLineNumber sourceLineNumbers, string directoryId)
10 {
11 return Message(sourceLineNumbers, Ids.DirectoryRefStandardDirectoryDeprecated, "Using DirectoryRef to reference the standard directory '{0}' is deprecated. Use the StandardDirectory element instead.", directoryId);
12 }
13
14 public static Message DefiningStandardDirectoryDeprecated(SourceLineNumber sourceLineNumbers, string directoryId)
15 {
16 return Message(sourceLineNumbers, Ids.DefiningStandardDirectoryDeprecated, "It is no longer necessary to define the standard directory '{0}'. Use the StandardDirectory element instead.", directoryId);
17 }
18
19 public static Message DiscouragedVersionAttribute(SourceLineNumber sourceLineNumbers)
20 {
21 return Message(sourceLineNumbers, Ids.DiscouragedVersionAttribute, "The Provides/@Version attribute should not be specified in an MSI package. The ProductVersion will be used by default.");
22 }
23
24 public static Message DiscouragedVersionAttribute(SourceLineNumber sourceLineNumbers, string id)
25 {
26 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);
27 }
28
29 public static Message PropertyRemoved(string name)
30 {
31 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);
32 }
33
34 public static Message ProvidesKeyNotFound(SourceLineNumber sourceLineNumbers, string id)
35 {
36 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);
37 }
38
39 public static Message RequiresKeyNotFound(SourceLineNumber sourceLineNumbers, string id)
40 {
41 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);
42 }
43
44 public static Message Win64Component(SourceLineNumber sourceLineNumbers, string componentId)
45 {
46 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);
47 }
48
49 private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args)
50 {
51 return new Message(sourceLineNumber, MessageLevel.Warning, (int)id, format, args);
52 }
53
54 public enum Ids
55 {
56 ProvidesKeyNotFound = 5431,
57 RequiresKeyNotFound = 5432,
58 PropertyRemoved = 5433,
59 DiscouragedVersionAttribute = 5434,
60 Win64Component = 5435,
61 DirectoryRefStandardDirectoryDeprecated = 5436,
62 DefiningStandardDirectoryDeprecated = 5437,
63 } // 5400-5499 and 6600-6699 were the ranges for Dependency and Tag which are now in Core between CompilerWarnings and CompilerErrors.
64 }
65}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.Globalization;
9 using System.IO;
10 using System.Linq;
11 using System.Xml.Linq;
12 using WixToolset.Data;
13 using WixToolset.Data.Burn;
14 using WixToolset.Data.Symbols;
15 using WixToolset.Extensibility;
16
17 /// <summary>
18 /// Compiler of the WiX toolset.
19 /// </summary>
20 internal partial class Compiler : ICompiler
21 {
22 private static readonly Identifier BurnUXContainerId = new Identifier(AccessModifier.Section, BurnConstants.BurnUXContainerName);
23 private static readonly Identifier BurnDefaultAttachedContainerId = new Identifier(AccessModifier.Section, BurnConstants.BurnDefaultAttachedContainerName);
24 private static readonly Identifier BundleLayoutOnlyPayloads = new Identifier(AccessModifier.Section, BurnConstants.BundleLayoutOnlyPayloadsName);
25
26 /// <summary>
27 /// Parses an ApprovedExeForElevation element.
28 /// </summary>
29 /// <param name="node">Element to parse</param>
30 private void ParseApprovedExeForElevation(XElement node)
31 {
32 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
33 Identifier id = null;
34 string key = null;
35 string valueName = null;
36 var win64 = this.Context.IsCurrentPlatform64Bit;
37
38 foreach (var attrib in node.Attributes())
39 {
40 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
41 {
42 switch (attrib.Name.LocalName)
43 {
44 case "Id":
45 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
46 break;
47 case "Bitness":
48 var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
49 switch (bitnessValue)
50 {
51 case "always32":
52 win64 = false;
53 break;
54 case "always64":
55 win64 = true;
56 break;
57 case "default":
58 case "":
59 break;
60 default:
61 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64"));
62 break;
63 }
64 break;
65 case "Key":
66 key = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
67 break;
68 case "Value":
69 valueName = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
70 break;
71 default:
72 this.Core.UnexpectedAttribute(node, attrib);
73 break;
74 }
75 }
76 else
77 {
78 this.Core.ParseExtensionAttribute(node, attrib);
79 }
80 }
81
82 if (null == id)
83 {
84 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
85 }
86
87 if (null == key)
88 {
89 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key"));
90 }
91
92 var attributes = WixApprovedExeForElevationAttributes.None;
93
94 if (win64)
95 {
96 attributes |= WixApprovedExeForElevationAttributes.Win64;
97 }
98
99 this.Core.ParseForExtensionElements(node);
100
101 if (!this.Core.EncounteredError)
102 {
103 this.Core.AddSymbol(new WixApprovedExeForElevationSymbol(sourceLineNumbers, id)
104 {
105 Key = key,
106 ValueName = valueName,
107 Attributes = attributes,
108 });
109 }
110 }
111
112 /// <summary>
113 /// Parses a Bundle element.
114 /// </summary>
115 /// <param name="node">Element to parse</param>
116 private void ParseBundleElement(XElement node)
117 {
118 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
119 string copyright = null;
120 string aboutUrl = null;
121 var compressed = YesNoDefaultType.Default;
122 WixBundleAttributes attributes = 0;
123 string helpTelephone = null;
124 string helpUrl = null;
125 string manufacturer = null;
126 string name = null;
127 string tag = null;
128 string updateUrl = null;
129 string upgradeCode = null;
130 string version = null;
131 string condition = null;
132 string parentName = null;
133
134 string fileSystemSafeBundleName = null;
135 string logVariablePrefixAndExtension = null;
136 string iconSourceFile = null;
137 string splashScreenSourceFile = null;
138
139 // Process only standard attributes until the active section is initialized.
140 foreach (var attrib in node.Attributes())
141 {
142 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
143 {
144 switch (attrib.Name.LocalName)
145 {
146 case "AboutUrl":
147 aboutUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
148 break;
149 case "Compressed":
150 compressed = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib);
151 break;
152 case "Condition":
153 condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
154 break;
155 case "Copyright":
156 copyright = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
157 break;
158 case "DisableModify":
159 var value = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
160 switch (value)
161 {
162 case "button":
163 attributes |= WixBundleAttributes.SingleChangeUninstallButton;
164 break;
165 case "yes":
166 attributes |= WixBundleAttributes.DisableModify;
167 break;
168 case "no":
169 break;
170 default:
171 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, value, "button", "yes", "no"));
172 break;
173 }
174 break;
175 case "DisableRemove":
176 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
177 {
178 attributes |= WixBundleAttributes.DisableRemove;
179 }
180 break;
181 case "HelpTelephone":
182 helpTelephone = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
183 break;
184 case "HelpUrl":
185 helpUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
186 break;
187 case "Manufacturer":
188 manufacturer = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
189 break;
190 case "IconSourceFile":
191 iconSourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
192 break;
193 case "Name":
194 name = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
195 break;
196 case "ParentName":
197 parentName = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
198 break;
199 case "ProviderKey":
200 // This can't be processed until we create the section.
201 break;
202 case "SplashScreenSourceFile":
203 splashScreenSourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
204 break;
205 case "Tag":
206 tag = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
207 break;
208 case "UpdateUrl":
209 updateUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
210 break;
211 case "UpgradeCode":
212 upgradeCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
213 break;
214 case "Version":
215 version = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib);
216 break;
217 default:
218 this.Core.UnexpectedAttribute(node, attrib);
219 break;
220 }
221 }
222 }
223
224 if (String.IsNullOrEmpty(version))
225 {
226 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version"));
227 }
228 else if (!CompilerCore.IsValidModuleOrBundleVersion(version))
229 {
230 this.Core.Write(WarningMessages.InvalidModuleOrBundleVersion(sourceLineNumbers, "Bundle", version));
231 }
232
233 if (String.IsNullOrEmpty(upgradeCode))
234 {
235 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "UpgradeCode"));
236 }
237
238 if (String.IsNullOrEmpty(copyright))
239 {
240 if (String.IsNullOrEmpty(manufacturer))
241 {
242 copyright = "Copyright (c). All rights reserved.";
243 }
244 else
245 {
246 copyright = String.Format("Copyright (c) {0}. All rights reserved.", manufacturer);
247 }
248 }
249
250 if (String.IsNullOrEmpty(name))
251 {
252 logVariablePrefixAndExtension = String.Concat("WixBundleLog:Setup:log");
253 }
254 else
255 {
256 // Ensure only allowable path characters are in "name" (and change spaces to underscores).
257 fileSystemSafeBundleName = CompilerCore.MakeValidLongFileName(name.Replace(' ', '_'), '_');
258 logVariablePrefixAndExtension = String.Concat("WixBundleLog:", fileSystemSafeBundleName, ":log");
259 }
260
261 this.activeName = String.IsNullOrEmpty(name) ? Common.GenerateGuid() : name;
262 this.Core.CreateActiveSection(this.activeName, SectionType.Bundle, this.Context.CompilationId);
263
264 // Now that the active section is initialized, process only extension attributes and the special ProviderKey attribute.
265 foreach (var attrib in node.Attributes())
266 {
267 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
268 {
269 switch (attrib.Name.LocalName)
270 {
271 case "ProviderKey":
272 this.ParseBundleProviderKeyAttribute(sourceLineNumbers, node, attrib);
273 break;
274 // Unknown attributes were reported earlier.
275 }
276 }
277 else
278 {
279 this.Core.ParseExtensionAttribute(node, attrib);
280 }
281 }
282
283 var baSeen = false;
284 var chainSeen = false;
285 var logSeen = false;
286
287 foreach (var child in node.Elements())
288 {
289 if (CompilerCore.WixNamespace == child.Name.Namespace)
290 {
291 switch (child.Name.LocalName)
292 {
293 case "ApprovedExeForElevation":
294 this.ParseApprovedExeForElevation(child);
295 break;
296 case "BootstrapperApplication":
297 if (baSeen)
298 {
299 var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
300 this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, "BootstrapperApplication"));
301 }
302 this.ParseBootstrapperApplicationElement(child);
303 baSeen = true;
304 break;
305 case "BootstrapperApplicationRef":
306 this.ParseBootstrapperApplicationRefElement(child);
307 break;
308 case "BundleCustomData":
309 this.ParseBundleCustomDataElement(child);
310 break;
311 case "BundleCustomDataRef":
312 this.ParseBundleCustomDataRefElement(child);
313 break;
314 case "BundleExtension":
315 this.ParseBundleExtensionElement(child);
316 break;
317 case "BundleExtensionRef":
318 this.ParseSimpleRefElement(child, SymbolDefinitions.WixBundleExtension);
319 break;
320 case "OptionalUpdateRegistration":
321 this.ParseOptionalUpdateRegistrationElement(child, manufacturer, parentName, name);
322 break;
323 case "Chain":
324 if (chainSeen)
325 {
326 var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
327 this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, "Chain"));
328 }
329 this.ParseChainElement(child);
330 chainSeen = true;
331 break;
332 case "Container":
333 this.ParseContainerElement(child);
334 break;
335 case "ContainerRef":
336 this.ParseSimpleRefElement(child, SymbolDefinitions.WixBundleContainer);
337 break;
338 case "Log":
339 if (logSeen)
340 {
341 var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
342 this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, "Log"));
343 }
344 logVariablePrefixAndExtension = this.ParseLogElement(child, fileSystemSafeBundleName);
345 logSeen = true;
346 break;
347 case "PayloadGroup":
348 this.ParsePayloadGroupElement(child, ComplexReferenceParentType.Layout, Compiler.BundleLayoutOnlyPayloads);
349 break;
350 case "PayloadGroupRef":
351 this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Layout, Compiler.BundleLayoutOnlyPayloads, ComplexReferenceChildType.Unknown, null);
352 break;
353 case "RelatedBundle":
354 this.ParseRelatedBundleElement(child);
355 break;
356 case "Requires":
357 this.ParseRequiresElement(child, null);
358 break;
359 case "SetVariable":
360 this.ParseSetVariableElement(child);
361 break;
362 case "SetVariableRef":
363 this.ParseSimpleRefElement(child, SymbolDefinitions.WixSetVariable);
364 break;
365 case "SoftwareTag":
366 this.ParseBundleTagElement(child);
367 break;
368 case "Update":
369 this.ParseUpdateElement(child);
370 break;
371 case "Variable":
372 this.ParseVariableElement(child);
373 break;
374 case "WixVariable":
375 this.ParseWixVariableElement(child);
376 break;
377 default:
378 this.Core.UnexpectedElement(node, child);
379 break;
380 }
381 }
382 else
383 {
384 this.Core.ParseExtensionElement(node, child);
385 }
386 }
387
388 if (!chainSeen)
389 {
390 this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "Chain"));
391 }
392
393 if (!this.Core.EncounteredError)
394 {
395 var symbol = this.Core.AddSymbol(new WixBundleSymbol(sourceLineNumbers)
396 {
397 UpgradeCode = upgradeCode,
398 Version = version,
399 Copyright = copyright,
400 Name = name,
401 Manufacturer = manufacturer,
402 Attributes = attributes,
403 AboutUrl = aboutUrl,
404 HelpUrl = helpUrl,
405 HelpTelephone = helpTelephone,
406 UpdateUrl = updateUrl,
407 Compressed = YesNoDefaultType.Yes == compressed ? true : YesNoDefaultType.No == compressed ? (bool?)false : null,
408 IconSourceFile = iconSourceFile,
409 SplashScreenSourceFile = splashScreenSourceFile,
410 Condition = condition,
411 Tag = tag,
412 Platform = this.CurrentPlatform,
413 ParentName = parentName,
414 });
415
416 if (!String.IsNullOrEmpty(logVariablePrefixAndExtension))
417 {
418 var split = logVariablePrefixAndExtension.Split(':');
419 symbol.LogPathVariable = split[0];
420 symbol.LogPrefix = split[1];
421 symbol.LogExtension = split[2];
422 }
423
424 if (null != upgradeCode)
425 {
426 this.Core.AddSymbol(new WixRelatedBundleSymbol(sourceLineNumbers)
427 {
428 BundleId = upgradeCode,
429 Action = RelatedBundleActionType.Upgrade,
430 });
431 }
432
433 this.Core.AddSymbol(new WixBundleContainerSymbol(sourceLineNumbers, Compiler.BurnDefaultAttachedContainerId)
434 {
435 Name = "bundle-attached.cab",
436 Type = ContainerType.Attached,
437 });
438
439 // Ensure that the bundle stores the well-known persisted values.
440 this.Core.AddSymbol(new WixBundleVariableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, BurnConstants.BURN_BUNDLE_NAME))
441 {
442 Hidden = false,
443 Persisted = true,
444 });
445
446 this.Core.AddSymbol(new WixBundleVariableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, BurnConstants.BURN_BUNDLE_ORIGINAL_SOURCE))
447 {
448 Hidden = false,
449 Persisted = true,
450 });
451
452 this.Core.AddSymbol(new WixBundleVariableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, BurnConstants.BURN_BUNDLE_ORIGINAL_SOURCE_FOLDER))
453 {
454 Hidden = false,
455 Persisted = true,
456 });
457
458 this.Core.AddSymbol(new WixBundleVariableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, BurnConstants.BURN_BUNDLE_LAST_USED_SOURCE))
459 {
460 Hidden = false,
461 Persisted = true,
462 });
463 }
464 }
465
466 /// <summary>
467 /// Parse a Container element.
468 /// </summary>
469 /// <param name="node">Element to parse</param>
470 /// <param name="fileSystemSafeBundleName"></param>
471 private string ParseLogElement(XElement node, string fileSystemSafeBundleName)
472 {
473 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
474 var disableLog = YesNoType.NotSet;
475 var variable = "WixBundleLog";
476 var logPrefix = fileSystemSafeBundleName ?? "Setup";
477 var logExtension = ".log";
478
479 foreach (var attrib in node.Attributes())
480 {
481 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
482 {
483 switch (attrib.Name.LocalName)
484 {
485 case "Disable":
486 disableLog = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
487 break;
488 case "PathVariable":
489 variable = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty);
490 break;
491 case "Prefix":
492 logPrefix = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
493 break;
494 case "Extension":
495 logExtension = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
496 break;
497 default:
498 this.Core.UnexpectedAttribute(node, attrib);
499 break;
500 }
501 }
502 else
503 {
504 this.Core.ParseExtensionAttribute(node, attrib);
505 }
506 }
507
508 if (!logExtension.StartsWith(".", StringComparison.Ordinal))
509 {
510 logExtension = String.Concat(".", logExtension);
511 }
512
513 this.Core.ParseForExtensionElements(node);
514
515 return YesNoType.Yes == disableLog ? null : String.Join(":", variable, logPrefix, logExtension);
516 }
517
518 /// <summary>
519 /// Parse a Container element.
520 /// </summary>
521 /// <param name="node">Element to parse</param>
522 private void ParseContainerElement(XElement node)
523 {
524 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
525 Identifier id = null;
526 string downloadUrl = null;
527 string name = null;
528 var type = ContainerType.Detached;
529
530 foreach (var attrib in node.Attributes())
531 {
532 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
533 {
534 switch (attrib.Name.LocalName)
535 {
536 case "Id":
537 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
538 if (id?.Id == BurnConstants.BurnUXContainerName || id?.Id == BurnConstants.BurnDefaultAttachedContainerName)
539 {
540 this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Id", id.Id));
541 }
542 break;
543 case "DownloadUrl":
544 downloadUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
545 break;
546 case "Name":
547 name = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
548 break;
549 case "Type":
550 var typeString = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
551 switch (typeString)
552 {
553 case "attached":
554 type = ContainerType.Attached;
555 break;
556 case "detached":
557 type = ContainerType.Detached;
558 break;
559 default:
560 this.Core.Write(ErrorMessages.IllegalAttributeValueWithLegalList(sourceLineNumbers, node.Name.LocalName, "Type", typeString, "attached, detached"));
561 break;
562 }
563 break;
564 default:
565 this.Core.UnexpectedAttribute(node, attrib);
566 break;
567 }
568 }
569 else
570 {
571 this.Core.ParseExtensionAttribute(node, attrib);
572 }
573 }
574
575 if (null == id)
576 {
577 if (!String.IsNullOrEmpty(name))
578 {
579 id = this.Core.CreateIdentifierFromFilename(name);
580 }
581
582 if (null == id)
583 {
584 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
585 id = Identifier.Invalid;
586 }
587 else if (!Common.IsIdentifier(id.Id))
588 {
589 this.Core.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, node.Name.LocalName, "Id", id.Id));
590 }
591 }
592 else if (null == name)
593 {
594 name = id.Id;
595 }
596
597 if (!String.IsNullOrEmpty(downloadUrl) && ContainerType.Detached != type)
598 {
599 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DownloadUrl", "Type", "attached"));
600 }
601
602 foreach (var child in node.Elements())
603 {
604 if (CompilerCore.WixNamespace == child.Name.Namespace)
605 {
606 switch (child.Name.LocalName)
607 {
608 case "PackageGroupRef":
609 this.ParsePackageGroupRefElement(child, ComplexReferenceParentType.Container, id.Id);
610 break;
611 default:
612 this.Core.UnexpectedElement(node, child);
613 break;
614 }
615 }
616 else
617 {
618 this.Core.ParseExtensionElement(node, child);
619 }
620 }
621
622
623 if (!this.Core.EncounteredError)
624 {
625 this.Core.AddSymbol(new WixBundleContainerSymbol(sourceLineNumbers, id)
626 {
627 Name = name,
628 Type = type,
629 DownloadUrl = downloadUrl
630 });
631 }
632 }
633
634 /// <summary>
635 /// Parse the BoostrapperApplication element.
636 /// </summary>
637 /// <param name="node">Element to parse</param>
638 private void ParseBootstrapperApplicationElement(XElement node)
639 {
640 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
641 Identifier id = null;
642 Identifier previousId = null;
643 var previousType = ComplexReferenceChildType.Unknown;
644
645 foreach (var attrib in node.Attributes())
646 {
647 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
648 {
649 switch (attrib.Name.LocalName)
650 {
651 case "Id":
652 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
653 break;
654 default:
655 this.Core.UnexpectedAttribute(node, attrib);
656 break;
657 }
658 }
659 }
660
661 foreach (var child in node.Elements())
662 {
663 if (CompilerCore.WixNamespace == child.Name.Namespace)
664 {
665 switch (child.Name.LocalName)
666 {
667 case "BootstrapperApplicationDll":
668 previousId = this.ParseBootstrapperApplicationDllElement(child, id, previousType, previousId);
669 previousType = ComplexReferenceChildType.Payload;
670 break;
671 case "Payload":
672 previousId = this.ParsePayloadElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId);
673 previousType = ComplexReferenceChildType.Payload;
674 break;
675 case "PayloadGroupRef":
676 previousId = this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId);
677 previousType = ComplexReferenceChildType.PayloadGroup;
678 break;
679 default:
680 this.Core.UnexpectedElement(node, child);
681 break;
682 }
683 }
684 else
685 {
686 this.Core.ParseExtensionElement(node, child);
687 }
688 }
689
690 if (id != null)
691 {
692 this.Core.AddSymbol(new WixBootstrapperApplicationSymbol(sourceLineNumbers, id));
693 }
694 }
695
696 /// <summary>
697 /// Parse the BoostrapperApplication element.
698 /// </summary>
699 /// <param name="node">Element to parse</param>
700 /// <param name="defaultId"></param>
701 /// <param name="previousType"></param>
702 /// <param name="previousId"></param>
703 private Identifier ParseBootstrapperApplicationDllElement(XElement node, Identifier defaultId, ComplexReferenceChildType previousType, Identifier previousId)
704 {
705 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
706 var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node)
707 {
708 Id = defaultId,
709 };
710 var dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.PerMonitorV2;
711
712 // This list lets us evaluate extension attributes *after* all core attributes
713 // have been parsed and dealt with, regardless of authoring order.
714 var extensionAttributes = new List<XAttribute>();
715
716 foreach (var attrib in node.Attributes())
717 {
718 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
719 {
720 switch (attrib.Name.LocalName)
721 {
722 case "Id":
723 compilerPayload.ParseId(attrib);
724 break;
725 case "Name":
726 compilerPayload.ParseName(attrib);
727 break;
728 case "SourceFile":
729 compilerPayload.ParseSourceFile(attrib);
730 break;
731 case "DpiAwareness":
732 var dpiAwarenessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
733 switch (dpiAwarenessValue)
734 {
735 case "gdiScaled":
736 dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.GdiScaled;
737 break;
738 case "perMonitor":
739 dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.PerMonitor;
740 break;
741 case "perMonitorV2":
742 dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.PerMonitorV2;
743 break;
744 case "system":
745 dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.System;
746 break;
747 case "unaware":
748 dpiAwareness = WixBootstrapperApplicationDpiAwarenessType.Unaware;
749 break;
750 default:
751 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "DpiAwareness", dpiAwarenessValue, "gdiScaled", "perMonitor", "perMonitorV2", "system", "unaware"));
752 break;
753 }
754 break;
755 default:
756 this.Core.UnexpectedAttribute(node, attrib);
757 break;
758 }
759 }
760 else
761 {
762 extensionAttributes.Add(attrib);
763 }
764 }
765
766 compilerPayload.FinishCompilingPayload();
767
768 // Now that the Id is known, we can parse the extension attributes.
769 var context = new Dictionary<string, string>
770 {
771 ["Id"] = compilerPayload.Id.Id,
772 };
773
774 foreach (var extensionAttribute in extensionAttributes)
775 {
776 this.Core.ParseExtensionAttribute(node, extensionAttribute, context);
777 }
778
779 foreach (var child in node.Elements())
780 {
781 if (CompilerCore.WixNamespace == child.Name.Namespace)
782 {
783 switch (child.Name.LocalName)
784 {
785 default:
786 this.Core.UnexpectedElement(node, child);
787 break;
788 }
789 }
790 else
791 {
792 this.Core.ParseExtensionElement(node, child);
793 }
794 }
795
796 if (!this.Core.EncounteredError)
797 {
798 compilerPayload.CreatePayloadSymbol(ComplexReferenceParentType.Container, Compiler.BurnUXContainerId.Id, previousType, previousId?.Id);
799 this.Core.AddSymbol(new WixBundleContainerSymbol(sourceLineNumbers, Compiler.BurnUXContainerId)
800 {
801 Name = "bundle-ux.cab",
802 Type = ContainerType.Attached
803 });
804
805 this.Core.AddSymbol(new WixBootstrapperApplicationDllSymbol(sourceLineNumbers, compilerPayload.Id)
806 {
807 DpiAwareness = dpiAwareness,
808 });
809 }
810
811 return compilerPayload.Id;
812 }
813
814 /// <summary>
815 /// Parse the BoostrapperApplicationRef element.
816 /// </summary>
817 /// <param name="node">Element to parse</param>
818 private void ParseBootstrapperApplicationRefElement(XElement node)
819 {
820 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
821 string id = null;
822 Identifier previousId = null;
823 var previousType = ComplexReferenceChildType.Unknown;
824
825 foreach (var attrib in node.Attributes())
826 {
827 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
828 {
829 switch (attrib.Name.LocalName)
830 {
831 case "Id":
832 id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
833 break;
834 default:
835 this.Core.UnexpectedAttribute(node, attrib);
836 break;
837 }
838 }
839 else
840 {
841 this.Core.ParseExtensionAttribute(node, attrib);
842 }
843 }
844
845 foreach (var child in node.Elements())
846 {
847 if (CompilerCore.WixNamespace == child.Name.Namespace)
848 {
849 switch (child.Name.LocalName)
850 {
851 case "Payload":
852 previousId = this.ParsePayloadElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId);
853 previousType = ComplexReferenceChildType.Payload;
854 break;
855 case "PayloadGroupRef":
856 previousId = this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId);
857 previousType = ComplexReferenceChildType.PayloadGroup;
858 break;
859 default:
860 this.Core.UnexpectedElement(node, child);
861 break;
862 }
863 }
864 else
865 {
866 this.Core.ParseExtensionElement(node, child);
867 }
868 }
869
870
871 if (String.IsNullOrEmpty(id))
872 {
873 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
874 }
875 else
876 {
877 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBootstrapperApplication, id);
878 }
879 }
880
881
882
883 /// <summary>
884 /// Parses a BundleCustomData element.
885 /// </summary>
886 /// <param name="node">Element to parse.</param>
887 private void ParseBundleCustomDataElement(XElement node)
888 {
889 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
890 string customDataId = null;
891 WixBundleCustomDataType? customDataType = null;
892 string extensionId = null;
893 var attributeDefinitions = new List<WixBundleCustomDataAttributeSymbol>();
894 var foundAttributeDefinitions = false;
895
896 foreach (var attrib in node.Attributes())
897 {
898 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
899 {
900 switch (attrib.Name.LocalName)
901 {
902 case "Id":
903 customDataId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
904 break;
905 case "Type":
906 var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
907 switch (typeValue)
908 {
909 case "bootstrapperApplication":
910 customDataType = WixBundleCustomDataType.BootstrapperApplication;
911 break;
912 case "bundleExtension":
913 customDataType = WixBundleCustomDataType.BundleExtension;
914 break;
915 default:
916 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Type", typeValue, "bootstrapperApplication", "bundleExtension"));
917 customDataType = WixBundleCustomDataType.Unknown; // set a value to prevent expected attribute error below.
918 break;
919 }
920 break;
921 case "ExtensionId":
922 extensionId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
923 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundleExtension, extensionId);
924 break;
925 default:
926 this.Core.UnexpectedAttribute(node, attrib);
927 break;
928 }
929 }
930 else
931 {
932 this.Core.ParseExtensionAttribute(node, attrib);
933 }
934 }
935
936 if (null == customDataId)
937 {
938 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
939 }
940
941 var hasExtensionId = null != extensionId;
942 if (!customDataType.HasValue)
943 {
944 customDataType = hasExtensionId ? WixBundleCustomDataType.BundleExtension : WixBundleCustomDataType.BootstrapperApplication;
945 }
946
947 if (!customDataType.HasValue)
948 {
949 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Type"));
950 }
951 else if (hasExtensionId)
952 {
953 if (customDataType.Value == WixBundleCustomDataType.BootstrapperApplication)
954 {
955 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "ExtensonId", "Type", "bootstrapperApplication"));
956 }
957 }
958 else if (customDataType.Value == WixBundleCustomDataType.BundleExtension)
959 {
960 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ExtensionId", "Type", "bundleExtension"));
961 }
962
963 foreach (var child in node.Elements())
964 {
965 if (CompilerCore.WixNamespace == child.Name.Namespace)
966 {
967 var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
968 switch (child.Name.LocalName)
969 {
970 case "BundleAttributeDefinition":
971 foundAttributeDefinitions = true;
972
973 var attributeDefinition = this.ParseBundleAttributeDefinitionElement(child, childSourceLineNumbers, customDataId);
974 if (attributeDefinition != null)
975 {
976 attributeDefinitions.Add(attributeDefinition);
977 }
978 break;
979 case "BundleElement":
980 this.ParseBundleElementElement(child, childSourceLineNumbers, customDataId);
981 break;
982 default:
983 this.Core.UnexpectedElement(node, child);
984 break;
985 }
986 }
987 else
988 {
989 this.Core.ParseExtensionElement(node, child);
990 }
991 }
992
993 if (attributeDefinitions.Count > 0)
994 {
995 if (!this.Core.EncounteredError)
996 {
997 var attributeNames = String.Join(new string(WixBundleCustomDataSymbol.AttributeNamesSeparator, 1), attributeDefinitions.Select(c => c.Name));
998
999 this.Core.AddSymbol(new WixBundleCustomDataSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, customDataId))
1000 {
1001 AttributeNames = attributeNames,
1002 Type = customDataType.Value,
1003 BundleExtensionRef = extensionId,
1004 });
1005 }
1006 }
1007 else if (!foundAttributeDefinitions)
1008 {
1009 this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "BundleAttributeDefinition"));
1010 }
1011 }
1012
1013 /// <summary>
1014 /// Parses a BundleCustomDataRef element.
1015 /// </summary>
1016 /// <param name="node">Element to parse.</param>
1017 private void ParseBundleCustomDataRefElement(XElement node)
1018 {
1019 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1020 string customDataId = null;
1021
1022 foreach (var attrib in node.Attributes())
1023 {
1024 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
1025 {
1026 switch (attrib.Name.LocalName)
1027 {
1028 case "Id":
1029 customDataId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
1030 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundleCustomData, customDataId);
1031 break;
1032 default:
1033 this.Core.UnexpectedAttribute(node, attrib);
1034 break;
1035 }
1036 }
1037 else
1038 {
1039 this.Core.ParseExtensionAttribute(node, attrib);
1040 }
1041 }
1042
1043 if (null == customDataId)
1044 {
1045 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
1046 }
1047
1048 foreach (var child in node.Elements())
1049 {
1050 if (CompilerCore.WixNamespace == child.Name.Namespace)
1051 {
1052 var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
1053 switch (child.Name.LocalName)
1054 {
1055 case "BundleElement":
1056 this.ParseBundleElementElement(child, childSourceLineNumbers, customDataId);
1057 break;
1058 default:
1059 this.Core.UnexpectedElement(node, child);
1060 break;
1061 }
1062 }
1063 else
1064 {
1065 this.Core.ParseExtensionElement(node, child);
1066 }
1067 }
1068 }
1069
1070 /// <summary>
1071 /// Parses a BundleAttributeDefinition element.
1072 /// </summary>
1073 /// <param name="node">Element to parse.</param>
1074 /// <param name="sourceLineNumbers">Element's SourceLineNumbers.</param>
1075 /// <param name="customDataId">BundleCustomData Id.</param>
1076 private WixBundleCustomDataAttributeSymbol ParseBundleAttributeDefinitionElement(XElement node, SourceLineNumber sourceLineNumbers, string customDataId)
1077 {
1078 string attributeName = null;
1079
1080 foreach (var attrib in node.Attributes())
1081 {
1082 switch (attrib.Name.LocalName)
1083 {
1084 case "Id":
1085 attributeName = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1086 break;
1087 default:
1088 this.Core.UnexpectedAttribute(node, attrib);
1089 break;
1090 }
1091 }
1092
1093 if (null == attributeName)
1094 {
1095 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
1096 }
1097
1098 this.Core.ParseForExtensionElements(node);
1099
1100 if (this.Core.EncounteredError)
1101 {
1102 return null;
1103 }
1104
1105 var customDataAttribute = this.Core.AddSymbol(new WixBundleCustomDataAttributeSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, customDataId, attributeName))
1106 {
1107 CustomDataRef = customDataId,
1108 Name = attributeName,
1109 });
1110 return customDataAttribute;
1111 }
1112
1113 /// <summary>
1114 /// Parses a BundleElement element.
1115 /// </summary>
1116 /// <param name="node">Element to parse.</param>
1117 /// <param name="sourceLineNumbers">Element's SourceLineNumbers.</param>
1118 /// <param name="customDataId">BundleCustomData Id.</param>
1119 private void ParseBundleElementElement(XElement node, SourceLineNumber sourceLineNumbers, string customDataId)
1120 {
1121 var elementId = Guid.NewGuid().ToString("N").ToUpperInvariant();
1122
1123 foreach (var attrib in node.Attributes())
1124 {
1125 this.Core.ParseExtensionAttribute(node, attrib);
1126 }
1127
1128 foreach (var child in node.Elements())
1129 {
1130 var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
1131 switch (child.Name.LocalName)
1132 {
1133 case "BundleAttribute":
1134 string attributeName = null;
1135 string value = null;
1136 foreach (var attrib in child.Attributes())
1137 {
1138 switch (attrib.Name.LocalName)
1139 {
1140 case "Id":
1141 attributeName = this.Core.GetAttributeValue(childSourceLineNumbers, attrib);
1142 break;
1143 case "Value":
1144 value = this.Core.GetAttributeValue(childSourceLineNumbers, attrib);
1145 break;
1146 default:
1147 this.Core.ParseExtensionAttribute(child, attrib);
1148 break;
1149 }
1150 }
1151
1152 if (null == attributeName)
1153 {
1154 this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Id"));
1155 }
1156
1157 if (!this.Core.EncounteredError)
1158 {
1159 this.Core.AddSymbol(new WixBundleCustomDataCellSymbol(childSourceLineNumbers, new Identifier(AccessModifier.Section, customDataId, elementId, attributeName))
1160 {
1161 ElementId = elementId,
1162 AttributeRef = attributeName,
1163 CustomDataRef = customDataId,
1164 Value = value,
1165 });
1166 }
1167 break;
1168 default:
1169 this.Core.UnexpectedElement(node, child);
1170 break;
1171 }
1172 }
1173
1174 if (!this.Core.EncounteredError)
1175 {
1176 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundleCustomData, customDataId);
1177 }
1178 }
1179
1180 /// <summary>
1181 /// Parse the BundleExtension element.
1182 /// </summary>
1183 /// <param name="node">Element to parse</param>
1184 private void ParseBundleExtensionElement(XElement node)
1185 {
1186 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1187 var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node);
1188 Identifier previousId = null;
1189 var previousType = ComplexReferenceChildType.Unknown;
1190
1191 // This list lets us evaluate extension attributes *after* all core attributes
1192 // have been parsed and dealt with, regardless of authoring order.
1193 var extensionAttributes = new List<XAttribute>();
1194
1195 foreach (var attrib in node.Attributes())
1196 {
1197 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
1198 {
1199 switch (attrib.Name.LocalName)
1200 {
1201 case "Id":
1202 compilerPayload.ParseId(attrib);
1203 break;
1204 case "Name":
1205 compilerPayload.ParseName(attrib);
1206 break;
1207 case "SourceFile":
1208 compilerPayload.ParseSourceFile(attrib);
1209 break;
1210 default:
1211 this.Core.UnexpectedAttribute(node, attrib);
1212 break;
1213 }
1214 }
1215 else
1216 {
1217 extensionAttributes.Add(attrib);
1218 }
1219 }
1220
1221 compilerPayload.FinishCompilingPayload();
1222
1223 // Now that the Id is known, we can parse the extension attributes.
1224 var context = new Dictionary<string, string>
1225 {
1226 ["Id"] = compilerPayload.Id.Id,
1227 };
1228
1229 foreach (var extensionAttribute in extensionAttributes)
1230 {
1231 this.Core.ParseExtensionAttribute(node, extensionAttribute, context);
1232 }
1233
1234 compilerPayload.CreatePayloadSymbol(ComplexReferenceParentType.Container, Compiler.BurnUXContainerId.Id, previousType, previousId?.Id);
1235 previousId = compilerPayload.Id;
1236 previousType = ComplexReferenceChildType.Payload;
1237
1238 foreach (var child in node.Elements())
1239 {
1240 if (CompilerCore.WixNamespace == child.Name.Namespace)
1241 {
1242 switch (child.Name.LocalName)
1243 {
1244 case "Payload":
1245 previousId = this.ParsePayloadElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId);
1246 previousType = ComplexReferenceChildType.Payload;
1247 break;
1248 case "PayloadGroupRef":
1249 previousId = this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId);
1250 previousType = ComplexReferenceChildType.PayloadGroup;
1251 break;
1252 default:
1253 this.Core.UnexpectedElement(node, child);
1254 break;
1255 }
1256 }
1257 else
1258 {
1259 this.Core.ParseExtensionElement(node, child);
1260 }
1261 }
1262
1263 // Add the BundleExtension.
1264 if (!this.Core.EncounteredError)
1265 {
1266 this.Core.AddSymbol(new WixBundleExtensionSymbol(sourceLineNumbers, compilerPayload.Id)
1267 {
1268 PayloadRef = compilerPayload.Id.Id,
1269 });
1270 }
1271 }
1272
1273 /// <summary>
1274 /// Parse the OptionalUpdateRegistration element.
1275 /// </summary>
1276 /// <param name="node">The element to parse.</param>
1277 /// <param name="defaultManufacturer">The manufacturer.</param>
1278 /// <param name="defaultProductFamily">The product family.</param>
1279 /// <param name="defaultName">The bundle name.</param>
1280 private void ParseOptionalUpdateRegistrationElement(XElement node, string defaultManufacturer, string defaultProductFamily, string defaultName)
1281 {
1282 const string defaultClassification = "Update";
1283
1284 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1285 string manufacturer = null;
1286 string department = null;
1287 string productFamily = null;
1288 string name = null;
1289 var classification = defaultClassification;
1290
1291 foreach (var attrib in node.Attributes())
1292 {
1293 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
1294 {
1295 switch (attrib.Name.LocalName)
1296 {
1297 case "Manufacturer":
1298 manufacturer = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1299 break;
1300 case "Department":
1301 department = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1302 break;
1303 case "ProductFamily":
1304 productFamily = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1305 break;
1306 case "Name":
1307 name = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1308 break;
1309 case "Classification":
1310 classification = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1311 break;
1312 default:
1313 this.Core.UnexpectedAttribute(node, attrib);
1314 break;
1315 }
1316 }
1317 else
1318 {
1319 this.Core.ParseExtensionAttribute(node, attrib);
1320 }
1321 }
1322
1323 if (String.IsNullOrEmpty(manufacturer))
1324 {
1325 if (!String.IsNullOrEmpty(defaultManufacturer))
1326 {
1327 manufacturer = defaultManufacturer;
1328 }
1329 else
1330 {
1331 this.Core.Write(ErrorMessages.ExpectedAttributeInElementOrParent(sourceLineNumbers, node.Name.LocalName, "Manufacturer", node.Parent.Name.LocalName));
1332 }
1333 }
1334
1335 if (String.IsNullOrEmpty(productFamily))
1336 {
1337 if (!String.IsNullOrEmpty(defaultProductFamily))
1338 {
1339 productFamily = defaultProductFamily;
1340 }
1341 }
1342
1343 if (String.IsNullOrEmpty(name))
1344 {
1345 if (!String.IsNullOrEmpty(defaultName))
1346 {
1347 name = defaultName;
1348 }
1349 else
1350 {
1351 this.Core.Write(ErrorMessages.ExpectedAttributeInElementOrParent(sourceLineNumbers, node.Name.LocalName, "Name", node.Parent.Name.LocalName));
1352 }
1353 }
1354
1355 if (String.IsNullOrEmpty(classification))
1356 {
1357 this.Core.Write(ErrorMessages.IllegalEmptyAttributeValue(sourceLineNumbers, node.Name.LocalName, "Classification", defaultClassification));
1358 }
1359
1360 this.Core.ParseForExtensionElements(node);
1361
1362 if (!this.Core.EncounteredError)
1363 {
1364 this.Core.AddSymbol(new WixUpdateRegistrationSymbol(sourceLineNumbers)
1365 {
1366 Manufacturer = manufacturer,
1367 Department = department,
1368 ProductFamily = productFamily,
1369 Name = name,
1370 Classification = classification
1371 });
1372 }
1373 }
1374
1375 /// <summary>
1376 /// Parse Payload element.
1377 /// </summary>
1378 /// <param name="node">Element to parse</param>
1379 /// <param name="parentType">ComplexReferenceParentType of parent element. (BA or PayloadGroup)</param>
1380 /// <param name="parentId">Identifier of parent element.</param>
1381 /// <param name="previousType"></param>
1382 /// <param name="previousId"></param>
1383 private Identifier ParsePayloadElement(XElement node, ComplexReferenceParentType parentType, Identifier parentId, ComplexReferenceChildType previousType, Identifier previousId)
1384 {
1385 Debug.Assert(ComplexReferenceParentType.PayloadGroup == parentType || ComplexReferenceParentType.Package == parentType || ComplexReferenceParentType.Container == parentType);
1386 Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PayloadGroup == previousType || ComplexReferenceChildType.Payload == previousType);
1387
1388 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1389 var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node);
1390
1391 // This list lets us evaluate extension attributes *after* all core attributes
1392 // have been parsed and dealt with, regardless of authoring order.
1393 var extensionAttributes = new List<XAttribute>();
1394
1395 foreach (var attrib in node.Attributes())
1396 {
1397 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
1398 {
1399 var allowed = true;
1400 switch (attrib.Name.LocalName)
1401 {
1402 case "Id":
1403 compilerPayload.ParseId(attrib);
1404 break;
1405 case "Compressed":
1406 compilerPayload.ParseCompressed(attrib);
1407 break;
1408 case "Name":
1409 compilerPayload.ParseName(attrib);
1410 break;
1411 case "SourceFile":
1412 compilerPayload.ParseSourceFile(attrib);
1413 break;
1414 case "DownloadUrl":
1415 compilerPayload.ParseDownloadUrl(attrib);
1416 break;
1417 default:
1418 allowed = false;
1419 break;
1420 }
1421
1422 if (!allowed)
1423 {
1424 this.Core.UnexpectedAttribute(node, attrib);
1425 }
1426 }
1427 else
1428 {
1429 extensionAttributes.Add(attrib);
1430 }
1431 }
1432
1433 compilerPayload.FinishCompilingPayload();
1434
1435 // Now that the PayloadId is known, we can parse the extension attributes.
1436 var context = new Dictionary<string, string>
1437 {
1438 ["Id"] = compilerPayload.Id.Id,
1439 };
1440
1441 foreach (var extensionAttribute in extensionAttributes)
1442 {
1443 this.Core.ParseExtensionAttribute(node, extensionAttribute, context);
1444 }
1445
1446 foreach (var child in node.Elements())
1447 {
1448 if (CompilerCore.WixNamespace == child.Name.Namespace)
1449 {
1450 switch (child.Name.LocalName)
1451 {
1452 default:
1453 this.Core.UnexpectedElement(node, child);
1454 break;
1455 }
1456 }
1457 else
1458 {
1459 this.Core.ParseExtensionElement(node, child, context);
1460 }
1461 }
1462
1463 compilerPayload.CreatePayloadSymbol(parentType, parentId?.Id, previousType, previousId?.Id);
1464
1465 return compilerPayload.Id;
1466 }
1467
1468 /// <summary>
1469 /// Parse PayloadGroup element.
1470 /// </summary>
1471 /// <param name="node">Element to parse</param>
1472 /// <param name="parentType">Optional ComplexReferenceParentType of parent element. (typically another PayloadGroup)</param>
1473 /// <param name="parentId">Identifier of parent element.</param>
1474 private void ParsePayloadGroupElement(XElement node, ComplexReferenceParentType parentType, Identifier parentId)
1475 {
1476 Debug.Assert(ComplexReferenceParentType.Unknown == parentType || ComplexReferenceParentType.Layout == parentType || ComplexReferenceParentType.PayloadGroup == parentType);
1477
1478 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1479 Identifier id = null;
1480
1481 foreach (var attrib in node.Attributes())
1482 {
1483 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
1484 {
1485 switch (attrib.Name.LocalName)
1486 {
1487 case "Id":
1488 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
1489 break;
1490 default:
1491 this.Core.UnexpectedAttribute(node, attrib);
1492 break;
1493 }
1494 }
1495 else
1496 {
1497 this.Core.ParseExtensionAttribute(node, attrib);
1498 }
1499 }
1500
1501 if (null == id)
1502 {
1503 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
1504 id = Identifier.Invalid;
1505 }
1506
1507 var previousType = ComplexReferenceChildType.Unknown;
1508 Identifier previousId = null;
1509 foreach (var child in node.Elements())
1510 {
1511 if (CompilerCore.WixNamespace == child.Name.Namespace)
1512 {
1513 WixBundlePackageType? packageType = null;
1514 switch (child.Name.LocalName)
1515 {
1516 case "ExePackagePayload":
1517 packageType = WixBundlePackageType.Exe;
1518 break;
1519 case "MsiPackagePayload":
1520 packageType = WixBundlePackageType.Msi;
1521 break;
1522 case "MspPackagePayload":
1523 packageType = WixBundlePackageType.Msp;
1524 break;
1525 case "MsuPackagePayload":
1526 packageType = WixBundlePackageType.Msu;
1527 break;
1528 case "Payload":
1529 previousId = this.ParsePayloadElement(child, ComplexReferenceParentType.PayloadGroup, id, previousType, previousId);
1530 previousType = ComplexReferenceChildType.Payload;
1531 break;
1532 case "PayloadGroupRef":
1533 previousId = this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.PayloadGroup, id, previousType, previousId);
1534 previousType = ComplexReferenceChildType.PayloadGroup;
1535 break;
1536 default:
1537 this.Core.UnexpectedElement(node, child);
1538 break;
1539 }
1540
1541 if (packageType.HasValue)
1542 {
1543 var compilerPayload = this.ParsePackagePayloadElement(null, child, packageType.Value, null);
1544 var payloadSymbol = compilerPayload.CreatePayloadSymbol(ComplexReferenceParentType.PayloadGroup, id?.Id, previousType, previousId?.Id);
1545 if (payloadSymbol != null)
1546 {
1547 previousId = payloadSymbol.Id;
1548 previousType = ComplexReferenceChildType.Payload;
1549
1550 this.CreatePackagePayloadSymbol(payloadSymbol.SourceLineNumbers, packageType.Value, payloadSymbol.Id, ComplexReferenceParentType.PayloadGroup, id);
1551 }
1552 }
1553 }
1554 else
1555 {
1556 this.Core.ParseExtensionElement(node, child);
1557 }
1558 }
1559
1560
1561 if (!this.Core.EncounteredError)
1562 {
1563 this.Core.AddSymbol(new WixBundlePayloadGroupSymbol(sourceLineNumbers, id));
1564
1565 this.Core.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId?.Id, ComplexReferenceChildType.PayloadGroup, id.Id, ComplexReferenceChildType.Unknown, null);
1566 }
1567 }
1568
1569 /// <summary>
1570 /// Parses a payload group reference element.
1571 /// </summary>
1572 /// <param name="node">Element to parse.</param>
1573 /// <param name="parentType">ComplexReferenceParentType of parent element (BA or PayloadGroup).</param>
1574 /// <param name="parentId">Identifier of parent element.</param>
1575 /// <param name="previousType"></param>
1576 /// <param name="previousId"></param>
1577 private Identifier ParsePayloadGroupRefElement(XElement node, ComplexReferenceParentType parentType, Identifier parentId, ComplexReferenceChildType previousType, Identifier previousId)
1578 {
1579 Debug.Assert(ComplexReferenceParentType.Layout == parentType || ComplexReferenceParentType.PayloadGroup == parentType || ComplexReferenceParentType.Package == parentType || ComplexReferenceParentType.Container == parentType);
1580 Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PayloadGroup == previousType || ComplexReferenceChildType.Payload == previousType);
1581
1582 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1583 Identifier id = null;
1584
1585 foreach (var attrib in node.Attributes())
1586 {
1587 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
1588 {
1589 switch (attrib.Name.LocalName)
1590 {
1591 case "Id":
1592 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
1593 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundlePayloadGroup, id.Id);
1594 break;
1595 default:
1596 this.Core.UnexpectedAttribute(node, attrib);
1597 break;
1598 }
1599 }
1600 else
1601 {
1602 this.Core.ParseExtensionAttribute(node, attrib);
1603 }
1604 }
1605
1606 if (null == id)
1607 {
1608 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
1609 }
1610
1611 this.Core.ParseForExtensionElements(node);
1612
1613 this.Core.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId?.Id, ComplexReferenceChildType.PayloadGroup, id?.Id, previousType, previousId?.Id);
1614
1615 return id;
1616 }
1617
1618 /// <summary>
1619 /// Parse ExitCode element.
1620 /// </summary>
1621 /// <param name="node">Element to parse</param>
1622 /// <param name="packageId">Id of parent element</param>
1623 private void ParseExitCodeElement(XElement node, string packageId)
1624 {
1625 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1626 var value = CompilerConstants.IntegerNotSet;
1627 var behavior = ExitCodeBehaviorType.NotSet;
1628
1629 foreach (var attrib in node.Attributes())
1630 {
1631 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
1632 {
1633 switch (attrib.Name.LocalName)
1634 {
1635 case "Value":
1636 value = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int32.MinValue + 2, Int32.MaxValue);
1637 break;
1638 case "Behavior":
1639 var behaviorString = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1640 switch (behaviorString)
1641 {
1642 case "error":
1643 behavior = ExitCodeBehaviorType.Error;
1644 break;
1645 case "forceReboot":
1646 behavior = ExitCodeBehaviorType.ForceReboot;
1647 break;
1648 case "scheduleReboot":
1649 behavior = ExitCodeBehaviorType.ScheduleReboot;
1650 break;
1651 case "success":
1652 behavior = ExitCodeBehaviorType.Success;
1653 break;
1654 default:
1655 this.Core.Write(ErrorMessages.IllegalAttributeValueWithLegalList(sourceLineNumbers, node.Name.LocalName, "Behavior", behaviorString, "success, error, scheduleReboot, forceReboot"));
1656 behavior = ExitCodeBehaviorType.Success; // set value to avoid ExpectedAttribute below.
1657 break;
1658 }
1659 break;
1660 default:
1661 this.Core.UnexpectedAttribute(node, attrib);
1662 break;
1663 }
1664 }
1665 else
1666 {
1667 this.Core.ParseExtensionAttribute(node, attrib);
1668 }
1669 }
1670
1671 if (ExitCodeBehaviorType.NotSet == behavior)
1672 {
1673 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Behavior"));
1674 }
1675
1676 this.Core.ParseForExtensionElements(node);
1677
1678 if (!this.Core.EncounteredError)
1679 {
1680 this.Core.AddSymbol(new WixBundlePackageExitCodeSymbol(sourceLineNumbers)
1681 {
1682 ChainPackageId = packageId,
1683 Code = value,
1684 Behavior = behavior
1685 });
1686 }
1687 }
1688
1689 /// <summary>
1690 /// Parse Chain element.
1691 /// </summary>
1692 /// <param name="node">Element to parse</param>
1693 private void ParseChainElement(XElement node)
1694 {
1695 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1696 var attributes = WixChainAttributes.None;
1697
1698 foreach (var attrib in node.Attributes())
1699 {
1700 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
1701 {
1702 switch (attrib.Name.LocalName)
1703 {
1704 case "DisableRollback":
1705 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
1706 {
1707 attributes |= WixChainAttributes.DisableRollback;
1708 }
1709 break;
1710 case "DisableSystemRestore":
1711 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
1712 {
1713 attributes |= WixChainAttributes.DisableSystemRestore;
1714 }
1715 break;
1716 case "ParallelCache":
1717 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
1718 {
1719 attributes |= WixChainAttributes.ParallelCache;
1720 }
1721 break;
1722 default:
1723 this.Core.UnexpectedAttribute(node, attrib);
1724 break;
1725 }
1726 }
1727 else
1728 {
1729 this.Core.ParseExtensionAttribute(node, attrib);
1730 }
1731 }
1732
1733 string previousId = null;
1734 var previousType = ComplexReferenceChildType.Unknown;
1735
1736 foreach (var child in node.Elements())
1737 {
1738 if (CompilerCore.WixNamespace == child.Name.Namespace)
1739 {
1740 switch (child.Name.LocalName)
1741 {
1742 case "MsiPackage":
1743 previousId = this.ParseMsiPackageElement(child, ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, previousType, previousId);
1744 previousType = ComplexReferenceChildType.Package;
1745 break;
1746 case "MspPackage":
1747 previousId = this.ParseMspPackageElement(child, ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, previousType, previousId);
1748 previousType = ComplexReferenceChildType.Package;
1749 break;
1750 case "MsuPackage":
1751 previousId = this.ParseMsuPackageElement(child, ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, previousType, previousId);
1752 previousType = ComplexReferenceChildType.Package;
1753 break;
1754 case "ExePackage":
1755 previousId = this.ParseExePackageElement(child, ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, previousType, previousId);
1756 previousType = ComplexReferenceChildType.Package;
1757 break;
1758 case "RollbackBoundary":
1759 previousId = this.ParseRollbackBoundaryElement(child, ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, previousType, previousId);
1760 previousType = ComplexReferenceChildType.Package;
1761 break;
1762 case "PackageGroupRef":
1763 previousId = this.ParsePackageGroupRefElement(child, ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, previousType, previousId);
1764 previousType = ComplexReferenceChildType.PackageGroup;
1765 break;
1766 default:
1767 this.Core.UnexpectedElement(node, child);
1768 break;
1769 }
1770 }
1771 else
1772 {
1773 this.Core.ParseExtensionElement(node, child);
1774 }
1775 }
1776
1777
1778 if (null == previousId)
1779 {
1780 this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "MsiPackage", "ExePackage", "PackageGroupRef"));
1781 }
1782
1783 if (!this.Core.EncounteredError)
1784 {
1785 this.Core.AddSymbol(new WixChainSymbol(sourceLineNumbers)
1786 {
1787 Attributes = attributes
1788 });
1789 }
1790 }
1791
1792 /// <summary>
1793 /// Parse MsiPackage element
1794 /// </summary>
1795 /// <param name="node">Element to parse</param>
1796 /// <param name="parentType">Type of parent group, if known.</param>
1797 /// <param name="parentId">Identifier of parent group, if known.</param>
1798 /// <param name="previousType">Type of previous item, if known.</param>
1799 /// <param name="previousId">Identifier of previous item, if known</param>
1800 /// <returns>Identifier for package element.</returns>
1801 private string ParseMsiPackageElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId)
1802 {
1803 return this.ParseChainPackage(node, WixBundlePackageType.Msi, parentType, parentId, previousType, previousId);
1804 }
1805
1806 /// <summary>
1807 /// Parse MspPackage element
1808 /// </summary>
1809 /// <param name="node">Element to parse</param>
1810 /// <param name="parentType">Type of parent group, if known.</param>
1811 /// <param name="parentId">Identifier of parent group, if known.</param>
1812 /// <param name="previousType">Type of previous item, if known.</param>
1813 /// <param name="previousId">Identifier of previous item, if known</param>
1814 /// <returns>Identifier for package element.</returns>
1815 private string ParseMspPackageElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId)
1816 {
1817 return this.ParseChainPackage(node, WixBundlePackageType.Msp, parentType, parentId, previousType, previousId);
1818 }
1819
1820 /// <summary>
1821 /// Parse MsuPackage element
1822 /// </summary>
1823 /// <param name="node">Element to parse</param>
1824 /// <param name="parentType">Type of parent group, if known.</param>
1825 /// <param name="parentId">Identifier of parent group, if known.</param>
1826 /// <param name="previousType">Type of previous item, if known.</param>
1827 /// <param name="previousId">Identifier of previous item, if known</param>
1828 /// <returns>Identifier for package element.</returns>
1829 private string ParseMsuPackageElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId)
1830 {
1831 return this.ParseChainPackage(node, WixBundlePackageType.Msu, parentType, parentId, previousType, previousId);
1832 }
1833
1834 /// <summary>
1835 /// Parse ExePackage element
1836 /// </summary>
1837 /// <param name="node">Element to parse</param>
1838 /// <param name="parentType">Type of parent group, if known.</param>
1839 /// <param name="parentId">Identifier of parent group, if known.</param>
1840 /// <param name="previousType">Type of previous item, if known.</param>
1841 /// <param name="previousId">Identifier of previous item, if known</param>
1842 /// <returns>Identifier for package element.</returns>
1843 private string ParseExePackageElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId)
1844 {
1845 return this.ParseChainPackage(node, WixBundlePackageType.Exe, parentType, parentId, previousType, previousId);
1846 }
1847
1848 /// <summary>
1849 /// Parse RollbackBoundary element
1850 /// </summary>
1851 /// <param name="node">Element to parse</param>
1852 /// <param name="parentType">Type of parent group, if known.</param>
1853 /// <param name="parentId">Identifier of parent group, if known.</param>
1854 /// <param name="previousType">Type of previous item, if known.</param>
1855 /// <param name="previousId">Identifier of previous item, if known</param>
1856 /// <returns>Identifier for package element.</returns>
1857 private string ParseRollbackBoundaryElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId)
1858 {
1859 Debug.Assert(ComplexReferenceParentType.PackageGroup == parentType);
1860 Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PackageGroup == previousType || ComplexReferenceChildType.Package == previousType);
1861
1862 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1863 Identifier id = null;
1864 var vital = YesNoType.Yes;
1865 var transaction = YesNoType.No;
1866
1867 // This list lets us evaluate extension attributes *after* all core attributes
1868 // have been parsed and dealt with, regardless of authoring order.
1869 var extensionAttributes = new List<XAttribute>();
1870
1871 foreach (var attrib in node.Attributes())
1872 {
1873 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
1874 {
1875 var allowed = true;
1876 switch (attrib.Name.LocalName)
1877 {
1878 case "Id":
1879 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
1880 if (id?.Id == BurnConstants.BundleDefaultBoundaryId)
1881 {
1882 this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Id", id.Id));
1883 }
1884 break;
1885 case "Vital":
1886 vital = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1887 break;
1888 case "Transaction":
1889 transaction = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1890 break;
1891 default:
1892 allowed = false;
1893 break;
1894 }
1895
1896 if (!allowed)
1897 {
1898 this.Core.UnexpectedAttribute(node, attrib);
1899 }
1900 }
1901 else
1902 {
1903 // Save the extension attributes for later...
1904 extensionAttributes.Add(attrib);
1905 }
1906 }
1907
1908 if (null == id)
1909 {
1910 if (!String.IsNullOrEmpty(previousId))
1911 {
1912 id = this.Core.CreateIdentifier("rba", previousId);
1913 }
1914
1915 if (null == id)
1916 {
1917 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
1918 id = Identifier.Invalid;
1919 }
1920 else if (!Common.IsIdentifier(id.Id))
1921 {
1922 this.Core.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, node.Name.LocalName, "Id", id.Id));
1923 }
1924 }
1925
1926 // Now that the rollback identifier is known, we can parse the extension attributes...
1927 var contextValues = new Dictionary<string, string>
1928 {
1929 ["RollbackBoundaryId"] = id.Id
1930 };
1931 foreach (var attribute in extensionAttributes)
1932 {
1933 this.Core.ParseExtensionAttribute(node, attribute, contextValues);
1934 }
1935
1936 this.Core.ParseForExtensionElements(node);
1937
1938 if (!this.Core.EncounteredError)
1939 {
1940 this.CreateRollbackBoundary(sourceLineNumbers, id, vital, transaction, parentType, parentId, previousType, previousId);
1941 }
1942
1943 return id.Id;
1944 }
1945
1946 /// <summary>
1947 /// Parses one of the ChainPackage elements
1948 /// </summary>
1949 /// <param name="node">Element to parse</param>
1950 /// <param name="packageType">Type of package to parse</param>
1951 /// <param name="parentType">Type of parent group, if known.</param>
1952 /// <param name="parentId">Identifier of parent group, if known.</param>
1953 /// <param name="previousType">Type of previous item, if known.</param>
1954 /// <param name="previousId">Identifier of previous item, if known</param>
1955 /// <returns>Identifier for package element.</returns>
1956 /// <remarks>This method contains the shared logic for parsing all of the ChainPackage
1957 /// types, as there is more in common between them than different.</remarks>
1958 private string ParseChainPackage(XElement node, WixBundlePackageType packageType, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId)
1959 {
1960 Debug.Assert(ComplexReferenceParentType.PackageGroup == parentType);
1961 Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PackageGroup == previousType || ComplexReferenceChildType.Package == previousType);
1962
1963 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1964 var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node)
1965 {
1966 IsRequired = false,
1967 };
1968 string after = null;
1969 string installCondition = null;
1970 var cache = YesNoAlwaysType.Yes; // the default is to cache everything in tradeoff for stability over disk space.
1971 string cacheId = null;
1972 string description = null;
1973 string displayName = null;
1974 var logPathVariable = (packageType == WixBundlePackageType.Msu) ? String.Empty : null;
1975 var rollbackPathVariable = (packageType == WixBundlePackageType.Msu) ? String.Empty : null;
1976 var permanent = YesNoType.NotSet;
1977 var visible = YesNoType.NotSet;
1978 var vital = YesNoType.Yes;
1979 string installArguments = null;
1980 string repairArguments = null;
1981 string uninstallArguments = null;
1982 var perMachine = YesNoDefaultType.NotSet;
1983 string detectCondition = null;
1984 string protocol = null;
1985 var installSize = CompilerConstants.IntegerNotSet;
1986 string msuKB = null;
1987 var enableFeatureSelection = YesNoType.NotSet;
1988 var forcePerMachine = YesNoType.NotSet;
1989 CompilerPayload childPackageCompilerPayload = null;
1990 var slipstream = YesNoType.NotSet;
1991 var hasPayloadInfo = false;
1992
1993 var expectedNetFx4Args = new string[] { "/q", "/norestart" };
1994
1995 // This list lets us evaluate extension attributes *after* all core attributes
1996 // have been parsed and dealt with, regardless of authoring order.
1997 var extensionAttributes = new List<XAttribute>();
1998
1999 foreach (var attrib in node.Attributes())
2000 {
2001 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
2002 {
2003 var allowed = true;
2004 switch (attrib.Name.LocalName)
2005 {
2006 case "Id":
2007 compilerPayload.ParseId(attrib);
2008 break;
2009 case "Name":
2010 compilerPayload.ParseName(attrib);
2011 hasPayloadInfo = true;
2012 break;
2013 case "SourceFile":
2014 compilerPayload.ParseSourceFile(attrib);
2015 hasPayloadInfo = true;
2016 break;
2017 case "DownloadUrl":
2018 compilerPayload.ParseDownloadUrl(attrib);
2019 hasPayloadInfo = true;
2020 break;
2021 case "After":
2022 after = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
2023 break;
2024 case "InstallCondition":
2025 installCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
2026 break;
2027 case "Cache":
2028 var value = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
2029 switch (value)
2030 {
2031 case "always":
2032 cache = YesNoAlwaysType.Always;
2033 break;
2034 case "yes":
2035 cache = YesNoAlwaysType.Yes;
2036 break;
2037 case "no":
2038 cache = YesNoAlwaysType.No;
2039 break;
2040 case "":
2041 break;
2042 default:
2043 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, value, "always", "yes", "no"));
2044 break;
2045 }
2046 break;
2047 case "CacheId":
2048 cacheId = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
2049 break;
2050 case "Description":
2051 description = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
2052 break;
2053 case "DisplayName":
2054 displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
2055 break;
2056 case "EnableFeatureSelection":
2057 enableFeatureSelection = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
2058 allowed = (packageType == WixBundlePackageType.Msi);
2059 break;
2060 case "ForcePerMachine":
2061 forcePerMachine = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
2062 allowed = (packageType == WixBundlePackageType.Msi);
2063 break;
2064 case "LogPathVariable":
2065 logPathVariable = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty);
2066 break;
2067 case "RollbackLogPathVariable":
2068 rollbackPathVariable = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty);
2069 break;
2070 case "Permanent":
2071 permanent = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
2072 break;
2073 case "Visible":
2074 visible = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
2075 allowed = (packageType == WixBundlePackageType.Msi);
2076 break;
2077 case "Vital":
2078 vital = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
2079 break;
2080 case "InstallArguments":
2081 installArguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
2082 allowed = (packageType == WixBundlePackageType.Exe);
2083 break;
2084 case "RepairArguments":
2085 repairArguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty);
2086 allowed = (packageType == WixBundlePackageType.Exe);
2087 break;
2088 case "UninstallArguments":
2089 uninstallArguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
2090 allowed = (packageType == WixBundlePackageType.Exe);
2091 break;
2092 case "PerMachine":
2093 perMachine = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib);
2094 allowed = (packageType == WixBundlePackageType.Exe || packageType == WixBundlePackageType.Msp);
2095 break;
2096 case "DetectCondition":
2097 detectCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty);
2098 allowed = (packageType == WixBundlePackageType.Exe || packageType == WixBundlePackageType.Msu);
2099 break;
2100 case "Protocol":
2101 protocol = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
2102 allowed = (packageType == WixBundlePackageType.Exe);
2103 break;
2104 case "InstallSize":
2105 installSize = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue);
2106 break;
2107 case "KB":
2108 msuKB = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
2109 allowed = (packageType == WixBundlePackageType.Msu);
2110 break;
2111 case "Compressed":
2112 compilerPayload.ParseCompressed(attrib);
2113 hasPayloadInfo = true;
2114 break;
2115 case "Slipstream":
2116 slipstream = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
2117 allowed = (packageType == WixBundlePackageType.Msp);
2118 break;
2119 default:
2120 allowed = false;
2121 break;
2122 }
2123
2124 if (!allowed)
2125 {
2126 this.Core.UnexpectedAttribute(node, attrib);
2127 }
2128 }
2129 else
2130 {
2131 // Save the extension attributes for later...
2132 extensionAttributes.Add(attrib);
2133 }
2134 }
2135
2136 // We need to handle the package payload up front because it affects Id generation. Id is needed by other child elements.
2137 var packagePayloadElementName = packageType + "PackagePayload";
2138 foreach (var child in node.Elements(CompilerCore.WixNamespace + packagePayloadElementName))
2139 {
2140 var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
2141
2142 if (childPackageCompilerPayload != null)
2143 {
2144 this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, child.Name.LocalName));
2145 }
2146 else if (hasPayloadInfo)
2147 {
2148 this.Core.Write(ErrorMessages.UnexpectedElementWithAttribute(sourceLineNumbers, node.Name.LocalName, child.Name.LocalName, "SourceFile", "Name", "DownloadUrl", "Compressed"));
2149 }
2150
2151 childPackageCompilerPayload = this.ParsePackagePayloadElement(childSourceLineNumbers, child, packageType, compilerPayload.Id);
2152 }
2153
2154 if (compilerPayload.Id == null && childPackageCompilerPayload != null)
2155 {
2156 compilerPayload.Id = childPackageCompilerPayload.Id;
2157 }
2158
2159 compilerPayload.FinishCompilingPackage();
2160 var id = compilerPayload.Id;
2161
2162 if (id.Id == BurnConstants.BundleDefaultBoundaryId)
2163 {
2164 this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Id", id.Id));
2165 }
2166
2167 if (null == logPathVariable)
2168 {
2169 logPathVariable = String.Concat("WixBundleLog_", id.Id);
2170 }
2171
2172 if (null == rollbackPathVariable)
2173 {
2174 rollbackPathVariable = String.Concat("WixBundleRollbackLog_", id.Id);
2175 }
2176
2177 if (!String.IsNullOrEmpty(protocol) && !protocol.Equals("burn", StringComparison.Ordinal) && !protocol.Equals("netfx4", StringComparison.Ordinal) && !protocol.Equals("none", StringComparison.Ordinal))
2178 {
2179 this.Core.Write(ErrorMessages.IllegalAttributeValueWithLegalList(sourceLineNumbers, node.Name.LocalName, "Protocol", protocol, "none, burn, netfx4"));
2180 }
2181
2182 if (!String.IsNullOrEmpty(protocol) && protocol.Equals("netfx4", StringComparison.Ordinal))
2183 {
2184 foreach (var expectedArgument in expectedNetFx4Args)
2185 {
2186 if (null == installArguments || -1 == installArguments.IndexOf(expectedArgument, StringComparison.OrdinalIgnoreCase))
2187 {
2188 this.Core.Write(WarningMessages.AttributeShouldContain(sourceLineNumbers, node.Name.LocalName, "InstallArguments", installArguments, expectedArgument, "Protocol", "netfx4"));
2189 }
2190
2191 if (!String.IsNullOrEmpty(repairArguments) && -1 == repairArguments.IndexOf(expectedArgument, StringComparison.OrdinalIgnoreCase))
2192 {
2193 this.Core.Write(WarningMessages.AttributeShouldContain(sourceLineNumbers, node.Name.LocalName, "RepairArguments", repairArguments, expectedArgument, "Protocol", "netfx4"));
2194 }
2195
2196 if (!String.IsNullOrEmpty(uninstallArguments) && -1 == uninstallArguments.IndexOf(expectedArgument, StringComparison.OrdinalIgnoreCase))
2197 {
2198 this.Core.Write(WarningMessages.AttributeShouldContain(sourceLineNumbers, node.Name.LocalName, "UninstallArguments", uninstallArguments, expectedArgument, "Protocol", "netfx4"));
2199 }
2200 }
2201 }
2202
2203 // Only set default scope for EXEs and MSPs if not already set.
2204 if ((WixBundlePackageType.Exe == packageType || WixBundlePackageType.Msp == packageType) && YesNoDefaultType.NotSet == perMachine)
2205 {
2206 perMachine = YesNoDefaultType.Default;
2207 }
2208
2209 // Detect condition is recommended or required for Exe and Msu packages
2210 // (depending on whether uninstall arguments were provided).
2211 if ((packageType == WixBundlePackageType.Exe || packageType == WixBundlePackageType.Msu) && String.IsNullOrEmpty(detectCondition))
2212 {
2213 if (String.IsNullOrEmpty(uninstallArguments))
2214 {
2215 this.Core.Write(WarningMessages.DetectConditionRecommended(sourceLineNumbers, node.Name.LocalName));
2216 }
2217 else
2218 {
2219 this.Core.Write(ErrorMessages.ExpectedAttributeWithValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DetectCondition", "UninstallArguments"));
2220 }
2221 }
2222
2223 // Now that the package ID is known, we can parse the extension attributes...
2224 var contextValues = new Dictionary<string, string>() { { "PackageId", id.Id } };
2225 foreach (var attribute in extensionAttributes)
2226 {
2227 this.Core.ParseExtensionAttribute(node, attribute, contextValues);
2228 }
2229
2230 foreach (var child in node.Elements())
2231 {
2232 if (CompilerCore.WixNamespace == child.Name.Namespace)
2233 {
2234 var allowed = true;
2235 switch (child.Name.LocalName)
2236 {
2237 case "SlipstreamMsp":
2238 allowed = (packageType == WixBundlePackageType.Msi);
2239 if (allowed)
2240 {
2241 this.ParseSlipstreamMspElement(child, id.Id);
2242 }
2243 break;
2244 case "MsiProperty":
2245 allowed = (packageType == WixBundlePackageType.Msi || packageType == WixBundlePackageType.Msp);
2246 if (allowed)
2247 {
2248 this.ParseMsiPropertyElement(child, id.Id);
2249 }
2250 break;
2251 case "Payload":
2252 this.ParsePayloadElement(child, ComplexReferenceParentType.Package, id, ComplexReferenceChildType.Unknown, null);
2253 break;
2254 case "PayloadGroupRef":
2255 this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Package, id, ComplexReferenceChildType.Unknown, null);
2256 break;
2257 case "Provides":
2258 this.ParseProvidesElement(child, packageType, id.Id, out _);
2259 break;
2260 case "ExitCode":
2261 allowed = (packageType == WixBundlePackageType.Exe);
2262 if (allowed)
2263 {
2264 this.ParseExitCodeElement(child, id.Id);
2265 }
2266 break;
2267 case "CommandLine":
2268 allowed = (packageType == WixBundlePackageType.Exe);
2269 if (allowed)
2270 {
2271 this.ParseCommandLineElement(child, id.Id);
2272 }
2273 break;
2274 case "ExePackagePayload":
2275 case "MsiPackagePayload":
2276 case "MspPackagePayload":
2277 case "MsuPackagePayload":
2278 allowed = packagePayloadElementName == child.Name.LocalName;
2279 // Handled previously
2280 break;
2281 default:
2282 allowed = false;
2283 break;
2284 }
2285
2286 if (!allowed)
2287 {
2288 this.Core.UnexpectedElement(node, child);
2289 }
2290 }
2291 else
2292 {
2293 var context = new Dictionary<string, string>() { { "Id", id.Id } };
2294 this.Core.ParseExtensionElement(node, child, context);
2295 }
2296 }
2297
2298 if (!this.Core.EncounteredError)
2299 {
2300 var packageCompilerPayload = childPackageCompilerPayload ?? (hasPayloadInfo ? compilerPayload : null);
2301 if (packageCompilerPayload != null)
2302 {
2303 var payload = packageCompilerPayload.CreatePayloadSymbol(ComplexReferenceParentType.Package, id.Id);
2304
2305 this.CreatePackagePayloadSymbol(sourceLineNumbers, packageType, payload.Id, ComplexReferenceParentType.Package, id);
2306 }
2307
2308 this.Core.AddSymbol(new WixChainItemSymbol(sourceLineNumbers, id));
2309
2310 WixBundlePackageAttributes attributes = 0;
2311 attributes |= (YesNoType.Yes == permanent) ? WixBundlePackageAttributes.Permanent : 0;
2312 attributes |= (YesNoType.Yes == visible) ? WixBundlePackageAttributes.Visible : 0;
2313
2314 var chainPackageSymbol = this.Core.AddSymbol(new WixBundlePackageSymbol(sourceLineNumbers, id)
2315 {
2316 Type = packageType,
2317 Attributes = attributes,
2318 InstallCondition = installCondition,
2319 CacheId = cacheId,
2320 Description = description,
2321 DisplayName = displayName,
2322 LogPathVariable = logPathVariable,
2323 RollbackLogPathVariable = rollbackPathVariable,
2324 });
2325
2326 if (YesNoAlwaysType.NotSet != cache)
2327 {
2328 chainPackageSymbol.Cache = cache;
2329 }
2330
2331 if (YesNoType.NotSet != vital)
2332 {
2333 chainPackageSymbol.Vital = (vital == YesNoType.Yes);
2334 }
2335
2336 if (YesNoDefaultType.NotSet != perMachine)
2337 {
2338 chainPackageSymbol.PerMachine = perMachine;
2339 }
2340
2341 if (CompilerConstants.IntegerNotSet != installSize)
2342 {
2343 chainPackageSymbol.InstallSize = installSize;
2344 }
2345
2346 switch (packageType)
2347 {
2348 case WixBundlePackageType.Exe:
2349 this.Core.AddSymbol(new WixBundleExePackageSymbol(sourceLineNumbers, id)
2350 {
2351 Attributes = WixBundleExePackageAttributes.None,
2352 DetectCondition = detectCondition,
2353 InstallCommand = installArguments,
2354 RepairCommand = repairArguments,
2355 UninstallCommand = uninstallArguments,
2356 ExeProtocol = protocol
2357 });
2358 break;
2359
2360 case WixBundlePackageType.Msi:
2361 WixBundleMsiPackageAttributes msiAttributes = 0;
2362 msiAttributes |= (YesNoType.Yes == enableFeatureSelection) ? WixBundleMsiPackageAttributes.EnableFeatureSelection : 0;
2363 msiAttributes |= (YesNoType.Yes == forcePerMachine) ? WixBundleMsiPackageAttributes.ForcePerMachine : 0;
2364
2365 this.Core.AddSymbol(new WixBundleMsiPackageSymbol(sourceLineNumbers, id)
2366 {
2367 Attributes = msiAttributes
2368 });
2369 break;
2370
2371 case WixBundlePackageType.Msp:
2372 WixBundleMspPackageAttributes mspAttributes = 0;
2373 mspAttributes |= (YesNoType.Yes == slipstream) ? WixBundleMspPackageAttributes.Slipstream : 0;
2374
2375 this.Core.AddSymbol(new WixBundleMspPackageSymbol(sourceLineNumbers, id)
2376 {
2377 Attributes = mspAttributes
2378 });
2379 break;
2380
2381 case WixBundlePackageType.Msu:
2382 this.Core.AddSymbol(new WixBundleMsuPackageSymbol(sourceLineNumbers, id)
2383 {
2384 DetectCondition = detectCondition,
2385 MsuKB = msuKB
2386 });
2387 break;
2388 }
2389
2390 this.CreateChainPackageMetaRows(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.Package, id.Id, previousType, previousId, after);
2391 this.Core.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.ContainerPackage, id.Id, ComplexReferenceChildType.Unknown, null);
2392 }
2393
2394 return id.Id;
2395 }
2396
2397 private void CreatePackagePayloadSymbol(SourceLineNumber sourceLineNumbers, WixBundlePackageType packageType, Identifier payloadId, ComplexReferenceParentType parentType, Identifier parentId)
2398 {
2399 switch (packageType)
2400 {
2401 case WixBundlePackageType.Exe:
2402 this.Core.AddSymbol(new WixBundleExePackagePayloadSymbol(sourceLineNumbers, payloadId));
2403 break;
2404
2405 case WixBundlePackageType.Msi:
2406 this.Core.AddSymbol(new WixBundleMsiPackagePayloadSymbol(sourceLineNumbers, payloadId));
2407 break;
2408
2409 case WixBundlePackageType.Msp:
2410 this.Core.AddSymbol(new WixBundleMspPackagePayloadSymbol(sourceLineNumbers, payloadId));
2411 break;
2412
2413 case WixBundlePackageType.Msu:
2414 this.Core.AddSymbol(new WixBundleMsuPackagePayloadSymbol(sourceLineNumbers, payloadId));
2415 break;
2416 }
2417
2418 this.Core.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId?.Id, ComplexReferenceChildType.PackagePayload, payloadId?.Id, ComplexReferenceChildType.Unknown, null);
2419 }
2420
2421 private CompilerPayload ParsePackagePayloadElement(SourceLineNumber sourceLineNumbers, XElement node, WixBundlePackageType packageType, Identifier defaultId)
2422 {
2423 sourceLineNumbers = sourceLineNumbers ?? Preprocessor.GetSourceLineNumbers(node);
2424 var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node)
2425 {
2426 Id = defaultId,
2427 IsRemoteAllowed = packageType == WixBundlePackageType.Exe || packageType == WixBundlePackageType.Msu,
2428 };
2429
2430 // This list lets us evaluate extension attributes *after* all core attributes
2431 // have been parsed and dealt with, regardless of authoring order.
2432 var extensionAttributes = new List<XAttribute>();
2433
2434 foreach (var attrib in node.Attributes())
2435 {
2436 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
2437 {
2438 var allowed = true;
2439 switch (attrib.Name.LocalName)
2440 {
2441 case "Id":
2442 compilerPayload.ParseId(attrib);
2443 break;
2444 case "Compressed":
2445 compilerPayload.ParseCompressed(attrib);
2446 break;
2447 case "Name":
2448 compilerPayload.ParseName(attrib);
2449 break;
2450 case "SourceFile":
2451 compilerPayload.ParseSourceFile(attrib);
2452 break;
2453 case "DownloadUrl":
2454 compilerPayload.ParseDownloadUrl(attrib);
2455 break;
2456 case "Description":
2457 allowed = compilerPayload.IsRemoteAllowed;
2458 if (allowed)
2459 {
2460 compilerPayload.ParseDescription(attrib);
2461 }
2462 break;
2463 case "Hash":
2464 allowed = compilerPayload.IsRemoteAllowed;
2465 if (allowed)
2466 {
2467 compilerPayload.ParseHash(attrib);
2468 }
2469 break;
2470 case "ProductName":
2471 allowed = compilerPayload.IsRemoteAllowed;
2472 if (allowed)
2473 {
2474 compilerPayload.ParseProductName(attrib);
2475 }
2476 break;
2477 case "Size":
2478 allowed = compilerPayload.IsRemoteAllowed;
2479 if (allowed)
2480 {
2481 compilerPayload.ParseSize(attrib);
2482 }
2483 break;
2484 case "Version":
2485 allowed = compilerPayload.IsRemoteAllowed;
2486 if (allowed)
2487 {
2488 compilerPayload.ParseVersion(attrib);
2489 }
2490 break;
2491 default:
2492 allowed = false;
2493 break;
2494 }
2495
2496 if (!allowed)
2497 {
2498 this.Core.UnexpectedAttribute(node, attrib);
2499 }
2500 }
2501 else
2502 {
2503 this.Core.ParseExtensionAttribute(node, attrib);
2504 }
2505 }
2506
2507 compilerPayload.FinishCompilingPackagePayload();
2508
2509 // Now that the PayloadId is known, we can parse the extension attributes.
2510 var context = new Dictionary<string, string>
2511 {
2512 ["Id"] = compilerPayload.Id.Id,
2513 };
2514
2515 foreach (var extensionAttribute in extensionAttributes)
2516 {
2517 this.Core.ParseExtensionAttribute(node, extensionAttribute, context);
2518 }
2519
2520 this.Core.ParseForExtensionElements(node);
2521
2522 return compilerPayload;
2523 }
2524
2525 /// <summary>
2526 /// Parse CommandLine element.
2527 /// </summary>
2528 /// <param name="node">Element to parse</param>
2529 /// <param name="packageId">Parent packageId</param>
2530 private void ParseCommandLineElement(XElement node, string packageId)
2531 {
2532 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2533 string installArgument = null;
2534 string uninstallArgument = null;
2535 string repairArgument = null;
2536 string condition = null;
2537
2538 foreach (var attrib in node.Attributes())
2539 {
2540 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
2541 {
2542 switch (attrib.Name.LocalName)
2543 {
2544 case "InstallArgument":
2545 installArgument = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
2546 break;
2547 case "UninstallArgument":
2548 uninstallArgument = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
2549 break;
2550 case "RepairArgument":
2551 repairArgument = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
2552 break;
2553 case "Condition":
2554 condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
2555 break;
2556 default:
2557 this.Core.UnexpectedAttribute(node, attrib);
2558 break;
2559 }
2560 }
2561 else
2562 {
2563 this.Core.ParseExtensionAttribute(node, attrib);
2564 }
2565 }
2566
2567 if (String.IsNullOrEmpty(condition))
2568 {
2569 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Condition"));
2570 }
2571
2572 this.Core.ParseForExtensionElements(node);
2573
2574 if (!this.Core.EncounteredError)
2575 {
2576 this.Core.AddSymbol(new WixBundlePackageCommandLineSymbol(sourceLineNumbers)
2577 {
2578 WixBundlePackageRef = packageId,
2579 InstallArgument = installArgument,
2580 UninstallArgument = uninstallArgument,
2581 RepairArgument = repairArgument,
2582 Condition = condition
2583 });
2584 }
2585 }
2586
2587 /// <summary>
2588 /// Parse PackageGroup element.
2589 /// </summary>
2590 /// <param name="node">Element to parse</param>
2591 private void ParsePackageGroupElement(XElement node)
2592 {
2593 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2594 Identifier id = null;
2595
2596 foreach (var attrib in node.Attributes())
2597 {
2598 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
2599 {
2600 switch (attrib.Name.LocalName)
2601 {
2602 case "Id":
2603 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
2604 if (id?.Id == BurnConstants.BundleChainPackageGroupId)
2605 {
2606 this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Id", id.Id));
2607 }
2608 break;
2609 default:
2610 this.Core.UnexpectedAttribute(node, attrib);
2611 break;
2612 }
2613 }
2614 else
2615 {
2616 this.Core.ParseExtensionAttribute(node, attrib);
2617 }
2618 }
2619
2620 if (null == id)
2621 {
2622 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
2623 id = Identifier.Invalid;
2624 }
2625
2626 var previousType = ComplexReferenceChildType.Unknown;
2627 string previousId = null;
2628 foreach (var child in node.Elements())
2629 {
2630 if (CompilerCore.WixNamespace == child.Name.Namespace)
2631 {
2632 switch (child.Name.LocalName)
2633 {
2634 case "MsiPackage":
2635 previousId = this.ParseMsiPackageElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId);
2636 previousType = ComplexReferenceChildType.Package;
2637 break;
2638 case "MspPackage":
2639 previousId = this.ParseMspPackageElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId);
2640 previousType = ComplexReferenceChildType.Package;
2641 break;
2642 case "MsuPackage":
2643 previousId = this.ParseMsuPackageElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId);
2644 previousType = ComplexReferenceChildType.Package;
2645 break;
2646 case "ExePackage":
2647 previousId = this.ParseExePackageElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId);
2648 previousType = ComplexReferenceChildType.Package;
2649 break;
2650 case "RollbackBoundary":
2651 previousId = this.ParseRollbackBoundaryElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId);
2652 previousType = ComplexReferenceChildType.Package;
2653 break;
2654 case "PackageGroupRef":
2655 previousId = this.ParsePackageGroupRefElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId);
2656 previousType = ComplexReferenceChildType.PackageGroup;
2657 break;
2658 default:
2659 this.Core.UnexpectedElement(node, child);
2660 break;
2661 }
2662 }
2663 else
2664 {
2665 this.Core.ParseExtensionElement(node, child);
2666 }
2667 }
2668
2669
2670 if (!this.Core.EncounteredError)
2671 {
2672 this.Core.AddSymbol(new WixBundlePackageGroupSymbol(sourceLineNumbers, id));
2673 }
2674 }
2675
2676 /// <summary>
2677 /// Parses a package group reference element.
2678 /// </summary>
2679 /// <param name="node">Element to parse.</param>
2680 /// <param name="parentType">ComplexReferenceParentType of parent element (Unknown or PackageGroup).</param>
2681 /// <param name="parentId">Identifier of parent element.</param>
2682 /// <returns>Identifier for package group element.</returns>
2683 private string ParsePackageGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId)
2684 {
2685 return this.ParsePackageGroupRefElement(node, parentType, parentId, ComplexReferenceChildType.Unknown, null);
2686 }
2687
2688 /// <summary>
2689 /// Parses a package group reference element.
2690 /// </summary>
2691 /// <param name="node">Element to parse.</param>
2692 /// <param name="parentType">ComplexReferenceParentType of parent element (Unknown or PackageGroup).</param>
2693 /// <param name="parentId">Identifier of parent element.</param>
2694 /// <param name="previousType"></param>
2695 /// <param name="previousId"></param>
2696 /// <returns>Identifier for package group element.</returns>
2697 private string ParsePackageGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId)
2698 {
2699 Debug.Assert(ComplexReferenceParentType.Unknown == parentType || ComplexReferenceParentType.PackageGroup == parentType || ComplexReferenceParentType.Container == parentType);
2700 Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PackageGroup == previousType || ComplexReferenceChildType.Package == previousType);
2701
2702 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2703 string id = null;
2704 string after = null;
2705
2706 foreach (var attrib in node.Attributes())
2707 {
2708 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
2709 {
2710 switch (attrib.Name.LocalName)
2711 {
2712 case "Id":
2713 id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
2714 if (id == BurnConstants.BundleChainPackageGroupId)
2715 {
2716 this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Id", id));
2717 }
2718 else
2719 {
2720 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundlePackageGroup, id);
2721 }
2722 break;
2723 case "After":
2724 after = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
2725 break;
2726 default:
2727 this.Core.UnexpectedAttribute(node, attrib);
2728 break;
2729 }
2730 }
2731 else
2732 {
2733 this.Core.ParseExtensionAttribute(node, attrib);
2734
2735 }
2736 }
2737
2738 if (null == id)
2739 {
2740 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
2741 }
2742
2743 if (null != after && ComplexReferenceParentType.Container == parentType)
2744 {
2745 this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "After", parentId));
2746 }
2747
2748 this.Core.ParseForExtensionElements(node);
2749
2750 if (ComplexReferenceParentType.Container == parentType)
2751 {
2752 this.Core.CreateWixGroupRow(sourceLineNumbers, ComplexReferenceParentType.Container, parentId, ComplexReferenceChildType.PackageGroup, id);
2753 }
2754 else
2755 {
2756 this.CreateChainPackageMetaRows(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.PackageGroup, id, previousType, previousId, after);
2757 }
2758
2759 return id;
2760 }
2761
2762 /// <summary>
2763 /// Creates rollback boundary.
2764 /// </summary>
2765 /// <param name="sourceLineNumbers">Source line numbers.</param>
2766 /// <param name="id">Identifier for the rollback boundary.</param>
2767 /// <param name="vital">Indicates whether the rollback boundary is vital or not.</param>
2768 /// <param name="transaction">Indicates whether the rollback boundary will use an MSI transaction.</param>
2769 /// <param name="parentType">Type of parent group.</param>
2770 /// <param name="parentId">Identifier of parent group.</param>
2771 /// <param name="previousType">Type of previous item, if any.</param>
2772 /// <param name="previousId">Identifier of previous item, if any.</param>
2773 private void CreateRollbackBoundary(SourceLineNumber sourceLineNumbers, Identifier id, YesNoType vital, YesNoType transaction, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId)
2774 {
2775 this.Core.AddSymbol(new WixChainItemSymbol(sourceLineNumbers, id));
2776
2777 var rollbackBoundary = this.Core.AddSymbol(new WixBundleRollbackBoundarySymbol(sourceLineNumbers, id));
2778
2779 if (YesNoType.NotSet != vital)
2780 {
2781 rollbackBoundary.Vital = (vital == YesNoType.Yes);
2782 }
2783
2784 if (YesNoType.NotSet != transaction)
2785 {
2786 rollbackBoundary.Transaction = (transaction == YesNoType.Yes);
2787 }
2788
2789 this.CreateChainPackageMetaRows(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.Package, id.Id, previousType, previousId, null);
2790 }
2791
2792 /// <summary>
2793 /// Creates group and ordering information for packages
2794 /// </summary>
2795 /// <param name="sourceLineNumbers">Source line numbers.</param>
2796 /// <param name="parentType">Type of parent group, if known.</param>
2797 /// <param name="parentId">Identifier of parent group, if known.</param>
2798 /// <param name="type">Type of this item.</param>
2799 /// <param name="id">Identifier for this item.</param>
2800 /// <param name="previousType">Type of previous item, if known.</param>
2801 /// <param name="previousId">Identifier of previous item, if known</param>
2802 /// <param name="afterId">Identifier of explicit 'After' attribute, if given.</param>
2803 private void CreateChainPackageMetaRows(SourceLineNumber sourceLineNumbers,
2804 ComplexReferenceParentType parentType, string parentId,
2805 ComplexReferenceChildType type, string id,
2806 ComplexReferenceChildType previousType, string previousId, string afterId)
2807 {
2808 // If there's an explicit 'After' attribute, it overrides the inferred previous item.
2809 if (null != afterId)
2810 {
2811 previousType = ComplexReferenceChildType.Package;
2812 previousId = afterId;
2813 }
2814
2815 this.Core.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId, type, id, previousType, previousId);
2816 }
2817
2818 /// <summary>
2819 /// Parse MsiProperty element
2820 /// </summary>
2821 /// <param name="node">Element to parse</param>
2822 /// <param name="packageId">Id of parent element</param>
2823 private void ParseMsiPropertyElement(XElement node, string packageId)
2824 {
2825 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2826 string name = null;
2827 string value = null;
2828 string condition = null;
2829
2830 foreach (var attrib in node.Attributes())
2831 {
2832 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
2833 {
2834 switch (attrib.Name.LocalName)
2835 {
2836 case "Name":
2837 name = this.Core.GetAttributeMsiPropertyNameValue(sourceLineNumbers, attrib);
2838 break;
2839 case "Value":
2840 value = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
2841 break;
2842 case "Condition":
2843 condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
2844 break;
2845 default:
2846 this.Core.UnexpectedAttribute(node, attrib);
2847 break;
2848 }
2849 }
2850 else
2851 {
2852 this.Core.ParseExtensionAttribute(node, attrib);
2853 }
2854 }
2855
2856 if (null == name)
2857 {
2858 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
2859 }
2860
2861 if (null == value)
2862 {
2863 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value"));
2864 }
2865
2866 this.Core.ParseForExtensionElements(node);
2867
2868 if (!this.Core.EncounteredError)
2869 {
2870 var symbol = this.Core.AddSymbol(new WixBundleMsiPropertySymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, packageId, name))
2871 {
2872 PackageRef = packageId,
2873 Name = name,
2874 Value = value
2875 });
2876
2877 if (!String.IsNullOrEmpty(condition))
2878 {
2879 symbol.Condition = condition;
2880 }
2881 }
2882 }
2883
2884 /// <summary>
2885 /// Parse SlipstreamMsp element
2886 /// </summary>
2887 /// <param name="node">Element to parse</param>
2888 /// <param name="packageId">Id of parent element</param>
2889 private void ParseSlipstreamMspElement(XElement node, string packageId)
2890 {
2891 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2892 string id = null;
2893
2894 foreach (var attrib in node.Attributes())
2895 {
2896 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
2897 {
2898 switch (attrib.Name.LocalName)
2899 {
2900 case "Id":
2901 id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
2902 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixBundlePackage, id);
2903 break;
2904 default:
2905 this.Core.UnexpectedAttribute(node, attrib);
2906 break;
2907 }
2908 }
2909 else
2910 {
2911 this.Core.ParseExtensionAttribute(node, attrib);
2912 }
2913 }
2914
2915 if (null == id)
2916 {
2917 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
2918 }
2919
2920 this.Core.ParseForExtensionElements(node);
2921
2922 if (!this.Core.EncounteredError)
2923 {
2924 this.Core.AddSymbol(new WixBundleSlipstreamMspSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, packageId, id))
2925 {
2926 TargetPackageRef = packageId,
2927 MspPackageRef = id
2928 });
2929 }
2930 }
2931
2932 /// <summary>
2933 /// Parse RelatedBundle element
2934 /// </summary>
2935 /// <param name="node">Element to parse</param>
2936 private void ParseRelatedBundleElement(XElement node)
2937 {
2938 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2939 string id = null;
2940 var actionType = RelatedBundleActionType.Detect;
2941
2942 foreach (var attrib in node.Attributes())
2943 {
2944 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
2945 {
2946 switch (attrib.Name.LocalName)
2947 {
2948 case "Id":
2949 id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
2950 break;
2951 case "Action":
2952 var action = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
2953 switch (action)
2954 {
2955 case "Detect":
2956 case "detect":
2957 actionType = RelatedBundleActionType.Detect;
2958 break;
2959 case "Upgrade":
2960 case "upgrade":
2961 actionType = RelatedBundleActionType.Upgrade;
2962 break;
2963 case "Addon":
2964 case "addon":
2965 actionType = RelatedBundleActionType.Addon;
2966 break;
2967 case "Patch":
2968 case "patch":
2969 actionType = RelatedBundleActionType.Patch;
2970 break;
2971 case "":
2972 break;
2973 default:
2974 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Action", action, "Detect", "Upgrade", "Addon", "Patch"));
2975 break;
2976 }
2977 break;
2978 default:
2979 this.Core.UnexpectedAttribute(node, attrib);
2980 break;
2981 }
2982 }
2983 else
2984 {
2985 this.Core.ParseExtensionAttribute(node, attrib);
2986 }
2987 }
2988
2989 if (null == id)
2990 {
2991 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
2992 }
2993
2994 this.Core.ParseForExtensionElements(node);
2995
2996 if (!this.Core.EncounteredError)
2997 {
2998 this.Core.AddSymbol(new WixRelatedBundleSymbol(sourceLineNumbers)
2999 {
3000 BundleId = id,
3001 Action = actionType,
3002 });
3003 }
3004 }
3005
3006 /// <summary>
3007 /// Parse Update element
3008 /// </summary>
3009 /// <param name="node">Element to parse</param>
3010 private void ParseUpdateElement(XElement node)
3011 {
3012 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
3013 string location = null;
3014
3015 foreach (var attrib in node.Attributes())
3016 {
3017 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
3018 {
3019 switch (attrib.Name.LocalName)
3020 {
3021 case "Location":
3022 location = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3023 break;
3024 default:
3025 this.Core.UnexpectedAttribute(node, attrib);
3026 break;
3027 }
3028 }
3029 else
3030 {
3031 this.Core.ParseExtensionAttribute(node, attrib);
3032 }
3033 }
3034
3035 if (null == location)
3036 {
3037 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Location"));
3038 }
3039
3040 this.Core.ParseForExtensionElements(node);
3041
3042 if (!this.Core.EncounteredError)
3043 {
3044 this.Core.AddSymbol(new WixBundleUpdateSymbol(sourceLineNumbers)
3045 {
3046 Location = location
3047 });
3048 }
3049 }
3050
3051 /// <summary>
3052 /// Parse SetVariable element
3053 /// </summary>
3054 /// <param name="node">Element to parse</param>
3055 private void ParseSetVariableElement(XElement node)
3056 {
3057 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
3058 Identifier id = null;
3059 string variable = null;
3060 string condition = null;
3061 string after = null;
3062 string value = null;
3063 string typeValue = null;
3064
3065 foreach (var attrib in node.Attributes())
3066 {
3067 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
3068 {
3069 switch (attrib.Name.LocalName)
3070 {
3071 case "Id":
3072 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
3073 break;
3074 case "Variable":
3075 variable = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3076 break;
3077 case "Condition":
3078 condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3079 break;
3080 case "After":
3081 after = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3082 break;
3083 case "Value":
3084 value = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3085 break;
3086 case "Type":
3087 typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3088 break;
3089
3090 default:
3091 this.Core.UnexpectedAttribute(node, attrib);
3092 break;
3093 }
3094 }
3095 else
3096 {
3097 this.Core.ParseExtensionAttribute(node, attrib, null);
3098 }
3099 }
3100
3101 var type = this.ValidateVariableTypeWithValue(sourceLineNumbers, node, typeValue, value);
3102
3103 this.Core.ParseForExtensionElements(node);
3104
3105 if (id == null)
3106 {
3107 id = this.Core.CreateIdentifier("sbv", variable, condition, after, value, type.ToString());
3108 }
3109
3110 this.Core.CreateWixSearchSymbol(sourceLineNumbers, node.Name.LocalName, id, variable, condition, after);
3111
3112 if (!this.Messaging.EncounteredError)
3113 {
3114 this.Core.AddSymbol(new WixSetVariableSymbol(sourceLineNumbers, id)
3115 {
3116 Value = value,
3117 Type = type,
3118 });
3119 }
3120 }
3121
3122 /// <summary>
3123 /// Parse Variable element
3124 /// </summary>
3125 /// <param name="node">Element to parse</param>
3126 private void ParseVariableElement(XElement node)
3127 {
3128 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
3129 var hidden = false;
3130 string name = null;
3131 var persisted = false;
3132 string value = null;
3133 string typeValue = null;
3134
3135 foreach (var attrib in node.Attributes())
3136 {
3137 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
3138 {
3139 switch (attrib.Name.LocalName)
3140 {
3141 case "Hidden":
3142 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
3143 {
3144 hidden = true;
3145 }
3146 break;
3147 case "Name":
3148 name = this.Core.GetAttributeBundleVariableValue(sourceLineNumbers, attrib);
3149 break;
3150 case "Persisted":
3151 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
3152 {
3153 persisted = true;
3154 }
3155 break;
3156 case "Value":
3157 value = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty);
3158 break;
3159 case "Type":
3160 typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3161 break;
3162 default:
3163 this.Core.UnexpectedAttribute(node, attrib);
3164 break;
3165 }
3166 }
3167 else
3168 {
3169 this.Core.ParseExtensionAttribute(node, attrib);
3170 }
3171 }
3172
3173 if (null == name)
3174 {
3175 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
3176 }
3177 else if (name.StartsWith("Wix", StringComparison.OrdinalIgnoreCase))
3178 {
3179 this.Core.Write(ErrorMessages.ReservedNamespaceViolation(sourceLineNumbers, node.Name.LocalName, "Name", "Wix"));
3180 }
3181
3182 if (hidden && persisted)
3183 {
3184 this.Core.Write(ErrorMessages.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Hidden", "yes", "Persisted"));
3185 }
3186
3187 var type = this.ValidateVariableTypeWithValue(sourceLineNumbers, node, typeValue, value);
3188
3189 this.Core.ParseForExtensionElements(node);
3190
3191 if (!this.Core.EncounteredError)
3192 {
3193 this.Core.AddSymbol(new WixBundleVariableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, name))
3194 {
3195 Value = value,
3196 Type = type,
3197 Hidden = hidden,
3198 Persisted = persisted
3199 });
3200 }
3201 }
3202
3203 private WixBundleVariableType ValidateVariableTypeWithValue(SourceLineNumber sourceLineNumbers, XElement node, string typeValue, string value)
3204 {
3205 WixBundleVariableType type;
3206 switch (typeValue)
3207 {
3208 case "formatted":
3209 type = WixBundleVariableType.Formatted;
3210 break;
3211 case "numeric":
3212 type = WixBundleVariableType.Numeric;
3213 break;
3214 case "string":
3215 type = WixBundleVariableType.String;
3216 break;
3217 case "version":
3218 type = WixBundleVariableType.Version;
3219 break;
3220 case null:
3221 type = WixBundleVariableType.Unknown;
3222 break;
3223 default:
3224 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Type", typeValue, "formatted", "numeric", "string", "version"));
3225 return WixBundleVariableType.Unknown;
3226 }
3227
3228 if (type != WixBundleVariableType.Unknown)
3229 {
3230 if (value == null)
3231 {
3232 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, "Variable", "Value", "Type"));
3233 }
3234
3235 return type;
3236 }
3237 else if (value == null)
3238 {
3239 return type;
3240 }
3241
3242 // Infer the type from the current value...
3243 if (value.StartsWith("v", StringComparison.OrdinalIgnoreCase))
3244 {
3245 // Version constructor does not support simple "v#" syntax so check to see if the value is
3246 // non-negative real quick.
3247 if (Int32.TryParse(value.Substring(1), NumberStyles.None, CultureInfo.InvariantCulture.NumberFormat, out var _))
3248 {
3249 return WixBundleVariableType.Version;
3250 }
3251 else if (Version.TryParse(value.Substring(1), out var _))
3252 {
3253 return WixBundleVariableType.Version;
3254 }
3255 }
3256
3257 // Not a version, check for numeric.
3258 if (Int64.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture.NumberFormat, out var _))
3259 {
3260 return WixBundleVariableType.Numeric;
3261 }
3262
3263 return WixBundleVariableType.String;
3264 }
3265 }
3266}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System;
6 using System.Xml.Linq;
7 using WixToolset.Data;
8 using WixToolset.Data.Symbols;
9
10 /// <summary>
11 /// Compiler of the WiX toolset.
12 /// </summary>
13 internal partial class Compiler : ICompiler
14 {
15 // The root registry key for the dependency extension. We write to Software\Classes explicitly
16 // based on the current security context instead of HKCR. See
17 // http://msdn.microsoft.com/en-us/library/ms724475(VS.85).aspx for more information.
18 private const string DependencyRegistryRoot = @"Software\Classes\Installer\Dependencies\";
19
20 private static readonly char[] InvalidDependencyCharacters = new char[] { ' ', '\"', ';', '\\' };
21
22 /// <summary>
23 /// Processes the ProviderKey bundle attribute.
24 /// </summary>
25 /// <param name="sourceLineNumbers">Source line number for the parent element.</param>
26 /// <param name="parentElement">Parent element of attribute.</param>
27 /// <param name="attribute">The XML attribute for the ProviderKey attribute.</param>
28 private void ParseBundleProviderKeyAttribute(SourceLineNumber sourceLineNumbers, XElement parentElement, XAttribute attribute)
29 {
30 var providerKey = this.Core.GetAttributeValue(sourceLineNumbers, attribute);
31 int illegalChar;
32
33 // Make sure the key does not contain any illegal characters or values.
34 if (String.IsNullOrEmpty(providerKey))
35 {
36 this.Messaging.Write(ErrorMessages.IllegalEmptyAttributeValue(sourceLineNumbers, parentElement.Name.LocalName, attribute.Name.LocalName));
37 }
38 else if (0 <= (illegalChar = providerKey.IndexOfAny(InvalidDependencyCharacters)))
39 {
40 this.Messaging.Write(CompilerErrors.IllegalCharactersInProvider(sourceLineNumbers, "ProviderKey", providerKey[illegalChar], String.Join(" ", InvalidDependencyCharacters)));
41 }
42 else if ("ALL" == providerKey)
43 {
44 this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, parentElement.Name.LocalName, "ProviderKey", providerKey));
45 }
46
47 if (!this.Messaging.EncounteredError)
48 {
49 // Generate the primary key for the row.
50 var id = this.Core.CreateIdentifier("dep", attribute.Name.LocalName, providerKey);
51
52 // Create the provider symbol for the bundle. The Component_ field is required
53 // in the table definition but unused for bundles, so just set it to the valid ID.
54 this.Core.AddSymbol(new WixDependencyProviderSymbol(sourceLineNumbers, id)
55 {
56 ParentRef = id.Id,
57 ProviderKey = providerKey,
58 Attributes = WixDependencyProviderAttributes.ProvidesAttributesBundle,
59 });
60 }
61 }
62
63 /// <summary>
64 /// Processes the Provides element.
65 /// </summary>
66 /// <param name="node">The XML node for the Provides element.</param>
67 /// <param name="packageType">The type of the package being chained into a bundle, or null if building an MSI package.</param>
68 /// <param name="parentId">The identifier of the parent component or package.</param>
69 /// <param name="possibleKeyPath">Possible KeyPath identifier.</param>
70 /// <returns>Yes if this is the keypath.</returns>
71 private YesNoType ParseProvidesElement(XElement node, WixBundlePackageType? packageType, string parentId, out string possibleKeyPath)
72 {
73 possibleKeyPath = null;
74
75 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
76 Identifier id = null;
77 string key = null;
78 string version = null;
79 string displayName = null;
80
81 foreach (var attrib in node.Attributes())
82 {
83 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
84 {
85 switch (attrib.Name.LocalName)
86 {
87 case "Id":
88 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
89 break;
90 case "Key":
91 key = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
92 break;
93 case "Version":
94 version = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib);
95 break;
96 case "DisplayName":
97 displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
98 break;
99 default:
100 this.Core.UnexpectedAttribute(node, attrib);
101 break;
102 }
103 }
104 else
105 {
106 this.Core.ParseExtensionAttribute(node, attrib);
107 }
108 }
109
110 // Make sure the key is valid. The key will default to the ProductCode for MSI packages
111 // and the package code for MSP packages in the binder if not specified.
112 if (!String.IsNullOrEmpty(key))
113 {
114 int illegalChar;
115
116 // Make sure the key does not contain any illegal characters or values.
117 if (0 <= (illegalChar = key.IndexOfAny(InvalidDependencyCharacters)))
118 {
119 this.Messaging.Write(CompilerErrors.IllegalCharactersInProvider(sourceLineNumbers, "Key", key[illegalChar], String.Join(" ", InvalidDependencyCharacters)));
120 }
121 else if ("ALL" == key)
122 {
123 this.Messaging.Write(CompilerErrors.ReservedValue(sourceLineNumbers, node.Name.LocalName, "Key", key));
124 }
125 }
126 else if (!packageType.HasValue)
127 {
128 // Make sure the ProductCode is authored and set the key.
129 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, "ProductCode");
130 key = "!(bind.property.ProductCode)";
131 }
132 else if (WixBundlePackageType.Exe == packageType || WixBundlePackageType.Msu == packageType)
133 {
134 // Must specify the provider key when authored for a package.
135 this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key"));
136 }
137
138 // The Version attribute should not be authored in or for an MSI package.
139 if (!String.IsNullOrEmpty(version))
140 {
141 switch (packageType)
142 {
143 case null:
144 this.Messaging.Write(CompilerWarnings.DiscouragedVersionAttribute(sourceLineNumbers));
145 break;
146 case WixBundlePackageType.Msi:
147 this.Messaging.Write(CompilerWarnings.DiscouragedVersionAttribute(sourceLineNumbers, parentId));
148 break;
149 }
150 }
151 else if (WixBundlePackageType.Msp == packageType || WixBundlePackageType.Msu == packageType)
152 {
153 // Must specify the Version when authored for packages that do not contain a version.
154 this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version"));
155 }
156
157 // Need the element ID for child element processing, so generate now if not authored.
158 if (null == id)
159 {
160 id = this.Core.CreateIdentifier("dep", node.Name.LocalName, parentId, key);
161 }
162
163 foreach (var child in node.Elements())
164 {
165 if (CompilerCore.WixNamespace == child.Name.Namespace)
166 {
167 switch (child.Name.LocalName)
168 {
169 case "Requires":
170 this.ParseRequiresElement(child, id.Id);
171 break;
172 case "RequiresRef":
173 this.ParseRequiresRefElement(child, id.Id, requiresAction: !packageType.HasValue);
174 break;
175 default:
176 this.Core.UnexpectedElement(node, child);
177 break;
178 }
179 }
180 else
181 {
182 this.Core.ParseExtensionElement(node, child);
183 }
184 }
185
186 if (!this.Messaging.EncounteredError)
187 {
188 var symbol = this.Core.AddSymbol(new WixDependencyProviderSymbol(sourceLineNumbers, id)
189 {
190 ParentRef = parentId,
191 ProviderKey = key,
192 });
193
194 if (!String.IsNullOrEmpty(version))
195 {
196 symbol.Version = version;
197 }
198
199 if (!String.IsNullOrEmpty(displayName))
200 {
201 symbol.DisplayName = displayName;
202 }
203
204 if (!packageType.HasValue)
205 {
206 // Generate registry rows for the provider using binder properties.
207 var keyProvides = String.Concat(DependencyRegistryRoot, key);
208 var root = RegistryRootType.MachineUser;
209
210 var value = "[ProductCode]";
211 this.Core.CreateRegistryRow(sourceLineNumbers, root, keyProvides, null, value, parentId);
212
213 value = !String.IsNullOrEmpty(version) ? version : "[ProductVersion]";
214 var versionRegistrySymbol = this.Core.CreateRegistryRow(sourceLineNumbers, root, keyProvides, "Version", value, parentId);
215
216 value = !String.IsNullOrEmpty(displayName) ? displayName : "[ProductName]";
217 this.Core.CreateRegistryRow(sourceLineNumbers, root, keyProvides, "DisplayName", value, parentId);
218
219 // Use the Version registry value and use that as a potential key path.
220 possibleKeyPath = versionRegistrySymbol.Id;
221 }
222 }
223
224 return YesNoType.NotSet;
225 }
226
227 /// <summary>
228 /// Processes the Requires element.
229 /// </summary>
230 /// <param name="node">The XML node for the Requires element.</param>
231 /// <param name="providerId">The parent provider identifier.</param>
232 private void ParseRequiresElement(XElement node, string providerId)
233 {
234 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
235 Identifier id = null;
236 string providerKey = null;
237 string minVersion = null;
238 string maxVersion = null;
239 var attributes = WixDependencySymbolAttributes.None;
240 var illegalChar = -1;
241
242 foreach (var attrib in node.Attributes())
243 {
244 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
245 {
246 switch (attrib.Name.LocalName)
247 {
248 case "Id":
249 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
250 break;
251 case "ProviderKey":
252 providerKey = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
253 break;
254 case "Minimum":
255 minVersion = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib);
256 break;
257 case "Maximum":
258 maxVersion = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib);
259 break;
260 case "IncludeMinimum":
261 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
262 {
263 attributes |= WixDependencySymbolAttributes.RequiresAttributesMinVersionInclusive;
264 }
265 break;
266 case "IncludeMaximum":
267 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
268 {
269 attributes |= WixDependencySymbolAttributes.RequiresAttributesMaxVersionInclusive;
270 }
271 break;
272 default:
273 this.Core.UnexpectedAttribute(node, attrib);
274 break;
275 }
276 }
277 else
278 {
279 this.Core.ParseExtensionAttribute(node, attrib);
280 }
281 }
282
283 this.Core.ParseForExtensionElements(node);
284
285 if (null == id)
286 {
287 // Generate an ID only if this element is authored under a Provides element; otherwise, a RequiresRef
288 // element will be necessary and the Id attribute will be required.
289 if (!String.IsNullOrEmpty(providerId))
290 {
291 id = this.Core.CreateIdentifier("dep", node.Name.LocalName, providerKey);
292 }
293 else
294 {
295 this.Messaging.Write(ErrorMessages.ExpectedAttributeWhenElementNotUnderElement(sourceLineNumbers, node.Name.LocalName, "Id", "Provides"));
296 id = Identifier.Invalid;
297 }
298 }
299
300 if (String.IsNullOrEmpty(providerKey))
301 {
302 this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ProviderKey"));
303 }
304 // Make sure the key does not contain any illegal characters.
305 else if (0 <= (illegalChar = providerKey.IndexOfAny(InvalidDependencyCharacters)))
306 {
307 this.Messaging.Write(CompilerErrors.IllegalCharactersInProvider(sourceLineNumbers, "ProviderKey", providerKey[illegalChar], String.Join(" ", InvalidDependencyCharacters)));
308 }
309
310 if (!this.Messaging.EncounteredError)
311 {
312 this.Core.AddSymbol(new WixDependencySymbol(sourceLineNumbers, id)
313 {
314 ProviderKey = providerKey,
315 MinVersion = minVersion,
316 MaxVersion = maxVersion,
317 Attributes = attributes
318 });
319
320 // Create the relationship between this WixDependency symbol and the WixDependencyProvider symbol.
321 if (!String.IsNullOrEmpty(providerId))
322 {
323 this.Core.AddSymbol(new WixDependencyRefSymbol(sourceLineNumbers)
324 {
325 WixDependencyProviderRef = providerId,
326 WixDependencyRef = id.Id,
327 });
328 }
329 }
330 }
331
332 /// <summary>
333 /// Processes the RequiresRef element.
334 /// </summary>
335 /// <param name="node">The XML node for the RequiresRef element.</param>
336 /// <param name="providerId">The parent provider identifier.</param>
337 /// <param name="requiresAction">Whether the Requires custom action should be referenced.</param>
338 private void ParseRequiresRefElement(XElement node, string providerId, bool requiresAction)
339 {
340 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
341 string id = null;
342
343 foreach (var attrib in node.Attributes())
344 {
345 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
346 {
347 switch (attrib.Name.LocalName)
348 {
349 case "Id":
350 id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
351 break;
352 default:
353 this.Core.UnexpectedAttribute(node, attrib);
354 break;
355 }
356 }
357 else
358 {
359 this.Core.ParseExtensionAttribute(node, attrib);
360 }
361 }
362
363 this.Core.ParseForExtensionElements(node);
364
365 if (String.IsNullOrEmpty(id))
366 {
367 this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
368 }
369
370 if (!this.Messaging.EncounteredError)
371 {
372 // Create a link dependency on the row that contains information we'll need during bind.
373 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixDependency, id);
374
375 // Create the relationship between the WixDependency row and the parent WixDependencyProvider row.
376 this.Core.AddSymbol(new WixDependencyRefSymbol(sourceLineNumbers)
377 {
378 WixDependencyProviderRef = providerId,
379 WixDependencyRef = id,
380 });
381 }
382 }
383 }
384}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System;
6 using System.IO;
7 using System.Xml.Linq;
8 using WixToolset.Data;
9 using WixToolset.Data.Symbols;
10 using WixToolset.Data.WindowsInstaller;
11
12 /// <summary>
13 /// Compiler of the WiX toolset.
14 /// </summary>
15 internal partial class Compiler : ICompiler
16 {
17 /// <summary>
18 /// Parses an EmbeddedChaniner element.
19 /// </summary>
20 /// <param name="node">Element to parse.</param>
21 private void ParseEmbeddedChainerElement(XElement node)
22 {
23 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
24 Identifier id = null;
25 string commandLine = null;
26 string condition = null;
27 string source = null;
28 var type = 0;
29
30 foreach (var attrib in node.Attributes())
31 {
32 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
33 {
34 switch (attrib.Name.LocalName)
35 {
36 case "Id":
37 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
38 break;
39 case "BinarySource":
40 if (null != source)
41 {
42 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "FileSource", "PropertySource"));
43 }
44 source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
45 type = 0x2;
46 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Binary, source); // add a reference to the appropriate Binary
47 break;
48 case "CommandLine":
49 commandLine = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
50 break;
51 case "Condition":
52 condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
53 break;
54 case "FileSource":
55 if (null != source)
56 {
57 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinarySource", "PropertySource"));
58 }
59 source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
60 type = 0x12;
61 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, source); // add a reference to the appropriate File
62 break;
63 case "PropertySource":
64 if (null != source)
65 {
66 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinarySource", "FileSource"));
67 }
68 source = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
69 type = 0x32;
70 // cannot add a reference to a Property because it may be created at runtime.
71 break;
72 default:
73 this.Core.UnexpectedAttribute(node, attrib);
74 break;
75 }
76 }
77 else
78 {
79 this.Core.ParseExtensionAttribute(node, attrib);
80 }
81 }
82
83 if (null == id)
84 {
85 id = this.Core.CreateIdentifier("mec", source, type.ToString());
86 }
87
88 if (null == source)
89 {
90 this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "BinarySource", "FileSource", "PropertySource"));
91 }
92
93 this.Core.ParseForExtensionElements(node);
94
95 if (!this.Core.EncounteredError)
96 {
97 this.Core.AddSymbol(new MsiEmbeddedChainerSymbol(sourceLineNumbers, id)
98 {
99 Condition = condition,
100 CommandLine = commandLine,
101 Source = source,
102 Type = type
103 });
104 }
105 }
106
107 /// <summary>
108 /// Parses an EmbeddedUI element.
109 /// </summary>
110 /// <param name="node">Element to parse.</param>
111 private void ParseEmbeddedUIElement(XElement node)
112 {
113 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
114 Identifier id = null;
115 string name = null;
116 var supportsBasicUI = false;
117 var messageFilter = WindowsInstallerConstants.INSTALLLOGMODE_FATALEXIT | WindowsInstallerConstants.INSTALLLOGMODE_ERROR | WindowsInstallerConstants.INSTALLLOGMODE_WARNING | WindowsInstallerConstants.INSTALLLOGMODE_USER
118 | WindowsInstallerConstants.INSTALLLOGMODE_INFO | WindowsInstallerConstants.INSTALLLOGMODE_FILESINUSE | WindowsInstallerConstants.INSTALLLOGMODE_RESOLVESOURCE
119 | WindowsInstallerConstants.INSTALLLOGMODE_OUTOFDISKSPACE | WindowsInstallerConstants.INSTALLLOGMODE_ACTIONSTART | WindowsInstallerConstants.INSTALLLOGMODE_ACTIONDATA
120 | WindowsInstallerConstants.INSTALLLOGMODE_PROGRESS | WindowsInstallerConstants.INSTALLLOGMODE_COMMONDATA | WindowsInstallerConstants.INSTALLLOGMODE_INITIALIZE
121 | WindowsInstallerConstants.INSTALLLOGMODE_TERMINATE | WindowsInstallerConstants.INSTALLLOGMODE_SHOWDIALOG | WindowsInstallerConstants.INSTALLLOGMODE_RMFILESINUSE
122 | WindowsInstallerConstants.INSTALLLOGMODE_INSTALLSTART | WindowsInstallerConstants.INSTALLLOGMODE_INSTALLEND;
123 string sourceFile = null;
124
125 foreach (var attrib in node.Attributes())
126 {
127 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
128 {
129 switch (attrib.Name.LocalName)
130 {
131 case "Id":
132 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
133 break;
134 case "Name":
135 name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false);
136 break;
137 case "IgnoreFatalExit":
138 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
139 {
140 messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_FATALEXIT;
141 }
142 break;
143 case "IgnoreError":
144 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
145 {
146 messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_ERROR;
147 }
148 break;
149 case "IgnoreWarning":
150 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
151 {
152 messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_WARNING;
153 }
154 break;
155 case "IgnoreUser":
156 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
157 {
158 messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_USER;
159 }
160 break;
161 case "IgnoreInfo":
162 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
163 {
164 messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_INFO;
165 }
166 break;
167 case "IgnoreFilesInUse":
168 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
169 {
170 messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_FILESINUSE;
171 }
172 break;
173 case "IgnoreResolveSource":
174 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
175 {
176 messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_RESOLVESOURCE;
177 }
178 break;
179 case "IgnoreOutOfDiskSpace":
180 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
181 {
182 messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_OUTOFDISKSPACE;
183 }
184 break;
185 case "IgnoreActionStart":
186 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
187 {
188 messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_ACTIONSTART;
189 }
190 break;
191 case "IgnoreActionData":
192 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
193 {
194 messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_ACTIONDATA;
195 }
196 break;
197 case "IgnoreProgress":
198 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
199 {
200 messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_PROGRESS;
201 }
202 break;
203 case "IgnoreCommonData":
204 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
205 {
206 messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_COMMONDATA;
207 }
208 break;
209 case "IgnoreInitialize":
210 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
211 {
212 messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_INITIALIZE;
213 }
214 break;
215 case "IgnoreTerminate":
216 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
217 {
218 messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_TERMINATE;
219 }
220 break;
221 case "IgnoreShowDialog":
222 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
223 {
224 messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_SHOWDIALOG;
225 }
226 break;
227 case "IgnoreRMFilesInUse":
228 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
229 {
230 messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_RMFILESINUSE;
231 }
232 break;
233 case "IgnoreInstallStart":
234 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
235 {
236 messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_INSTALLSTART;
237 }
238 break;
239 case "IgnoreInstallEnd":
240 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
241 {
242 messageFilter ^= WindowsInstallerConstants.INSTALLLOGMODE_INSTALLEND;
243 }
244 break;
245 case "SourceFile":
246 sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
247 break;
248 case "SupportBasicUI":
249 supportsBasicUI = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
250 break;
251 default:
252 this.Core.UnexpectedAttribute(node, attrib);
253 break;
254 }
255 }
256 else
257 {
258 this.Core.ParseExtensionAttribute(node, attrib);
259 }
260 }
261
262 if (String.IsNullOrEmpty(sourceFile))
263 {
264 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile"));
265 }
266 else if (String.IsNullOrEmpty(name))
267 {
268 name = Path.GetFileName(sourceFile);
269 if (!this.Core.IsValidLongFilename(name, false))
270 {
271 this.Core.Write(ErrorMessages.IllegalLongFilename(sourceLineNumbers, node.Name.LocalName, "Source", name));
272 }
273 }
274
275 if (null == id)
276 {
277 if (!String.IsNullOrEmpty(name))
278 {
279 id = this.Core.CreateIdentifierFromFilename(name);
280 }
281
282 if (null == id)
283 {
284 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
285 }
286 else if (!Common.IsIdentifier(id.Id))
287 {
288 this.Core.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, node.Name.LocalName, "Id", id.Id));
289 }
290 }
291 else if (String.IsNullOrEmpty(name))
292 {
293 name = id.Id;
294 }
295
296 if (!name.Contains("."))
297 {
298 this.Core.Write(ErrorMessages.InvalidEmbeddedUIFileName(sourceLineNumbers, name));
299 }
300
301 foreach (var child in node.Elements())
302 {
303 if (CompilerCore.WixNamespace == child.Name.Namespace)
304 {
305 switch (child.Name.LocalName)
306 {
307 case "EmbeddedUIResource":
308 this.ParseEmbeddedUIResourceElement(child);
309 break;
310 default:
311 this.Core.UnexpectedElement(node, child);
312 break;
313 }
314 }
315 else
316 {
317 this.Core.ParseExtensionElement(node, child);
318 }
319 }
320
321 if (!this.Core.EncounteredError)
322 {
323 this.Core.AddSymbol(new MsiEmbeddedUISymbol(sourceLineNumbers, id)
324 {
325 FileName = name,
326 EntryPoint = true,
327 SupportsBasicUI = supportsBasicUI,
328 MessageFilter = messageFilter,
329 Source = sourceFile
330 });
331 }
332 }
333
334 /// <summary>
335 /// Parses a embedded UI resource element.
336 /// </summary>
337 /// <param name="node">Element to parse.</param>
338 private void ParseEmbeddedUIResourceElement(XElement node)
339 {
340 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
341 Identifier id = null;
342 string name = null;
343 string sourceFile = null;
344
345 foreach (var attrib in node.Attributes())
346 {
347 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
348 {
349 switch (attrib.Name.LocalName)
350 {
351 case "Id":
352 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
353 break;
354 case "Name":
355 name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false);
356 break;
357 case "SourceFile":
358 sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
359 break;
360 default:
361 this.Core.UnexpectedAttribute(node, attrib);
362 break;
363 }
364 }
365 else
366 {
367 this.Core.ParseExtensionAttribute(node, attrib);
368 }
369 }
370
371 if (String.IsNullOrEmpty(sourceFile))
372 {
373 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile"));
374 }
375 else if (String.IsNullOrEmpty(name))
376 {
377 name = Path.GetFileName(sourceFile);
378 if (!this.Core.IsValidLongFilename(name, false))
379 {
380 this.Core.Write(ErrorMessages.IllegalLongFilename(sourceLineNumbers, node.Name.LocalName, "Source", name));
381 }
382 }
383
384 if (null == id)
385 {
386 if (!String.IsNullOrEmpty(name))
387 {
388 id = this.Core.CreateIdentifierFromFilename(name);
389 }
390
391 if (null == id)
392 {
393 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
394 }
395 else if (!Common.IsIdentifier(id.Id))
396 {
397 this.Core.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, node.Name.LocalName, "Id", id.Id));
398 }
399 }
400 else if (String.IsNullOrEmpty(name))
401 {
402 name = id.Id;
403 }
404
405 this.Core.ParseForExtensionElements(node);
406
407 if (!this.Core.EncounteredError)
408 {
409 this.Core.AddSymbol(new MsiEmbeddedUISymbol(sourceLineNumbers, id)
410 {
411 FileName = name,
412 Source = sourceFile
413 });
414 }
415 }
416 }
417}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System;
6 using System.Globalization;
7 using System.Xml.Linq;
8 using WixToolset.Data;
9 using WixToolset.Data.Symbols;
10 using WixToolset.Extensibility;
11
12 /// <summary>
13 /// Compiler of the WiX toolset.
14 /// </summary>
15 internal partial class Compiler : ICompiler
16 {
17 /// <summary>
18 /// Parses a module element.
19 /// </summary>
20 /// <param name="node">Element to parse.</param>
21 private void ParseModuleElement(XElement node)
22 {
23 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
24 var codepage = 0;
25 string moduleId = null;
26 string version = null;
27 var setCodepage = false;
28 var setPackageName = false;
29 var setKeywords = false;
30 var ignoredForMergeModules = false;
31
32 this.GetDefaultPlatformAndInstallerVersion(out var platform, out var msiVersion);
33
34 this.activeName = null;
35 this.activeLanguage = null;
36
37 foreach (var attrib in node.Attributes())
38 {
39 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
40 {
41 switch (attrib.Name.LocalName)
42 {
43 case "Id":
44 this.activeName = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
45 if ("PUT-MODULE-NAME-HERE" == this.activeName)
46 {
47 this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, this.activeName));
48 }
49 else
50 {
51 this.activeName = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
52 }
53 break;
54 case "Codepage":
55 codepage = this.Core.GetAttributeCodePageValue(sourceLineNumbers, attrib);
56 break;
57 case "Guid":
58 moduleId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
59 break;
60 case "InstallerVersion":
61 msiVersion = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue);
62 break;
63 case "Language":
64 this.activeLanguage = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue);
65 break;
66 case "Version":
67 version = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib);
68 break;
69 default:
70 this.Core.UnexpectedAttribute(node, attrib);
71 break;
72 }
73 }
74 else
75 {
76 this.Core.ParseExtensionAttribute(node, attrib);
77 }
78 }
79
80 if (null == this.activeName)
81 {
82 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
83 }
84
85 if (null == moduleId)
86 {
87 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Guid"));
88 }
89
90 if (null == this.activeLanguage)
91 {
92 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Language"));
93 }
94
95 if (null == version)
96 {
97 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version"));
98 }
99 else if (!CompilerCore.IsValidModuleOrBundleVersion(version))
100 {
101 this.Core.Write(WarningMessages.InvalidModuleOrBundleVersion(sourceLineNumbers, "Module", version));
102 }
103
104 try
105 {
106 this.compilingModule = true; // notice that we are actually building a Merge Module here
107 this.Core.CreateActiveSection(this.activeName, SectionType.Module, this.Context.CompilationId);
108
109 foreach (var child in node.Elements())
110 {
111 if (CompilerCore.WixNamespace == child.Name.Namespace)
112 {
113 switch (child.Name.LocalName)
114 {
115 case "AdminExecuteSequence":
116 this.ParseSequenceElement(child, SequenceTable.AdminExecuteSequence);
117 break;
118 case "AdminUISequence":
119 this.ParseSequenceElement(child, SequenceTable.AdminUISequence);
120 break;
121 case "AdvertiseExecuteSequence":
122 this.ParseSequenceElement(child, SequenceTable.AdvertiseExecuteSequence);
123 break;
124 case "InstallExecuteSequence":
125 this.ParseSequenceElement(child, SequenceTable.InstallExecuteSequence);
126 break;
127 case "InstallUISequence":
128 this.ParseSequenceElement(child, SequenceTable.InstallUISequence);
129 break;
130 case "AppId":
131 this.ParseAppIdElement(child, null, YesNoType.Yes, null, null, null);
132 break;
133 case "Binary":
134 this.ParseBinaryElement(child);
135 break;
136 case "Component":
137 this.ParseComponentElement(child, ComplexReferenceParentType.Module, this.activeName, this.activeLanguage, CompilerConstants.IntegerNotSet, null, null);
138 break;
139 case "ComponentGroupRef":
140 this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.Module, this.activeName, this.activeLanguage);
141 break;
142 case "ComponentRef":
143 this.ParseComponentRefElement(child, ComplexReferenceParentType.Module, this.activeName, this.activeLanguage);
144 break;
145 case "Configuration":
146 this.ParseConfigurationElement(child);
147 break;
148 case "CustomAction":
149 this.ParseCustomActionElement(child);
150 break;
151 case "CustomActionRef":
152 this.ParseSimpleRefElement(child, SymbolDefinitions.CustomAction);
153 break;
154 case "CustomTable":
155 this.ParseCustomTableElement(child);
156 break;
157 case "CustomTableRef":
158 this.ParseCustomTableRefElement(child);
159 break;
160 case "Dependency":
161 this.ParseDependencyElement(child);
162 break;
163 case "Directory":
164 this.ParseDirectoryElement(child, null, CompilerConstants.IntegerNotSet, String.Empty);
165 break;
166 case "DirectoryRef":
167 this.ParseDirectoryRefElement(child);
168 break;
169 case "EmbeddedChainer":
170 this.ParseEmbeddedChainerElement(child);
171 break;
172 case "EmbeddedChainerRef":
173 this.ParseSimpleRefElement(child, SymbolDefinitions.MsiEmbeddedChainer);
174 break;
175 case "EnsureTable":
176 this.ParseEnsureTableElement(child);
177 break;
178 case "Exclusion":
179 this.ParseExclusionElement(child);
180 break;
181 case "Icon":
182 this.ParseIconElement(child);
183 break;
184 case "IgnoreTable":
185 this.ParseIgnoreTableElement(child);
186 break;
187 case "Property":
188 this.ParsePropertyElement(child);
189 break;
190 case "PropertyRef":
191 this.ParseSimpleRefElement(child, SymbolDefinitions.Property);
192 break;
193 case "Requires":
194 this.ParseRequiresElement(child, null);
195 break;
196 case "SetDirectory":
197 this.ParseSetDirectoryElement(child);
198 break;
199 case "SetProperty":
200 this.ParseSetPropertyElement(child);
201 break;
202 case "SFPCatalog":
203 string parentName = null;
204 this.ParseSFPCatalogElement(child, ref parentName);
205 break;
206 case "StandardDirectory":
207 this.ParseStandardDirectoryElement(child);
208 break;
209 case "Substitution":
210 this.ParseSubstitutionElement(child);
211 break;
212 case "SummaryInformation":
213 this.ParseSummaryInformationElement(child, ref setCodepage, ref setPackageName, ref setKeywords, ref ignoredForMergeModules);
214 break;
215 case "UI":
216 this.ParseUIElement(child);
217 break;
218 case "UIRef":
219 this.ParseSimpleRefElement(child, SymbolDefinitions.WixUI);
220 break;
221 case "WixVariable":
222 this.ParseWixVariableElement(child);
223 break;
224 default:
225 this.Core.UnexpectedElement(node, child);
226 break;
227 }
228 }
229 else
230 {
231 this.Core.ParseExtensionElement(node, child);
232 }
233 }
234
235
236 if (!this.Core.EncounteredError)
237 {
238 if (!setPackageName)
239 {
240 this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers)
241 {
242 PropertyId = SummaryInformationType.Subject,
243 Value = this.activeName
244 });
245 }
246
247 if (!setKeywords)
248 {
249 this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers)
250 {
251 PropertyId = SummaryInformationType.Keywords,
252 Value = "Installer"
253 });
254 }
255
256 var symbol = this.Core.AddSymbol(new WixModuleSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, this.activeName, this.activeLanguage))
257 {
258 ModuleId = this.activeName,
259 Language = this.activeLanguage,
260 Version = version
261 });
262
263 this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers)
264 {
265 PropertyId = SummaryInformationType.PackageCode,
266 Value = moduleId
267 });
268
269 this.ValidateAndAddCommonSummaryInformationSymbols(sourceLineNumbers, msiVersion, platform, this.activeLanguage);
270 }
271 }
272 finally
273 {
274 this.compilingModule = false; // notice that we are no longer building a Merge Module here
275 }
276 }
277
278 /// <summary>
279 /// Parses a dependency element.
280 /// </summary>
281 /// <param name="node">Element to parse.</param>
282 private void ParseDependencyElement(XElement node)
283 {
284 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
285 string requiredId = null;
286 var requiredLanguage = CompilerConstants.IntegerNotSet;
287 string requiredVersion = null;
288
289 foreach (var attrib in node.Attributes())
290 {
291 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
292 {
293 switch (attrib.Name.LocalName)
294 {
295 case "RequiredId":
296 requiredId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
297 break;
298 case "RequiredLanguage":
299 requiredLanguage = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue);
300 break;
301 case "RequiredVersion":
302 requiredVersion = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
303 break;
304 default:
305 this.Core.UnexpectedAttribute(node, attrib);
306 break;
307 }
308 }
309 else
310 {
311 this.Core.ParseExtensionAttribute(node, attrib);
312 }
313 }
314
315 if (null == requiredId)
316 {
317 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "RequiredId"));
318 requiredId = String.Empty;
319 }
320
321 if (CompilerConstants.IntegerNotSet == requiredLanguage)
322 {
323 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "RequiredLanguage"));
324 requiredLanguage = CompilerConstants.IllegalInteger;
325 }
326
327 this.Core.ParseForExtensionElements(node);
328
329 if (!this.Core.EncounteredError)
330 {
331 var symbol = this.Core.AddSymbol(new ModuleDependencySymbol(sourceLineNumbers)
332 {
333 ModuleID = this.activeName,
334 RequiredID = requiredId,
335 RequiredLanguage = requiredLanguage,
336 RequiredVersion = requiredVersion
337 });
338
339 symbol.Set((int)ModuleDependencySymbolFields.ModuleLanguage, this.activeLanguage);
340 }
341 }
342
343 /// <summary>
344 /// Parses an exclusion element.
345 /// </summary>
346 /// <param name="node">Element to parse.</param>
347 private void ParseExclusionElement(XElement node)
348 {
349 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
350 string excludedId = null;
351 var excludeExceptLanguage = CompilerConstants.IntegerNotSet;
352 var excludeLanguage = CompilerConstants.IntegerNotSet;
353 var excludedLanguageField = "0";
354 string excludedMaxVersion = null;
355 string excludedMinVersion = null;
356
357 foreach (var attrib in node.Attributes())
358 {
359 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
360 {
361 switch (attrib.Name.LocalName)
362 {
363 case "ExcludedId":
364 excludedId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
365 break;
366 case "ExcludeExceptLanguage":
367 excludeExceptLanguage = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue);
368 break;
369 case "ExcludeLanguage":
370 excludeLanguage = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue);
371 break;
372 case "ExcludedMaxVersion":
373 excludedMaxVersion = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
374 break;
375 case "ExcludedMinVersion":
376 excludedMinVersion = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
377 break;
378 default:
379 this.Core.UnexpectedAttribute(node, attrib);
380 break;
381 }
382 }
383 else
384 {
385 this.Core.ParseExtensionAttribute(node, attrib);
386 }
387 }
388
389 if (null == excludedId)
390 {
391 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ExcludedId"));
392 excludedId = String.Empty;
393 }
394
395 if (CompilerConstants.IntegerNotSet != excludeExceptLanguage && CompilerConstants.IntegerNotSet != excludeLanguage)
396 {
397 this.Core.Write(ErrorMessages.IllegalModuleExclusionLanguageAttributes(sourceLineNumbers));
398 }
399 else if (CompilerConstants.IntegerNotSet != excludeExceptLanguage)
400 {
401 excludedLanguageField = Convert.ToString(-excludeExceptLanguage, CultureInfo.InvariantCulture);
402 }
403 else if (CompilerConstants.IntegerNotSet != excludeLanguage)
404 {
405 excludedLanguageField = Convert.ToString(excludeLanguage, CultureInfo.InvariantCulture);
406 }
407
408 this.Core.ParseForExtensionElements(node);
409
410 if (!this.Core.EncounteredError)
411 {
412 var symbol = this.Core.AddSymbol(new ModuleExclusionSymbol(sourceLineNumbers)
413 {
414 ModuleID = this.activeName,
415 ExcludedID = excludedId,
416 ExcludedMinVersion = excludedMinVersion,
417 ExcludedMaxVersion = excludedMaxVersion
418 });
419
420 symbol.Set((int)ModuleExclusionSymbolFields.ModuleLanguage, this.activeLanguage);
421 symbol.Set((int)ModuleExclusionSymbolFields.ExcludedLanguage, excludedLanguageField);
422 }
423 }
424
425 /// <summary>
426 /// Parses a configuration element for a configurable merge module.
427 /// </summary>
428 /// <param name="node">Element to parse.</param>
429 private void ParseConfigurationElement(XElement node)
430 {
431 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
432 string contextData = null;
433 string defaultValue = null;
434 string description = null;
435 string displayName = null;
436 var format = CompilerConstants.IntegerNotSet;
437 string helpKeyword = null;
438 string helpLocation = null;
439 bool keyNoOrphan = false;
440 bool nonNullable = false;
441 Identifier name = null;
442 string type = null;
443
444 foreach (var attrib in node.Attributes())
445 {
446 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
447 {
448 switch (attrib.Name.LocalName)
449 {
450 case "Name":
451 name = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
452 break;
453 case "ContextData":
454 contextData = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
455 break;
456 case "Description":
457 description = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
458 break;
459 case "DefaultValue":
460 defaultValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
461 break;
462 case "DisplayName":
463 displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
464 break;
465 case "Format":
466 var formatStr = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
467 switch (formatStr)
468 {
469 case "Text":
470 case "text":
471 format = 0;
472 break;
473 case "Key":
474 case "key":
475 format = 1;
476 break;
477 case "Integer":
478 case "integer":
479 format = 2;
480 break;
481 case "Bitfield":
482 case "bitfield":
483 format = 3;
484 break;
485 case "":
486 break;
487 default:
488 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Format", formatStr, "Text", "Key", "Integer", "Bitfield"));
489 break;
490 }
491 break;
492 case "HelpKeyword":
493 helpKeyword = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
494 break;
495 case "HelpLocation":
496 helpLocation = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
497 break;
498 case "KeyNoOrphan":
499 keyNoOrphan = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
500 break;
501 case "NonNullable":
502 nonNullable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
503 break;
504 case "Type":
505 type = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
506 break;
507 default:
508 this.Core.UnexpectedAttribute(node, attrib);
509 break;
510 }
511 }
512 else
513 {
514 this.Core.ParseExtensionAttribute(node, attrib);
515 }
516 }
517
518 if (null == name)
519 {
520 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
521 }
522
523 if (CompilerConstants.IntegerNotSet == format)
524 {
525 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Format"));
526 }
527
528 this.Core.ParseForExtensionElements(node);
529
530 if (!this.Core.EncounteredError)
531 {
532 this.Core.AddSymbol(new ModuleConfigurationSymbol(sourceLineNumbers, name)
533 {
534 Format = format,
535 Type = type,
536 ContextData = contextData,
537 DefaultValue = defaultValue,
538 KeyNoOrphan = keyNoOrphan,
539 NonNullable = nonNullable,
540 DisplayName = displayName,
541 Description = description,
542 HelpLocation = helpLocation,
543 HelpKeyword = helpKeyword
544 });
545 }
546 }
547
548 /// <summary>
549 /// Parses a substitution element for a configurable merge module.
550 /// </summary>
551 /// <param name="node">Element to parse.</param>
552 private void ParseSubstitutionElement(XElement node)
553 {
554 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
555 string column = null;
556 string rowKeys = null;
557 string table = null;
558 string value = null;
559
560 foreach (var attrib in node.Attributes())
561 {
562 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
563 {
564 switch (attrib.Name.LocalName)
565 {
566 case "Column":
567 column = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
568 break;
569 case "Row":
570 rowKeys = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
571 break;
572 case "Table":
573 table = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
574 break;
575 case "Value":
576 value = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
577 break;
578 default:
579 this.Core.UnexpectedAttribute(node, attrib);
580 break;
581 }
582 }
583 else
584 {
585 this.Core.ParseExtensionAttribute(node, attrib);
586 }
587 }
588
589 if (null == column)
590 {
591 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Column"));
592 column = String.Empty;
593 }
594
595 if (null == table)
596 {
597 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Table"));
598 table = String.Empty;
599 }
600
601 if (null == rowKeys)
602 {
603 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Row"));
604 }
605
606 this.Core.ParseForExtensionElements(node);
607
608 if (!this.Core.EncounteredError)
609 {
610 this.Core.AddSymbol(new ModuleSubstitutionSymbol(sourceLineNumbers)
611 {
612 Table = table,
613 Row = rowKeys,
614 Column = column,
615 Value = value
616 });
617 }
618 }
619
620 /// <summary>
621 /// Parses an IgnoreTable element.
622 /// </summary>
623 /// <param name="node">Element to parse.</param>
624 private void ParseIgnoreTableElement(XElement node)
625 {
626 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
627 string id = null;
628
629 foreach (var attrib in node.Attributes())
630 {
631 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
632 {
633 switch (attrib.Name.LocalName)
634 {
635 case "Id":
636 id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
637 break;
638 default:
639 this.Core.UnexpectedAttribute(node, attrib);
640 break;
641 }
642 }
643 else
644 {
645 this.Core.ParseExtensionAttribute(node, attrib);
646 }
647 }
648
649 if (null == id)
650 {
651 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
652 }
653
654 this.Core.ParseForExtensionElements(node);
655
656 if (!this.Core.EncounteredError)
657 {
658 this.Core.AddSymbol(new ModuleIgnoreTableSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, id)));
659 }
660 }
661 }
662}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System;
6 using System.Collections;
7 using System.Collections.Generic;
8 using System.Globalization;
9 using System.IO;
10 using System.Xml.Linq;
11 using WixToolset.Data;
12 using WixToolset.Data.Symbols;
13 using WixToolset.Data.WindowsInstaller;
14 using WixToolset.Extensibility;
15
16 /// <summary>
17 /// Compiler of the WiX toolset.
18 /// </summary>
19 internal partial class Compiler : ICompiler
20 {
21 /// <summary>
22 /// Parses a product element.
23 /// </summary>
24 /// <param name="node">Element to parse.</param>
25 private void ParsePackageElement(XElement node)
26 {
27 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
28 var compressed = YesNoDefaultType.Default;
29 var sourceBits = 0;
30 string codepage = null;
31 var productCode = "*";
32 string productLanguage = null;
33 var isPerMachine = true;
34 string upgradeCode = null;
35 string manufacturer = null;
36 string version = null;
37 string symbols = null;
38 var isCodepageSet = false;
39 var isPackageNameSet = false;
40 var isKeywordsSet = false;
41 var isPackageAuthorSet = false;
42
43 this.GetDefaultPlatformAndInstallerVersion(out var platform, out var msiVersion);
44
45 this.activeName = null;
46 this.activeLanguage = null;
47
48 foreach (var attrib in node.Attributes())
49 {
50 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
51 {
52 switch (attrib.Name.LocalName)
53 {
54 case "Codepage":
55 codepage = this.Core.GetAttributeLocalizableCodePageValue(sourceLineNumbers, attrib);
56 break;
57 case "Compressed":
58 compressed = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib);
59 break;
60 case "InstallerVersion":
61 msiVersion = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue);
62 break;
63 case "Language":
64 productLanguage = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue);
65 break;
66 case "Manufacturer":
67 manufacturer = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.MustHaveNonWhitespaceCharacters);
68 if ("PUT-COMPANY-NAME-HERE" == manufacturer)
69 {
70 this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, manufacturer));
71 }
72 break;
73 case "Name":
74 this.activeName = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.MustHaveNonWhitespaceCharacters);
75 if ("PUT-PRODUCT-NAME-HERE" == this.activeName)
76 {
77 this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, this.activeName));
78 }
79 break;
80 case "ProductCode":
81 productCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, true);
82 break;
83 case "Scope":
84 var installScope = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
85 switch (installScope)
86 {
87 case "perMachine":
88 // handled below after we create the section.
89 break;
90 case "perUser":
91 isPerMachine = false;
92 sourceBits |= 8;
93 break;
94 default:
95 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, installScope, "perMachine", "perUser"));
96 break;
97 }
98 break;
99 case "ShortNames":
100 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
101 {
102 sourceBits |= 1;
103 }
104 break;
105 case "UpgradeCode":
106 upgradeCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
107 break;
108 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").
109 var verifiedVersion = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib);
110 if (!String.IsNullOrEmpty(verifiedVersion))
111 {
112 version = attrib.Value;
113 }
114 break;
115 default:
116 this.Core.UnexpectedAttribute(node, attrib);
117 break;
118 }
119 }
120 else
121 {
122 this.Core.ParseExtensionAttribute(node, attrib);
123 }
124 }
125
126 if (null == productCode)
127 {
128 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
129 }
130
131 if (null == manufacturer)
132 {
133 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Manufacturer"));
134 }
135
136 if (null == this.activeName)
137 {
138 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
139 }
140
141 if (null == upgradeCode)
142 {
143 this.Core.Write(WarningMessages.MissingUpgradeCode(sourceLineNumbers));
144 }
145
146 if (null == version)
147 {
148 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version"));
149 }
150 else if (!CompilerCore.IsValidProductVersion(version))
151 {
152 this.Core.Write(ErrorMessages.InvalidProductVersion(sourceLineNumbers, version));
153 }
154
155 if (compressed != YesNoDefaultType.No)
156 {
157 sourceBits |= 2;
158 }
159
160 if (this.Core.EncounteredError)
161 {
162 return;
163 }
164
165 try
166 {
167 this.compilingProduct = true;
168 this.Core.CreateActiveSection(productCode, SectionType.Product, this.Context.CompilationId);
169
170 this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "Manufacturer"), manufacturer, false, false, false, true);
171 this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductCode"), productCode, false, false, false, true);
172 this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductLanguage"), productLanguage, false, false, false, true);
173 this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductName"), this.activeName, false, false, false, true);
174 this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ProductVersion"), version, false, false, false, true);
175 if (null != upgradeCode)
176 {
177 this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "UpgradeCode"), upgradeCode, false, false, false, true);
178 }
179
180 if (isPerMachine)
181 {
182 this.AddProperty(sourceLineNumbers, new Identifier(AccessModifier.Global, "ALLUSERS"), "1", false, false, false, false);
183 }
184
185 this.ValidateAndAddCommonSummaryInformationSymbols(sourceLineNumbers, msiVersion, platform, productLanguage);
186
187 this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers)
188 {
189 PropertyId = SummaryInformationType.WordCount,
190 Value = sourceBits.ToString(CultureInfo.InvariantCulture)
191 });
192
193 var contextValues = new Dictionary<string, string>
194 {
195 ["ProductLanguage"] = productLanguage,
196 ["ProductVersion"] = version,
197 ["UpgradeCode"] = upgradeCode
198 };
199
200 var featureDisplay = 0;
201 foreach (var child in node.Elements())
202 {
203 if (CompilerCore.WixNamespace == child.Name.Namespace)
204 {
205 switch (child.Name.LocalName)
206 {
207 case "_locDefinition":
208 break;
209 case "AdminExecuteSequence":
210 this.ParseSequenceElement(child, SequenceTable.AdminExecuteSequence);
211 break;
212 case "AdminUISequence":
213 this.ParseSequenceElement(child, SequenceTable.AdminUISequence);
214 break;
215 case "AdvertiseExecuteSequence":
216 this.ParseSequenceElement(child, SequenceTable.AdvertiseExecuteSequence);
217 break;
218 case "InstallExecuteSequence":
219 this.ParseSequenceElement(child, SequenceTable.InstallExecuteSequence);
220 break;
221 case "InstallUISequence":
222 this.ParseSequenceElement(child, SequenceTable.InstallUISequence);
223 break;
224 case "AppId":
225 this.ParseAppIdElement(child, null, YesNoType.Yes, null, null, null);
226 break;
227 case "Binary":
228 this.ParseBinaryElement(child);
229 break;
230 case "ComplianceCheck":
231 this.ParseComplianceCheckElement(child);
232 break;
233 case "Component":
234 this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, CompilerConstants.IntegerNotSet, null, null);
235 break;
236 case "ComponentGroup":
237 this.ParseComponentGroupElement(child, ComplexReferenceParentType.Unknown, null);
238 break;
239 case "CustomAction":
240 this.ParseCustomActionElement(child);
241 break;
242 case "CustomActionRef":
243 this.ParseSimpleRefElement(child, SymbolDefinitions.CustomAction);
244 break;
245 case "CustomTable":
246 this.ParseCustomTableElement(child);
247 break;
248 case "CustomTableRef":
249 this.ParseCustomTableRefElement(child);
250 break;
251 case "Directory":
252 this.ParseDirectoryElement(child, null, CompilerConstants.IntegerNotSet, String.Empty);
253 break;
254 case "DirectoryRef":
255 this.ParseDirectoryRefElement(child);
256 break;
257 case "EmbeddedChainer":
258 this.ParseEmbeddedChainerElement(child);
259 break;
260 case "EmbeddedChainerRef":
261 this.ParseSimpleRefElement(child, SymbolDefinitions.MsiEmbeddedChainer);
262 break;
263 case "EnsureTable":
264 this.ParseEnsureTableElement(child);
265 break;
266 case "Feature":
267 this.ParseFeatureElement(child, ComplexReferenceParentType.Product, productCode, ref featureDisplay);
268 break;
269 case "FeatureRef":
270 this.ParseFeatureRefElement(child, ComplexReferenceParentType.Product, productCode);
271 break;
272 case "FeatureGroupRef":
273 this.ParseFeatureGroupRefElement(child, ComplexReferenceParentType.Product, productCode);
274 break;
275 case "Icon":
276 this.ParseIconElement(child);
277 break;
278 case "InstanceTransforms":
279 this.ParseInstanceTransformsElement(child);
280 break;
281 case "Launch":
282 this.ParseLaunchElement(child);
283 break;
284 case "MajorUpgrade":
285 this.ParseMajorUpgradeElement(child, contextValues);
286 break;
287 case "Media":
288 this.ParseMediaElement(child, null);
289 break;
290 case "MediaTemplate":
291 this.ParseMediaTemplateElement(child, null);
292 break;
293 case "PackageCertificates":
294 case "PatchCertificates":
295 this.ParseCertificatesElement(child);
296 break;
297 case "Property":
298 this.ParsePropertyElement(child);
299 break;
300 case "PropertyRef":
301 this.ParseSimpleRefElement(child, SymbolDefinitions.Property);
302 break;
303 case "Requires":
304 this.ParseRequiresElement(child, null);
305 break;
306 case "SetDirectory":
307 this.ParseSetDirectoryElement(child);
308 break;
309 case "SetProperty":
310 this.ParseSetPropertyElement(child);
311 break;
312 case "SFPCatalog":
313 string parentName = null;
314 this.ParseSFPCatalogElement(child, ref parentName);
315 break;
316 case "SoftwareTag":
317 this.ParsePackageTagElement(child);
318 break;
319 case "StandardDirectory":
320 this.ParseStandardDirectoryElement(child);
321 break;
322 case "SummaryInformation":
323 this.ParseSummaryInformationElement(child, ref isCodepageSet, ref isPackageNameSet, ref isKeywordsSet, ref isPackageAuthorSet);
324 break;
325 case "SymbolPath":
326 if (null != symbols)
327 {
328 symbols += ";" + this.ParseSymbolPathElement(child);
329 }
330 else
331 {
332 symbols = this.ParseSymbolPathElement(child);
333 }
334 break;
335 case "UI":
336 this.ParseUIElement(child);
337 break;
338 case "UIRef":
339 this.ParseSimpleRefElement(child, SymbolDefinitions.WixUI);
340 break;
341 case "Upgrade":
342 this.ParseUpgradeElement(child);
343 break;
344 case "WixVariable":
345 this.ParseWixVariableElement(child);
346 break;
347 default:
348 this.Core.UnexpectedElement(node, child);
349 break;
350 }
351 }
352 else
353 {
354 this.Core.ParseExtensionElement(node, child);
355 }
356 }
357
358 if (!this.Core.EncounteredError)
359 {
360 this.Core.AddSymbol(new WixPackageSymbol(sourceLineNumbers)
361 {
362 PackageId = productCode,
363 UpgradeCode = upgradeCode,
364 Name = this.activeName,
365 Language = productLanguage,
366 Version = version,
367 Manufacturer = manufacturer,
368 Attributes = isPerMachine ? WixPackageAttributes.PerMachine : WixPackageAttributes.None,
369 Codepage = codepage,
370 });
371
372 if (!isPackageNameSet)
373 {
374 this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers)
375 {
376 PropertyId = SummaryInformationType.Subject,
377 Value = this.activeName
378 });
379 }
380
381 if (!isPackageAuthorSet)
382 {
383 this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers)
384 {
385 PropertyId = SummaryInformationType.Author,
386 Value = manufacturer
387 });
388 }
389
390 if (!isKeywordsSet)
391 {
392 this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers)
393 {
394 PropertyId = SummaryInformationType.Keywords,
395 Value = "Installer"
396 });
397 }
398
399 if (null != symbols)
400 {
401 this.Core.AddSymbol(new WixDeltaPatchSymbolPathsSymbol(sourceLineNumbers)
402 {
403 SymbolId = productCode,
404 SymbolType = SymbolPathType.Product,
405 SymbolPaths = symbols,
406 });
407 }
408 }
409 }
410 finally
411 {
412 this.compilingProduct = false;
413 }
414 }
415
416 private void GetDefaultPlatformAndInstallerVersion(out string platform, out int msiVersion)
417 {
418 // Let's default to a modern version of MSI. Users can override,
419 // of course, subject to platform-specific limitations.
420 msiVersion = 500;
421
422 switch (this.CurrentPlatform)
423 {
424 case Platform.X86:
425 platform = "Intel";
426 break;
427 case Platform.X64:
428 platform = "x64";
429 break;
430 case Platform.ARM64:
431 platform = "Arm64";
432 break;
433 default:
434 throw new ArgumentException("Unknown platform enumeration '{0}' encountered.", this.CurrentPlatform.ToString());
435 }
436 }
437
438 private void ValidateAndAddCommonSummaryInformationSymbols(SourceLineNumber sourceLineNumbers, int msiVersion, string platform, string language)
439 {
440 if (String.Equals(platform, "X64", StringComparison.OrdinalIgnoreCase) && 200 > msiVersion)
441 {
442 msiVersion = 200;
443 this.Core.Write(WarningMessages.RequiresMsi200for64bitPackage(sourceLineNumbers));
444 }
445
446 if (String.Equals(platform, "Arm64", StringComparison.OrdinalIgnoreCase) && 500 > msiVersion)
447 {
448 msiVersion = 500;
449 this.Core.Write(WarningMessages.RequiresMsi500forArmPackage(sourceLineNumbers));
450 }
451
452 this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers)
453 {
454 PropertyId = SummaryInformationType.Comments,
455 Value = String.Format(CultureInfo.InvariantCulture, "This installer database contains the logic and data required to install {0}.", this.activeName)
456 });
457
458 this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers)
459 {
460 PropertyId = SummaryInformationType.Title,
461 Value = "Installation Database"
462 });
463
464 this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers)
465 {
466 PropertyId = SummaryInformationType.PlatformAndLanguage,
467 Value = $"{platform};{language}"
468 });
469
470 this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers)
471 {
472 PropertyId = SummaryInformationType.WindowsInstallerVersion,
473 Value = msiVersion.ToString(CultureInfo.InvariantCulture)
474 });
475
476 this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers)
477 {
478 PropertyId = SummaryInformationType.Security,
479 Value = "2"
480 });
481 }
482
483 /// <summary>
484 /// Parses an odbc driver or translator element.
485 /// </summary>
486 /// <param name="node">Element to parse.</param>
487 /// <param name="componentId">Identifier of parent component.</param>
488 /// <param name="fileId">Default identifer for driver/translator file.</param>
489 /// <param name="symbolDefinitionType">Symbol type we're processing for.</param>
490 private void ParseODBCDriverOrTranslator(XElement node, string componentId, string fileId, SymbolDefinitionType symbolDefinitionType)
491 {
492 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
493 Identifier id = null;
494 var driver = fileId;
495 string name = null;
496 var setup = fileId;
497
498 foreach (var attrib in node.Attributes())
499 {
500 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
501 {
502 switch (attrib.Name.LocalName)
503 {
504 case "Id":
505 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
506 break;
507 case "File":
508 driver = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
509 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, driver);
510 break;
511 case "Name":
512 name = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
513 break;
514 case "SetupFile":
515 setup = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
516 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, setup);
517 break;
518 default:
519 this.Core.UnexpectedAttribute(node, attrib);
520 break;
521 }
522 }
523 else
524 {
525 this.Core.ParseExtensionAttribute(node, attrib);
526 }
527 }
528
529 if (null == name)
530 {
531 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
532 }
533
534 if (null == id)
535 {
536 id = this.Core.CreateIdentifier("odb", name, fileId, setup);
537 }
538
539 // drivers have a few possible children
540 if (SymbolDefinitionType.ODBCDriver == symbolDefinitionType)
541 {
542 // process any data sources for the driver
543 foreach (var child in node.Elements())
544 {
545 if (CompilerCore.WixNamespace == child.Name.Namespace)
546 {
547 switch (child.Name.LocalName)
548 {
549 case "ODBCDataSource":
550 string ignoredKeyPath = null;
551 this.ParseODBCDataSource(child, componentId, name, out ignoredKeyPath);
552 break;
553 case "Property":
554 this.ParseODBCProperty(child, id.Id, SymbolDefinitionType.ODBCAttribute);
555 break;
556 default:
557 this.Core.UnexpectedElement(node, child);
558 break;
559 }
560 }
561 else
562 {
563 this.Core.ParseExtensionElement(node, child);
564 }
565 }
566 }
567 else
568 {
569 this.Core.ParseForExtensionElements(node);
570 }
571
572 if (!this.Core.EncounteredError)
573 {
574 switch (symbolDefinitionType)
575 {
576 case SymbolDefinitionType.ODBCDriver:
577 this.Core.AddSymbol(new ODBCDriverSymbol(sourceLineNumbers, id)
578 {
579 ComponentRef = componentId,
580 Description = name,
581 FileRef = driver,
582 SetupFileRef = setup,
583 });
584 break;
585 case SymbolDefinitionType.ODBCTranslator:
586 this.Core.AddSymbol(new ODBCTranslatorSymbol(sourceLineNumbers, id)
587 {
588 ComponentRef = componentId,
589 Description = name,
590 FileRef = driver,
591 SetupFileRef = setup,
592 });
593 break;
594 default:
595 throw new ArgumentOutOfRangeException(nameof(symbolDefinitionType));
596 }
597 }
598 }
599
600 /// <summary>
601 /// Parses a Property element underneath an ODBC driver or translator.
602 /// </summary>
603 /// <param name="node">Element to parse.</param>
604 /// <param name="parentId">Identifier of parent driver or translator.</param>
605 /// <param name="symbolDefinitionType">Name of the table to create property in.</param>
606 private void ParseODBCProperty(XElement node, string parentId, SymbolDefinitionType symbolDefinitionType)
607 {
608 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
609 string id = null;
610 string propertyValue = null;
611
612 foreach (var attrib in node.Attributes())
613 {
614 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
615 {
616 switch (attrib.Name.LocalName)
617 {
618 case "Id":
619 id = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
620 break;
621 case "Value":
622 propertyValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
623 break;
624 default:
625 this.Core.UnexpectedAttribute(node, attrib);
626 break;
627 }
628 }
629 else
630 {
631 this.Core.ParseExtensionAttribute(node, attrib);
632 }
633 }
634
635 if (null == id)
636 {
637 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
638 }
639
640 this.Core.ParseForExtensionElements(node);
641
642 if (!this.Core.EncounteredError)
643 {
644 var identifier = new Identifier(AccessModifier.Section, parentId, id);
645 switch (symbolDefinitionType)
646 {
647 case SymbolDefinitionType.ODBCAttribute:
648 this.Core.AddSymbol(new ODBCAttributeSymbol(sourceLineNumbers, identifier)
649 {
650 DriverRef = parentId,
651 Attribute = id,
652 Value = propertyValue,
653 });
654 break;
655 case SymbolDefinitionType.ODBCSourceAttribute:
656 this.Core.AddSymbol(new ODBCSourceAttributeSymbol(sourceLineNumbers, identifier)
657 {
658 DataSourceRef = parentId,
659 Attribute = id,
660 Value = propertyValue,
661 });
662 break;
663 default:
664 throw new ArgumentOutOfRangeException(nameof(symbolDefinitionType));
665 }
666 }
667 }
668
669 /// <summary>
670 /// Parse an odbc data source element.
671 /// </summary>
672 /// <param name="node">Element to parse.</param>
673 /// <param name="componentId">Identifier of parent component.</param>
674 /// <param name="driverName">Default name of driver.</param>
675 /// <param name="possibleKeyPath">Identifier of this element in case it is a keypath.</param>
676 /// <returns>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.</returns>
677 private YesNoType ParseODBCDataSource(XElement node, string componentId, string driverName, out string possibleKeyPath)
678 {
679 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
680 Identifier id = null;
681 var keyPath = YesNoType.NotSet;
682 string name = null;
683 var registration = CompilerConstants.IntegerNotSet;
684
685 foreach (var attrib in node.Attributes())
686 {
687 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
688 {
689 switch (attrib.Name.LocalName)
690 {
691 case "Id":
692 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
693 break;
694 case "DriverName":
695 driverName = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
696 break;
697 case "KeyPath":
698 keyPath = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
699 break;
700 case "Name":
701 name = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
702 break;
703 case "Registration":
704 var registrationValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
705 switch (registrationValue)
706 {
707 case "machine":
708 registration = 0;
709 break;
710 case "user":
711 registration = 1;
712 break;
713 case "":
714 break;
715 default:
716 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Registration", registrationValue, "machine", "user"));
717 break;
718 }
719 break;
720 default:
721 this.Core.UnexpectedAttribute(node, attrib);
722 break;
723 }
724 }
725 else
726 {
727 this.Core.ParseExtensionAttribute(node, attrib);
728 }
729 }
730
731 if (CompilerConstants.IntegerNotSet == registration)
732 {
733 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Registration"));
734 registration = CompilerConstants.IllegalInteger;
735 }
736
737 if (null == id)
738 {
739 id = this.Core.CreateIdentifier("odc", name, driverName, registration.ToString());
740 }
741
742 foreach (var child in node.Elements())
743 {
744 if (CompilerCore.WixNamespace == child.Name.Namespace)
745 {
746 switch (child.Name.LocalName)
747 {
748 case "Property":
749 this.ParseODBCProperty(child, id.Id, SymbolDefinitionType.ODBCSourceAttribute);
750 break;
751 default:
752 this.Core.UnexpectedElement(node, child);
753 break;
754 }
755 }
756 else
757 {
758 this.Core.ParseExtensionElement(node, child);
759 }
760 }
761
762 if (!this.Core.EncounteredError)
763 {
764 this.Core.AddSymbol(new ODBCDataSourceSymbol(sourceLineNumbers, id)
765 {
766 ComponentRef = componentId,
767 Description = name,
768 DriverDescription = driverName,
769 Registration = registration
770 });
771 }
772
773 possibleKeyPath = id.Id;
774 return keyPath;
775 }
776
777 /// <summary>
778 /// Parses a package element.
779 /// </summary>
780 /// <param name="node">Element to parse.</param>
781 /// <param name="isCodepageSet"></param>
782 /// <param name="isPackageNameSet"></param>
783 /// <param name="isKeywordsSet"></param>
784 /// <param name="isPackageAuthorSet"></param>
785 private void ParseSummaryInformationElement(XElement node, ref bool isCodepageSet, ref bool isPackageNameSet, ref bool isKeywordsSet, ref bool isPackageAuthorSet)
786 {
787 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
788 string codepage = null;
789 string packageName = null;
790 string keywords = null;
791 string packageAuthor = null;
792
793 foreach (var attrib in node.Attributes())
794 {
795 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
796 {
797 switch (attrib.Name.LocalName)
798 {
799 case "Codepage":
800 codepage = this.Core.GetAttributeLocalizableCodePageValue(sourceLineNumbers, attrib, true);
801 break;
802 case "Description":
803 packageName = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
804 break;
805 case "Keywords":
806 keywords = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
807 break;
808 case "Manufacturer":
809 packageAuthor = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
810 if ("PUT-COMPANY-NAME-HERE" == packageAuthor)
811 {
812 this.Core.Write(WarningMessages.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, packageAuthor));
813 }
814 break;
815 default:
816 this.Core.UnexpectedAttribute(node, attrib);
817 break;
818 }
819 }
820 else
821 {
822 this.Core.ParseExtensionAttribute(node, attrib);
823 }
824 }
825
826 this.Core.ParseForExtensionElements(node);
827
828 if (!this.Core.EncounteredError)
829 {
830 if (null != codepage)
831 {
832 isCodepageSet = true;
833 this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers)
834 {
835 PropertyId = SummaryInformationType.Codepage,
836 Value = codepage
837 });
838 }
839
840 if (null != packageName)
841 {
842 isPackageNameSet = true;
843 this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers)
844 {
845 PropertyId = SummaryInformationType.Subject,
846 Value = packageName
847 });
848 }
849
850 if (null != packageAuthor)
851 {
852 isPackageAuthorSet = true;
853 this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers)
854 {
855 PropertyId = SummaryInformationType.Author,
856 Value = packageAuthor
857 });
858 }
859
860 if (null != keywords)
861 {
862 isKeywordsSet = true;
863 this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers)
864 {
865 PropertyId = SummaryInformationType.Keywords,
866 Value = keywords
867 });
868 }
869 }
870 }
871
872 /// <summary>
873 /// Parses a patch information element.
874 /// </summary>
875 /// <param name="node">Element to parse.</param>
876 private void ParsePatchInformationElement(XElement node)
877 {
878 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
879 var codepage = "1252";
880 string comments = null;
881 var keywords = "Installer,Patching,PCP,Database";
882 var msiVersion = 1; // Should always be 1 for patches
883 string packageAuthor = null;
884 var packageName = this.activeName;
885 var security = YesNoDefaultType.Default;
886
887 foreach (var attrib in node.Attributes())
888 {
889 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
890 {
891 switch (attrib.Name.LocalName)
892 {
893 case "AdminImage":
894 this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName));
895 break;
896 case "Comments":
897 comments = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
898 break;
899 case "Compressed":
900 this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName));
901 break;
902 case "Description":
903 packageName = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
904 break;
905 case "Keywords":
906 keywords = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
907 break;
908 case "Languages":
909 this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName));
910 break;
911 case "Manufacturer":
912 packageAuthor = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
913 break;
914 case "Platforms":
915 this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName));
916 break;
917 case "ReadOnly":
918 security = this.Core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib);
919 break;
920 case "ShortNames":
921 this.Core.Write(WarningMessages.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName));
922 break;
923 case "SummaryCodepage":
924 codepage = this.Core.GetAttributeLocalizableCodePageValue(sourceLineNumbers, attrib);
925 break;
926 default:
927 this.Core.UnexpectedAttribute(node, attrib);
928 break;
929 }
930 }
931 else
932 {
933 this.Core.ParseExtensionAttribute(node, attrib);
934 }
935 }
936
937 this.Core.ParseForExtensionElements(node);
938
939 if (!this.Core.EncounteredError)
940 {
941 this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers)
942 {
943 PropertyId = SummaryInformationType.Codepage,
944 Value = codepage
945 });
946
947 this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers)
948 {
949 PropertyId = SummaryInformationType.Title,
950 Value = "Patch"
951 });
952
953 if (null != packageName)
954 {
955 this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers)
956 {
957 PropertyId = SummaryInformationType.Subject,
958 Value = packageName
959 });
960 }
961
962 if (null != packageAuthor)
963 {
964 this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers)
965 {
966 PropertyId = SummaryInformationType.Author,
967 Value = packageAuthor
968 });
969 }
970
971 if (null != keywords)
972 {
973 this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers)
974 {
975 PropertyId = SummaryInformationType.Keywords,
976 Value = keywords
977 });
978 }
979
980 if (null != comments)
981 {
982 this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers)
983 {
984 PropertyId = SummaryInformationType.Comments,
985 Value = comments
986 });
987 }
988
989 this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers)
990 {
991 PropertyId = SummaryInformationType.WindowsInstallerVersion,
992 Value = msiVersion.ToString(CultureInfo.InvariantCulture)
993 });
994
995 this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers)
996 {
997 PropertyId = SummaryInformationType.WordCount,
998 Value = "0"
999 });
1000
1001 this.Core.AddSymbol(new SummaryInformationSymbol(sourceLineNumbers)
1002 {
1003 PropertyId = SummaryInformationType.Security,
1004 Value = YesNoDefaultType.No == security ? "0" : YesNoDefaultType.Yes == security ? "4" : "2"
1005 });
1006 }
1007 }
1008
1009 /// <summary>
1010 /// Parses a permission element.
1011 /// </summary>
1012 /// <param name="node">Element to parse.</param>
1013 /// <param name="objectId">Identifier of object to be secured.</param>
1014 /// <param name="tableName">Name of table that contains objectId.</param>
1015 private void ParsePermissionElement(XElement node, string objectId, string tableName)
1016 {
1017 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1018 var bits = new BitArray(32);
1019 string domain = null;
1020 string[] specialPermissions = null;
1021 string user = null;
1022
1023 switch (tableName)
1024 {
1025 case "CreateFolder":
1026 specialPermissions = LockPermissionConstants.FolderPermissions;
1027 break;
1028 case "File":
1029 specialPermissions = LockPermissionConstants.FilePermissions;
1030 break;
1031 case "Registry":
1032 specialPermissions = LockPermissionConstants.RegistryPermissions;
1033 break;
1034 default:
1035 this.Core.UnexpectedElement(node.Parent, node);
1036 return; // stop processing this element since no valid permissions are available
1037 }
1038
1039 foreach (var attrib in node.Attributes())
1040 {
1041 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
1042 {
1043 switch (attrib.Name.LocalName)
1044 {
1045 case "Domain":
1046 domain = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1047 break;
1048 case "User":
1049 user = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1050 break;
1051 case "FileAllRights":
1052 // match the WinNT.h mask FILE_ALL_ACCESS for value 0x001F01FF (aka 1 1111 0000 0001 1111 1111 or 2032127)
1053 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;
1054 break;
1055 case "SpecificRightsAll":
1056 // match the WinNT.h mask SPECIFIC_RIGHTS_ALL for value 0x0000FFFF (aka 1111 1111 1111 1111)
1057 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;
1058 break;
1059 default:
1060 var attribValue = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1061 if (!this.Core.TrySetBitFromName(LockPermissionConstants.StandardPermissions, attrib.Name.LocalName, attribValue, bits, 16))
1062 {
1063 if (!this.Core.TrySetBitFromName(LockPermissionConstants.GenericPermissions, attrib.Name.LocalName, attribValue, bits, 28))
1064 {
1065 if (!this.Core.TrySetBitFromName(specialPermissions, attrib.Name.LocalName, attribValue, bits, 0))
1066 {
1067 this.Core.UnexpectedAttribute(node, attrib);
1068 break;
1069 }
1070 }
1071 }
1072 break;
1073 }
1074 }
1075 else
1076 {
1077 this.Core.ParseExtensionAttribute(node, attrib);
1078 }
1079 }
1080
1081 if (null == user)
1082 {
1083 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "User"));
1084 }
1085
1086 var permission = this.Core.CreateIntegerFromBitArray(bits);
1087
1088 if (Int32.MinValue == permission) // just GENERIC_READ, which is MSI_NULL
1089 {
1090 this.Core.Write(ErrorMessages.GenericReadNotAllowed(sourceLineNumbers));
1091 }
1092
1093 this.Core.ParseForExtensionElements(node);
1094
1095 if (!this.Core.EncounteredError)
1096 {
1097 this.Core.AddSymbol(new LockPermissionsSymbol(sourceLineNumbers)
1098 {
1099 LockObject = objectId,
1100 Table = tableName,
1101 Domain = domain,
1102 User = user,
1103 Permission = permission
1104 });
1105 }
1106 }
1107
1108 /// <summary>
1109 /// Parses an extended permission element.
1110 /// </summary>
1111 /// <param name="node">Element to parse.</param>
1112 /// <param name="objectId">Identifier of object to be secured.</param>
1113 /// <param name="tableName">Name of table that contains objectId.</param>
1114 private void ParsePermissionExElement(XElement node, string objectId, string tableName)
1115 {
1116 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1117 string condition = null;
1118 Identifier id = null;
1119 string sddl = null;
1120
1121 switch (tableName)
1122 {
1123 case "CreateFolder":
1124 case "File":
1125 case "Registry":
1126 case "ServiceInstall":
1127 break;
1128 default:
1129 this.Core.UnexpectedElement(node.Parent, node);
1130 return; // stop processing this element since nothing will be valid.
1131 }
1132
1133 foreach (var attrib in node.Attributes())
1134 {
1135 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
1136 {
1137 switch (attrib.Name.LocalName)
1138 {
1139 case "Id":
1140 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
1141 break;
1142 case "Condition":
1143 condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1144 break;
1145 case "Sddl":
1146 sddl = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1147 break;
1148 default:
1149 this.Core.UnexpectedAttribute(node, attrib);
1150 break;
1151 }
1152 }
1153 else
1154 {
1155 this.Core.ParseExtensionAttribute(node, attrib);
1156 }
1157 }
1158
1159 if (null == sddl)
1160 {
1161 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Sddl"));
1162 }
1163
1164 if (null == id)
1165 {
1166 id = this.Core.CreateIdentifier("pme", objectId, tableName, sddl);
1167 }
1168
1169 this.Core.ParseForExtensionElements(node);
1170
1171 if (!this.Core.EncounteredError)
1172 {
1173 this.Core.AddSymbol(new MsiLockPermissionsExSymbol(sourceLineNumbers, id)
1174 {
1175 LockObject = objectId,
1176 Table = tableName,
1177 SDDLText = sddl,
1178 Condition = condition
1179 });
1180 }
1181 }
1182
1183 /// <summary>
1184 /// Parses a progid element
1185 /// </summary>
1186 /// <param name="node">Element to parse.</param>
1187 /// <param name="componentId">Identifier of parent component.</param>
1188 /// <param name="advertise">Flag if progid is advertised.</param>
1189 /// <param name="classId">CLSID related to ProgId.</param>
1190 /// <param name="description">Default description of ProgId</param>
1191 /// <param name="parent">Optional parent ProgId</param>
1192 /// <param name="foundExtension">Set to true if an extension is found; used for error-checking.</param>
1193 /// <param name="firstProgIdForClass">Whether or not this ProgId is the first one found in the parent class.</param>
1194 /// <returns>This element's Id.</returns>
1195 private string ParseProgIdElement(XElement node, string componentId, YesNoType advertise, string classId, string description, string parent, ref bool foundExtension, YesNoType firstProgIdForClass)
1196 {
1197 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1198 string icon = null;
1199 var iconIndex = CompilerConstants.IntegerNotSet;
1200 string noOpen = null;
1201 string progId = null;
1202 var progIdAdvertise = YesNoType.NotSet;
1203
1204 foreach (var attrib in node.Attributes())
1205 {
1206 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
1207 {
1208 switch (attrib.Name.LocalName)
1209 {
1210 case "Id":
1211 progId = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1212 break;
1213 case "Advertise":
1214 progIdAdvertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1215 break;
1216 case "Description":
1217 description = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty);
1218 break;
1219 case "Icon":
1220 icon = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
1221 break;
1222 case "IconIndex":
1223 iconIndex = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int16.MinValue + 1, Int16.MaxValue);
1224 break;
1225 case "NoOpen":
1226 noOpen = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty);
1227 break;
1228 default:
1229 this.Core.UnexpectedAttribute(node, attrib);
1230 break;
1231 }
1232 }
1233 else
1234 {
1235 this.Core.ParseExtensionAttribute(node, attrib);
1236 }
1237 }
1238
1239 if ((YesNoType.No == advertise && YesNoType.Yes == progIdAdvertise) || (YesNoType.Yes == advertise && YesNoType.No == progIdAdvertise))
1240 {
1241 this.Core.Write(ErrorMessages.AdvertiseStateMustMatch(sourceLineNumbers, advertise.ToString(), progIdAdvertise.ToString()));
1242 }
1243 else if (YesNoType.NotSet != progIdAdvertise)
1244 {
1245 advertise = progIdAdvertise;
1246 }
1247
1248 if (YesNoType.NotSet == advertise)
1249 {
1250 advertise = YesNoType.No;
1251 }
1252
1253 if (null != parent && (null != icon || CompilerConstants.IntegerNotSet != iconIndex))
1254 {
1255 this.Core.Write(ErrorMessages.VersionIndependentProgIdsCannotHaveIcons(sourceLineNumbers));
1256 }
1257
1258 var firstProgIdForNestedClass = YesNoType.Yes;
1259 foreach (var child in node.Elements())
1260 {
1261 if (CompilerCore.WixNamespace == child.Name.Namespace)
1262 {
1263 switch (child.Name.LocalName)
1264 {
1265 case "Extension":
1266 this.ParseExtensionElement(child, componentId, advertise, progId);
1267 foundExtension = true;
1268 break;
1269 case "ProgId":
1270 // Only allow one nested ProgId. If we have a child, we should not have a parent.
1271 if (null == parent)
1272 {
1273 if (YesNoType.Yes == advertise)
1274 {
1275 this.ParseProgIdElement(child, componentId, advertise, null, description, progId, ref foundExtension, firstProgIdForNestedClass);
1276 }
1277 else if (YesNoType.No == advertise)
1278 {
1279 this.ParseProgIdElement(child, componentId, advertise, classId, description, progId, ref foundExtension, firstProgIdForNestedClass);
1280 }
1281
1282 firstProgIdForNestedClass = YesNoType.No; // any ProgId after this one is definitely not the first.
1283 }
1284 else
1285 {
1286 var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
1287 this.Core.Write(ErrorMessages.ProgIdNestedTooDeep(childSourceLineNumbers));
1288 }
1289 break;
1290 default:
1291 this.Core.UnexpectedElement(node, child);
1292 break;
1293 }
1294 }
1295 else
1296 {
1297 this.Core.ParseExtensionElement(node, child);
1298 }
1299 }
1300
1301 if (YesNoType.Yes == advertise)
1302 {
1303 if (!this.Core.EncounteredError)
1304 {
1305 var symbol = this.Core.AddSymbol(new ProgIdSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, progId))
1306 {
1307 ProgId = progId,
1308 ParentProgIdRef = parent,
1309 ClassRef = classId,
1310 Description = description,
1311 });
1312
1313 if (null != icon)
1314 {
1315 symbol.IconRef = icon;
1316 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Icon, icon);
1317 }
1318
1319 if (CompilerConstants.IntegerNotSet != iconIndex)
1320 {
1321 symbol.IconIndex = iconIndex;
1322 }
1323
1324 this.Core.EnsureTable(sourceLineNumbers, WindowsInstallerTableDefinitions.Class);
1325 }
1326 }
1327 else if (YesNoType.No == advertise)
1328 {
1329 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, progId, String.Empty, description, componentId);
1330 if (null != classId)
1331 {
1332 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(progId, "\\CLSID"), String.Empty, classId, componentId);
1333 if (null != parent) // if this is a version independent ProgId
1334 {
1335 if (YesNoType.Yes == firstProgIdForClass)
1336 {
1337 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\VersionIndependentProgID"), String.Empty, progId, componentId);
1338 }
1339
1340 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(progId, "\\CurVer"), String.Empty, parent, componentId);
1341 }
1342 else
1343 {
1344 if (YesNoType.Yes == firstProgIdForClass)
1345 {
1346 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat("CLSID\\", classId, "\\ProgID"), String.Empty, progId, componentId);
1347 }
1348 }
1349 }
1350
1351 if (null != icon) // ProgId's Default Icon
1352 {
1353 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, icon);
1354
1355 icon = String.Format(CultureInfo.InvariantCulture, "\"[#{0}]\"", icon);
1356
1357 if (CompilerConstants.IntegerNotSet != iconIndex)
1358 {
1359 icon = String.Concat(icon, ",", iconIndex);
1360 }
1361
1362 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(progId, "\\DefaultIcon"), String.Empty, icon, componentId);
1363 }
1364 }
1365
1366 if (null != noOpen)
1367 {
1368 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, progId, "NoOpen", noOpen, componentId); // ProgId NoOpen name
1369 }
1370
1371 // raise an error for an orphaned ProgId
1372 if (YesNoType.Yes == advertise && !foundExtension && null == parent && null == classId)
1373 {
1374 this.Core.Write(WarningMessages.OrphanedProgId(sourceLineNumbers, progId));
1375 }
1376
1377 return progId;
1378 }
1379
1380 /// <summary>
1381 /// Parses a property element.
1382 /// </summary>
1383 /// <param name="node">Element to parse.</param>
1384 private void ParsePropertyElement(XElement node)
1385 {
1386 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1387 Identifier id = null;
1388 var admin = false;
1389 var complianceCheck = false;
1390 var hidden = false;
1391 var secure = false;
1392 var suppressModularization = YesNoType.NotSet;
1393 string value = null;
1394
1395 foreach (var attrib in node.Attributes())
1396 {
1397 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
1398 {
1399 switch (attrib.Name.LocalName)
1400 {
1401 case "Id":
1402 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
1403 break;
1404 case "Admin":
1405 admin = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1406 break;
1407 case "ComplianceCheck":
1408 complianceCheck = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1409 break;
1410 case "Hidden":
1411 hidden = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1412 break;
1413 case "Secure":
1414 secure = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1415 break;
1416 case "SuppressModularization":
1417 suppressModularization = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1418 break;
1419 case "Value":
1420 value = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1421 break;
1422 default:
1423 this.Core.UnexpectedAttribute(node, attrib);
1424 break;
1425 }
1426 }
1427 else
1428 {
1429 this.Core.ParseExtensionAttribute(node, attrib);
1430 }
1431 }
1432
1433 if (null == id)
1434 {
1435 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
1436 id = Identifier.Invalid;
1437 }
1438 else if ("ProductID" == id.Id)
1439 {
1440 this.Core.Write(WarningMessages.ProductIdAuthored(sourceLineNumbers));
1441 }
1442 else if ("SecureCustomProperties" == id.Id || "AdminProperties" == id.Id || "MsiHiddenProperties" == id.Id)
1443 {
1444 this.Core.Write(ErrorMessages.CannotAuthorSpecialProperties(sourceLineNumbers, id.Id));
1445 }
1446
1447 if ("ErrorDialog" == id.Id)
1448 {
1449 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Dialog, value);
1450 }
1451
1452 foreach (var child in node.Elements())
1453 {
1454 if (CompilerCore.WixNamespace == child.Name.Namespace)
1455 {
1456 {
1457 switch (child.Name.LocalName)
1458 {
1459 case "ProductSearch":
1460 this.ParseProductSearchElement(child, id.Id);
1461 secure = true;
1462 break;
1463 default:
1464 // let ParseSearchSignatures handle standard AppSearch children and unknown elements
1465 break;
1466 }
1467 }
1468 }
1469 }
1470
1471 this.Core.InnerTextDisallowed(node);
1472
1473 // see if this property is used for appSearch
1474 var signatures = this.ParseSearchSignatures(node);
1475
1476 // If we're doing CCP then there must be a signature.
1477 if (complianceCheck && 0 == signatures.Count)
1478 {
1479 this.Core.Write(ErrorMessages.SearchElementRequiredWithAttribute(sourceLineNumbers, node.Name.LocalName, "ComplianceCheck", "yes"));
1480 }
1481
1482 foreach (var sig in signatures)
1483 {
1484 if (complianceCheck && !this.Core.EncounteredError)
1485 {
1486 this.Core.AddSymbol(new CCPSearchSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, sig)));
1487 }
1488
1489 this.AddAppSearch(sourceLineNumbers, id, sig);
1490 }
1491
1492 // If we're doing AppSearch get that setup.
1493 if (0 < signatures.Count)
1494 {
1495 this.AddProperty(sourceLineNumbers, id, value, admin, secure, hidden, false);
1496 }
1497 else // just a normal old property.
1498 {
1499 // If the property value is empty and none of the flags are set, print out a warning that we're ignoring
1500 // the element.
1501 if (String.IsNullOrEmpty(value) && !admin && !secure && !hidden)
1502 {
1503 this.Core.Write(WarningMessages.PropertyUseless(sourceLineNumbers, id.Id));
1504 }
1505 else // there is a value and/or a flag set, do that.
1506 {
1507 this.AddProperty(sourceLineNumbers, id, value, admin, secure, hidden, false);
1508 }
1509 }
1510
1511 if (!this.Core.EncounteredError && YesNoType.Yes == suppressModularization)
1512 {
1513 this.Core.Write(WarningMessages.PropertyModularizationSuppressed(sourceLineNumbers));
1514
1515 this.Core.AddSymbol(new WixSuppressModularizationSymbol(sourceLineNumbers)
1516 {
1517 SuppressIdentifier = id.Id
1518 });
1519 }
1520 }
1521
1522 /// <summary>
1523 /// Parses a RegistryKey element.
1524 /// </summary>
1525 /// <param name="node">Element to parse.</param>
1526 /// <param name="componentId">Identifier for parent component.</param>
1527 /// <param name="root">Root specified when element is nested under another Registry element, otherwise CompilerConstants.IntegerNotSet.</param>
1528 /// <param name="parentKey">Parent key for this Registry element when nested.</param>
1529 /// <param name="win64Component">true if the component is 64-bit.</param>
1530 /// <param name="possibleKeyPath">Identifier of this registry key since it could be the component's keypath.</param>
1531 /// <returns>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.</returns>
1532 private YesNoType ParseRegistryKeyElement(XElement node, string componentId, RegistryRootType? root, string parentKey, bool win64Component, out string possibleKeyPath)
1533 {
1534 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1535 Identifier id = null;
1536 var key = parentKey; // default to parent key path
1537 var forceCreateOnInstall = false;
1538 var forceDeleteOnUninstall = false;
1539 var keyPath = YesNoType.NotSet;
1540
1541 possibleKeyPath = null;
1542
1543 foreach (var attrib in node.Attributes())
1544 {
1545 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
1546 {
1547 switch (attrib.Name.LocalName)
1548 {
1549 case "Id":
1550 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
1551 break;
1552 case "ForceCreateOnInstall":
1553 forceCreateOnInstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1554 break;
1555 case "ForceDeleteOnUninstall":
1556 forceDeleteOnUninstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1557 break;
1558 case "Key":
1559 key = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1560 if (null != parentKey)
1561 {
1562 key = Path.Combine(parentKey, key);
1563 }
1564 key = key?.TrimEnd('\\');
1565 break;
1566 case "Root":
1567 if (root.HasValue)
1568 {
1569 this.Core.Write(ErrorMessages.RegistryRootInvalid(sourceLineNumbers));
1570 }
1571
1572 root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, true);
1573 break;
1574 default:
1575 this.Core.UnexpectedAttribute(node, attrib);
1576 break;
1577 }
1578 }
1579 else
1580 {
1581 this.Core.ParseExtensionAttribute(node, attrib);
1582 }
1583 }
1584
1585 var name = forceCreateOnInstall ? (forceDeleteOnUninstall ? "*" : "+") : (forceDeleteOnUninstall ? "-" : null);
1586
1587 if (forceCreateOnInstall || forceDeleteOnUninstall) // generates a Registry row, so an Id must be present
1588 {
1589 // generate the identifier if it wasn't provided
1590 if (null == id)
1591 {
1592 id = this.Core.CreateIdentifier("reg", componentId, ((int)root).ToString(CultureInfo.InvariantCulture.NumberFormat), LowercaseOrNull(key), LowercaseOrNull(name));
1593 }
1594 }
1595 else // does not generate a Registry row, so no Id should be present
1596 {
1597 if (null != id)
1598 {
1599 this.Core.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "Id", "ForceCreateOnInstall", "ForceDeleteOnUninstall", "yes", true));
1600 }
1601 }
1602
1603 if (!root.HasValue)
1604 {
1605 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root"));
1606 }
1607
1608 if (null == key)
1609 {
1610 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key"));
1611 key = String.Empty; // set the key to something to prevent null reference exceptions
1612 }
1613
1614 foreach (var child in node.Elements())
1615 {
1616 if (CompilerCore.WixNamespace == child.Name.Namespace)
1617 {
1618 string possibleChildKeyPath = null;
1619
1620 switch (child.Name.LocalName)
1621 {
1622 case "RegistryKey":
1623 if (YesNoType.Yes == this.ParseRegistryKeyElement(child, componentId, root, key, win64Component, out possibleChildKeyPath))
1624 {
1625 if (YesNoType.Yes == keyPath)
1626 {
1627 this.Core.Write(ErrorMessages.ComponentMultipleKeyPaths(sourceLineNumbers, child.Name.LocalName, "KeyPath", "yes", "File", "RegistryValue", "ODBCDataSource"));
1628 }
1629
1630 possibleKeyPath = possibleChildKeyPath; // the child is the key path
1631 keyPath = YesNoType.Yes;
1632 }
1633 else if (null == possibleKeyPath && null != possibleChildKeyPath)
1634 {
1635 possibleKeyPath = possibleChildKeyPath;
1636 }
1637 break;
1638 case "RegistryValue":
1639 if (YesNoType.Yes == this.ParseRegistryValueElement(child, componentId, root, key, win64Component, out possibleChildKeyPath))
1640 {
1641 if (YesNoType.Yes == keyPath)
1642 {
1643 this.Core.Write(ErrorMessages.ComponentMultipleKeyPaths(sourceLineNumbers, child.Name.LocalName, "KeyPath", "yes", "File", "RegistryValue", "ODBCDataSource"));
1644 }
1645
1646 possibleKeyPath = possibleChildKeyPath; // the child is the key path
1647 keyPath = YesNoType.Yes;
1648 }
1649 else if (null == possibleKeyPath && null != possibleChildKeyPath)
1650 {
1651 possibleKeyPath = possibleChildKeyPath;
1652 }
1653 break;
1654 case "Permission":
1655 if (!forceCreateOnInstall)
1656 {
1657 this.Core.Write(ErrorMessages.UnexpectedElementWithAttributeValue(sourceLineNumbers, node.Name.LocalName, child.Name.LocalName, "ForceCreateOnInstall", "yes"));
1658 }
1659 this.ParsePermissionElement(child, id.Id, "Registry");
1660 break;
1661 case "PermissionEx":
1662 if (!forceCreateOnInstall)
1663 {
1664 this.Core.Write(ErrorMessages.UnexpectedElementWithAttributeValue(sourceLineNumbers, node.Name.LocalName, child.Name.LocalName, "ForceCreateOnInstall", "yes"));
1665 }
1666 this.ParsePermissionExElement(child, id.Id, "Registry");
1667 break;
1668 default:
1669 this.Core.UnexpectedElement(node, child);
1670 break;
1671 }
1672 }
1673 else
1674 {
1675 var context = new Dictionary<string, string>() { { "RegistryId", id?.Id }, { "ComponentId", componentId }, { "Win64", win64Component.ToString() } };
1676 this.Core.ParseExtensionElement(node, child, context);
1677 }
1678 }
1679
1680 if (!this.Core.EncounteredError && null != name)
1681 {
1682 this.Core.AddSymbol(new RegistrySymbol(sourceLineNumbers, id)
1683 {
1684 Root = root.Value,
1685 Key = key,
1686 Name = name,
1687 ComponentRef = componentId,
1688 });
1689 }
1690
1691 return keyPath;
1692 }
1693
1694 /// <summary>
1695 /// Parses a RegistryValue element.
1696 /// </summary>
1697 /// <param name="node">Element to parse.</param>
1698 /// <param name="componentId">Identifier for parent component.</param>
1699 /// <param name="root">Root specified when element is nested under a RegistryKey element, otherwise CompilerConstants.IntegerNotSet.</param>
1700 /// <param name="parentKey">Root specified when element is nested under a RegistryKey element, otherwise CompilerConstants.IntegerNotSet.</param>
1701 /// <param name="win64Component">true if the component is 64-bit.</param>
1702 /// <param name="possibleKeyPath">Identifier of this registry key since it could be the component's keypath.</param>
1703 /// <returns>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.</returns>
1704 private YesNoType ParseRegistryValueElement(XElement node, string componentId, RegistryRootType? root, string parentKey, bool win64Component, out string possibleKeyPath)
1705 {
1706 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1707 Identifier id = null;
1708 var key = parentKey; // default to parent key path
1709 string name = null;
1710 string value = null;
1711 string action = null;
1712 var valueType = RegistryValueType.String;
1713 var actionType = RegistryValueActionType.Write;
1714 var keyPath = YesNoType.NotSet;
1715
1716 possibleKeyPath = null;
1717
1718 foreach (var attrib in node.Attributes())
1719 {
1720 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
1721 {
1722 switch (attrib.Name.LocalName)
1723 {
1724 case "Id":
1725 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
1726 break;
1727 case "Action":
1728 var actionValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1729 switch (actionValue)
1730 {
1731 case "append":
1732 actionType = RegistryValueActionType.Append;
1733 break;
1734 case "prepend":
1735 actionType = RegistryValueActionType.Prepend;
1736 break;
1737 case "write":
1738 actionType = RegistryValueActionType.Write;
1739 break;
1740 case "":
1741 break;
1742 default:
1743 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, actionValue, "append", "prepend", "write"));
1744 break;
1745 }
1746 break;
1747 case "Key":
1748 key = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1749 if (null != parentKey)
1750 {
1751 if (parentKey.EndsWith("\\", StringComparison.Ordinal))
1752 {
1753 key = String.Concat(parentKey, key);
1754 }
1755 else
1756 {
1757 key = String.Concat(parentKey, "\\", key);
1758 }
1759 }
1760 break;
1761 case "KeyPath":
1762 keyPath = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1763 break;
1764 case "Name":
1765 name = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1766 break;
1767 case "Root":
1768 if (root.HasValue)
1769 {
1770 this.Core.Write(ErrorMessages.RegistryRootInvalid(sourceLineNumbers));
1771 }
1772
1773 root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, true);
1774 break;
1775 case "Type":
1776 var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1777 switch (typeValue)
1778 {
1779 case "binary":
1780 valueType = RegistryValueType.Binary;
1781 break;
1782 case "expandable":
1783 valueType = RegistryValueType.Expandable;
1784 break;
1785 case "integer":
1786 valueType = RegistryValueType.Integer;
1787 break;
1788 case "multiString":
1789 valueType = RegistryValueType.MultiString;
1790 break;
1791 case "string":
1792 valueType = RegistryValueType.String;
1793 break;
1794 case "":
1795 break;
1796 default:
1797 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, typeValue, "binary", "expandable", "integer", "multiString", "string"));
1798 break;
1799 }
1800 break;
1801 case "Value":
1802 value = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty);
1803 break;
1804 default:
1805 this.Core.UnexpectedAttribute(node, attrib);
1806 break;
1807 }
1808 }
1809 else
1810 {
1811 this.Core.ParseExtensionAttribute(node, attrib);
1812 }
1813 }
1814
1815 // generate the identifier if it wasn't provided
1816 if (null == id)
1817 {
1818 id = this.Core.CreateIdentifier("reg", componentId, ((int)(root ?? RegistryRootType.Unknown)).ToString(), LowercaseOrNull(key), LowercaseOrNull(name));
1819 }
1820
1821 if (RegistryValueType.MultiString != valueType && (RegistryValueActionType.Append == actionType || RegistryValueActionType.Prepend == actionType))
1822 {
1823 this.Core.Write(ErrorMessages.IllegalAttributeValueWithoutOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Action", action, "Type", "multiString"));
1824 }
1825
1826 if (null == key)
1827 {
1828 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key"));
1829 }
1830
1831 if (!root.HasValue)
1832 {
1833 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root"));
1834 }
1835
1836 foreach (var child in node.Elements())
1837 {
1838 if (CompilerCore.WixNamespace == child.Name.Namespace)
1839 {
1840 switch (child.Name.LocalName)
1841 {
1842 case "MultiString":
1843 case "MultiStringValue":
1844 if (RegistryValueType.MultiString != valueType && null != value)
1845 {
1846 this.Core.Write(ErrorMessages.RegistryMultipleValuesWithoutMultiString(sourceLineNumbers, node.Name.LocalName, "Value", child.Name.LocalName, "Type"));
1847 }
1848 else
1849 {
1850 value = this.ParseRegistryMultiStringElement(child, value);
1851 }
1852 break;
1853 case "Permission":
1854 this.ParsePermissionElement(child, id.Id, "Registry");
1855 break;
1856 case "PermissionEx":
1857 this.ParsePermissionExElement(child, id.Id, "Registry");
1858 break;
1859 default:
1860 this.Core.UnexpectedElement(node, child);
1861 break;
1862 }
1863 }
1864 else
1865 {
1866 var context = new Dictionary<string, string>() { { "RegistryId", id?.Id }, { "ComponentId", componentId }, { "Win64", win64Component.ToString() } };
1867 this.Core.ParseExtensionElement(node, child, context);
1868 }
1869 }
1870
1871 //switch (typeType)
1872 //{
1873 //case Wix.RegistryValue.TypeType.binary:
1874 // value = String.Concat("#x", value);
1875 // break;
1876 //case Wix.RegistryValue.TypeType.expandable:
1877 // value = String.Concat("#%", value);
1878 // break;
1879 //case Wix.RegistryValue.TypeType.integer:
1880 // value = String.Concat("#", value);
1881 // break;
1882 //case Wix.RegistryValue.TypeType.multiString:
1883 // switch (actionType)
1884 // {
1885 // case Wix.RegistryValue.ActionType.append:
1886 // value = String.Concat("[~]", value);
1887 // break;
1888 // case Wix.RegistryValue.ActionType.prepend:
1889 // value = String.Concat(value, "[~]");
1890 // break;
1891 // case Wix.RegistryValue.ActionType.write:
1892 // default:
1893 // if (null != value && -1 == value.IndexOf("[~]", StringComparison.Ordinal))
1894 // {
1895 // value = String.Format(CultureInfo.InvariantCulture, "[~]{0}[~]", value);
1896 // }
1897 // break;
1898 // }
1899 // break;
1900 //case Wix.RegistryValue.TypeType.@string:
1901 // // escape the leading '#' character for string registry keys
1902 // if (null != value && value.StartsWith("#", StringComparison.Ordinal))
1903 // {
1904 // value = String.Concat("#", value);
1905 // }
1906 // break;
1907 //}
1908
1909 // value may be set by child MultiStringValue elements, so it must be checked here
1910 if (null == value && valueType != RegistryValueType.Binary)
1911 {
1912 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value"));
1913 }
1914 else if (0 == value?.Length && ("+" == name || "-" == name || "*" == name)) // prevent accidental authoring of special name values
1915 {
1916 this.Core.Write(ErrorMessages.RegistryNameValueIncorrect(sourceLineNumbers, node.Name.LocalName, "Name", name));
1917 }
1918
1919 if (!this.Core.EncounteredError)
1920 {
1921 this.Core.AddSymbol(new RegistrySymbol(sourceLineNumbers, id)
1922 {
1923 Root = root.Value,
1924 Key = key,
1925 Name = name,
1926 Value = value,
1927 ValueType = valueType,
1928 ValueAction = actionType,
1929 ComponentRef = componentId,
1930 });
1931 }
1932
1933 // If this was just a regular registry key (that could be the key path)
1934 // and no child registry key set the possible key path, let's make this
1935 // Registry/@Id a possible key path.
1936 if (null == possibleKeyPath)
1937 {
1938 possibleKeyPath = id.Id;
1939 }
1940
1941 return keyPath;
1942 }
1943
1944 private string ParseRegistryMultiStringElement(XElement node, string value)
1945 {
1946 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1947 string multiStringValue = null;
1948
1949 foreach (var attrib in node.Attributes())
1950 {
1951 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
1952 {
1953 switch (attrib.Name.LocalName)
1954 {
1955 case "Value":
1956 multiStringValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1957 break;
1958 default:
1959 this.Core.UnexpectedAttribute(node, attrib);
1960 break;
1961 }
1962 }
1963 }
1964
1965 this.Core.ParseForExtensionElements(node);
1966
1967 return null == value ? multiStringValue ?? "[~]" : String.Concat(value, "[~]", multiStringValue);
1968 }
1969
1970 /// <summary>
1971 /// Parses a RemoveRegistryKey element.
1972 /// </summary>
1973 /// <param name="node">The element to parse.</param>
1974 /// <param name="componentId">The component identifier of the parent element.</param>
1975 private void ParseRemoveRegistryKeyElement(XElement node, string componentId)
1976 {
1977 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1978 Identifier id = null;
1979 RemoveRegistryActionType? actionType = null;
1980 string key = null;
1981 var name = "-";
1982 RegistryRootType? root = null;
1983
1984 foreach (var attrib in node.Attributes())
1985 {
1986 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
1987 {
1988 switch (attrib.Name.LocalName)
1989 {
1990 case "Id":
1991 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
1992 break;
1993 case "Action":
1994 var actionValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1995 switch (actionValue)
1996 {
1997 case "removeOnInstall":
1998 actionType = RemoveRegistryActionType.RemoveOnInstall;
1999 break;
2000 case "removeOnUninstall":
2001 actionType = RemoveRegistryActionType.RemoveOnUninstall;
2002 break;
2003 case "":
2004 break;
2005 default:
2006 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, actionValue, "removeOnInstall", "removeOnUninstall"));
2007 break;
2008 }
2009 //if (0 < action.Length)
2010 //{
2011 // if (!Wix.RemoveRegistryKey.TryParseActionType(action, out actionType))
2012 // {
2013 // this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, action, "removeOnInstall", "removeOnUninstall"));
2014 // }
2015 //}
2016 break;
2017 case "Key":
2018 key = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
2019 break;
2020 case "Root":
2021 root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, true);
2022 break;
2023 default:
2024 this.Core.UnexpectedAttribute(node, attrib);
2025 break;
2026 }
2027 }
2028 else
2029 {
2030 this.Core.ParseExtensionAttribute(node, attrib);
2031 }
2032 }
2033
2034 // generate the identifier if it wasn't provided
2035 if (null == id)
2036 {
2037 id = this.Core.CreateIdentifier("reg", componentId, ((int)root).ToString(CultureInfo.InvariantCulture.NumberFormat), LowercaseOrNull(key), LowercaseOrNull(name));
2038 }
2039
2040 if (!root.HasValue)
2041 {
2042 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root"));
2043 }
2044
2045 if (null == key)
2046 {
2047 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key"));
2048 }
2049
2050 if (!actionType.HasValue)
2051 {
2052 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Action"));
2053 }
2054
2055 this.Core.ParseForExtensionElements(node);
2056
2057 if (!this.Core.EncounteredError)
2058 {
2059 this.Core.AddSymbol(new RemoveRegistrySymbol(sourceLineNumbers, id)
2060 {
2061 Root = root.Value,
2062 Key = key,
2063 Name = name,
2064 Action = actionType.Value,
2065 ComponentRef = componentId,
2066 });
2067 }
2068 }
2069
2070 /// <summary>
2071 /// Parses a RemoveRegistryValue element.
2072 /// </summary>
2073 /// <param name="node">The element to parse.</param>
2074 /// <param name="componentId">The component identifier of the parent element.</param>
2075 private void ParseRemoveRegistryValueElement(XElement node, string componentId)
2076 {
2077 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2078 Identifier id = null;
2079 string key = null;
2080 string name = null;
2081 RegistryRootType? root = null;
2082
2083 foreach (var attrib in node.Attributes())
2084 {
2085 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
2086 {
2087 switch (attrib.Name.LocalName)
2088 {
2089 case "Id":
2090 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
2091 break;
2092 case "Key":
2093 key = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
2094 break;
2095 case "Name":
2096 name = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
2097 break;
2098 case "Root":
2099 root = this.Core.GetAttributeRegistryRootValue(sourceLineNumbers, attrib, true);
2100 break;
2101 default:
2102 this.Core.UnexpectedAttribute(node, attrib);
2103 break;
2104 }
2105 }
2106 else
2107 {
2108 this.Core.ParseExtensionAttribute(node, attrib);
2109 }
2110 }
2111
2112 // generate the identifier if it wasn't provided
2113 if (null == id)
2114 {
2115 id = this.Core.CreateIdentifier("reg", componentId, ((int)root).ToString(CultureInfo.InvariantCulture.NumberFormat), LowercaseOrNull(key), LowercaseOrNull(name));
2116 }
2117
2118 if (!root.HasValue)
2119 {
2120 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root"));
2121 }
2122
2123 if (null == key)
2124 {
2125 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key"));
2126 }
2127
2128 this.Core.ParseForExtensionElements(node);
2129
2130 if (!this.Core.EncounteredError)
2131 {
2132 this.Core.AddSymbol(new RemoveRegistrySymbol(sourceLineNumbers, id)
2133 {
2134 Root = root.Value,
2135 Key = key,
2136 Name = name,
2137 ComponentRef = componentId
2138 });
2139 }
2140 }
2141
2142 /// <summary>
2143 /// Parses a remove file element.
2144 /// </summary>
2145 /// <param name="node">Element to parse.</param>
2146 /// <param name="componentId">Identifier of parent component.</param>
2147 /// <param name="parentDirectory">Identifier of the parent component's directory.</param>
2148 private void ParseRemoveFileElement(XElement node, string componentId, string parentDirectory)
2149 {
2150 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2151 Identifier id = null;
2152 string directoryId = null;
2153 string subdirectory = null;
2154 string name = null;
2155 bool? onInstall = null;
2156 bool? onUninstall = null;
2157 string propertyId = null;
2158 string shortName = null;
2159
2160 foreach (var attrib in node.Attributes())
2161 {
2162 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
2163 {
2164 switch (attrib.Name.LocalName)
2165 {
2166 case "Id":
2167 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
2168 break;
2169 case "Directory":
2170 directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
2171 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId);
2172 break;
2173 case "Subdirectory":
2174 subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true);
2175 break;
2176 case "Name":
2177 name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, true);
2178 break;
2179 case "On":
2180 var onValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
2181 switch (onValue)
2182 {
2183 case "install":
2184 onInstall = true;
2185 break;
2186 case "uninstall":
2187 onUninstall = true;
2188 break;
2189 case "both":
2190 onInstall = true;
2191 onUninstall = true;
2192 break;
2193 }
2194 break;
2195 case "Property":
2196 propertyId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
2197 break;
2198 case "ShortName":
2199 shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, true);
2200 break;
2201 default:
2202 this.Core.UnexpectedAttribute(node, attrib);
2203 break;
2204 }
2205 }
2206 else
2207 {
2208 this.Core.ParseExtensionAttribute(node, attrib);
2209 }
2210 }
2211
2212 if (null == name)
2213 {
2214 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
2215 }
2216
2217 if (!onInstall.HasValue && !onUninstall.HasValue)
2218 {
2219 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "On"));
2220 }
2221
2222 if (String.IsNullOrEmpty(propertyId))
2223 {
2224 directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId ?? parentDirectory, subdirectory, "Directory", "Subdirectory");
2225 }
2226 else if (!String.IsNullOrEmpty(directoryId))
2227 {
2228 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "Directory", directoryId));
2229 }
2230 else if (!String.IsNullOrEmpty(subdirectory))
2231 {
2232 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "Subdirectory", subdirectory));
2233 }
2234
2235 if (null == id)
2236 {
2237 var on = (onInstall == true && onUninstall == true) ? 3 : (onUninstall == true) ? 2 : (onInstall == true) ? 1 : 0;
2238 id = this.Core.CreateIdentifier("rmf", directoryId ?? propertyId ?? parentDirectory, LowercaseOrNull(shortName), LowercaseOrNull(name), on.ToString());
2239 }
2240
2241 this.Core.ParseForExtensionElements(node);
2242
2243 if (!this.Core.EncounteredError)
2244 {
2245 this.Core.AddSymbol(new RemoveFileSymbol(sourceLineNumbers, id)
2246 {
2247 ComponentRef = componentId,
2248 FileName = name,
2249 ShortFileName = shortName,
2250 DirPropertyRef = directoryId ?? propertyId ?? parentDirectory,
2251 OnInstall = onInstall,
2252 OnUninstall = onUninstall,
2253 });
2254 }
2255 }
2256
2257 /// <summary>
2258 /// Parses a RemoveFolder element.
2259 /// </summary>
2260 /// <param name="node">Element to parse.</param>
2261 /// <param name="componentId">Identifier of parent component.</param>
2262 /// <param name="parentDirectory">Identifier of parent component's directory.</param>
2263 private void ParseRemoveFolderElement(XElement node, string componentId, string parentDirectory)
2264 {
2265 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2266 Identifier id = null;
2267 string directoryId = null;
2268 string subdirectory = null;
2269 bool? onInstall = null;
2270 bool? onUninstall = null;
2271 string propertyId = null;
2272
2273 foreach (var attrib in node.Attributes())
2274 {
2275 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
2276 {
2277 switch (attrib.Name.LocalName)
2278 {
2279 case "Id":
2280 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
2281 break;
2282 case "Directory":
2283 directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
2284 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId);
2285 break;
2286 case "Subdirectory":
2287 subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true);
2288 break;
2289 case "On":
2290 var onValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
2291 switch (onValue)
2292 {
2293 case "install":
2294 onInstall = true;
2295 break;
2296 case "uninstall":
2297 onUninstall = true;
2298 break;
2299 case "both":
2300 onInstall = true;
2301 onUninstall = true;
2302 break;
2303 }
2304 break;
2305 case "Property":
2306 propertyId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
2307 break;
2308 default:
2309 this.Core.UnexpectedAttribute(node, attrib);
2310 break;
2311 }
2312 }
2313 else
2314 {
2315 this.Core.ParseExtensionAttribute(node, attrib);
2316 }
2317 }
2318
2319 if (!onInstall.HasValue && !onUninstall.HasValue)
2320 {
2321 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "On"));
2322 }
2323
2324 if (String.IsNullOrEmpty(propertyId))
2325 {
2326 directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId ?? parentDirectory, subdirectory, "Directory", "Subdirectory");
2327 }
2328 else if (!String.IsNullOrEmpty(directoryId))
2329 {
2330 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "Directory", directoryId));
2331 }
2332 else if (!String.IsNullOrEmpty(subdirectory))
2333 {
2334 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "Subdirectory", subdirectory));
2335 }
2336
2337 if (null == id)
2338 {
2339 var on = (onInstall == true && onUninstall == true) ? 3 : (onUninstall == true) ? 2 : (onInstall == true) ? 1 : 0;
2340 id = this.Core.CreateIdentifier("rmf", directoryId ?? propertyId, on.ToString());
2341 }
2342
2343 this.Core.ParseForExtensionElements(node);
2344
2345 if (!this.Core.EncounteredError)
2346 {
2347 this.Core.AddSymbol(new RemoveFileSymbol(sourceLineNumbers, id)
2348 {
2349 ComponentRef = componentId,
2350 DirPropertyRef = directoryId ?? propertyId,
2351 OnInstall = onInstall,
2352 OnUninstall = onUninstall
2353 });
2354 }
2355 }
2356
2357 /// <summary>
2358 /// Parses a reserve cost element.
2359 /// </summary>
2360 /// <param name="node">Element to parse.</param>
2361 /// <param name="componentId">Identifier of parent component.</param>
2362 /// <param name="directoryId">Optional and default identifier of referenced directory.</param>
2363 private void ParseReserveCostElement(XElement node, string componentId, string directoryId)
2364 {
2365 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2366 Identifier id = null;
2367 string subdirectory = null;
2368 var runFromSource = CompilerConstants.IntegerNotSet;
2369 var runLocal = CompilerConstants.IntegerNotSet;
2370
2371 foreach (var attrib in node.Attributes())
2372 {
2373 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
2374 {
2375 switch (attrib.Name.LocalName)
2376 {
2377 case "Id":
2378 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
2379 break;
2380 case "Directory":
2381 directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
2382 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId);
2383 break;
2384 case "Subdirectory":
2385 subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true);
2386 break;
2387 case "RunFromSource":
2388 runFromSource = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue);
2389 break;
2390 case "RunLocal":
2391 runLocal = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue);
2392 break;
2393 default:
2394 this.Core.UnexpectedAttribute(node, attrib);
2395 break;
2396 }
2397 }
2398 else
2399 {
2400 this.Core.ParseExtensionAttribute(node, attrib);
2401 }
2402 }
2403
2404 directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId, subdirectory, "Directory", "Subdirectory");
2405
2406 if (null == id)
2407 {
2408 id = this.Core.CreateIdentifier("rc", componentId, directoryId);
2409 }
2410
2411 if (CompilerConstants.IntegerNotSet == runFromSource)
2412 {
2413 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "RunFromSource"));
2414 }
2415
2416 if (CompilerConstants.IntegerNotSet == runLocal)
2417 {
2418 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "RunLocal"));
2419 }
2420
2421 this.Core.ParseForExtensionElements(node);
2422
2423 if (!this.Core.EncounteredError)
2424 {
2425 this.Core.AddSymbol(new ReserveCostSymbol(sourceLineNumbers, id)
2426 {
2427 ComponentRef = componentId,
2428 ReserveFolder = directoryId,
2429 ReserveLocal = runLocal,
2430 ReserveSource = runFromSource
2431 });
2432 }
2433 }
2434
2435 /// <summary>
2436 /// Parses a sequence element.
2437 /// </summary>
2438 /// <param name="node">Element to parse.</param>
2439 /// <param name="sequenceTable">Name of sequence table.</param>
2440 private void ParseSequenceElement(XElement node, SequenceTable sequenceTable)
2441 {
2442 // Parse each action in the sequence.
2443 foreach (var child in node.Elements())
2444 {
2445 var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
2446 var actionName = child.Name.LocalName;
2447 string afterAction = null;
2448 string beforeAction = null;
2449 string condition = null;
2450 var customAction = "Custom" == actionName;
2451 var overridable = false;
2452 var exitSequence = CompilerConstants.IntegerNotSet;
2453 var sequence = CompilerConstants.IntegerNotSet;
2454 var showDialog = "Show" == actionName;
2455 var specialAction = "InstallExecute" == actionName || "InstallExecuteAgain" == actionName || "RemoveExistingProducts" == actionName || "DisableRollback" == actionName || "ScheduleReboot" == actionName || "ForceReboot" == actionName || "ResolveSource" == actionName;
2456 var specialStandardAction = "AppSearch" == actionName || "CCPSearch" == actionName || "RMCCPSearch" == actionName || "LaunchConditions" == actionName || "FindRelatedProducts" == actionName;
2457 var suppress = false;
2458
2459 foreach (var attrib in child.Attributes())
2460 {
2461 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
2462 {
2463 switch (attrib.Name.LocalName)
2464 {
2465 case "Action":
2466 if (customAction)
2467 {
2468 actionName = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib);
2469 this.Core.CreateSimpleReference(childSourceLineNumbers, SymbolDefinitions.CustomAction, actionName);
2470 }
2471 else
2472 {
2473 this.Core.UnexpectedAttribute(child, attrib);
2474 }
2475 break;
2476 case "After":
2477 if (customAction || showDialog || specialAction || specialStandardAction)
2478 {
2479 afterAction = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib);
2480 this.Core.CreateSimpleReference(childSourceLineNumbers, SymbolDefinitions.WixAction, sequenceTable.ToString(), afterAction);
2481 }
2482 else
2483 {
2484 this.Core.UnexpectedAttribute(child, attrib);
2485 }
2486 break;
2487 case "Before":
2488 if (customAction || showDialog || specialAction || specialStandardAction)
2489 {
2490 beforeAction = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib);
2491 this.Core.CreateSimpleReference(childSourceLineNumbers, SymbolDefinitions.WixAction, sequenceTable.ToString(), beforeAction);
2492 }
2493 else
2494 {
2495 this.Core.UnexpectedAttribute(child, attrib);
2496 }
2497 break;
2498 case "Condition":
2499 condition = this.Core.GetAttributeValue(childSourceLineNumbers, attrib);
2500 break;
2501 case "Dialog":
2502 if (showDialog)
2503 {
2504 actionName = this.Core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib);
2505 this.Core.CreateSimpleReference(childSourceLineNumbers, SymbolDefinitions.Dialog, actionName);
2506 }
2507 else
2508 {
2509 this.Core.UnexpectedAttribute(child, attrib);
2510 }
2511 break;
2512 case "OnExit":
2513 if (customAction || showDialog || specialAction)
2514 {
2515 var exitValue = this.Core.GetAttributeValue(childSourceLineNumbers, attrib);
2516 switch (exitValue)
2517 {
2518 case "success":
2519 exitSequence = -1;
2520 break;
2521 case "cancel":
2522 exitSequence = -2;
2523 break;
2524 case "error":
2525 exitSequence = -3;
2526 break;
2527 case "suspend":
2528 exitSequence = -4;
2529 break;
2530 }
2531 }
2532 else
2533 {
2534 this.Core.UnexpectedAttribute(child, attrib);
2535 }
2536 break;
2537 case "Overridable":
2538 overridable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, attrib);
2539 break;
2540 case "Sequence":
2541 sequence = this.Core.GetAttributeIntegerValue(childSourceLineNumbers, attrib, 1, Int16.MaxValue);
2542 break;
2543 case "Suppress":
2544 suppress = YesNoType.Yes == this.Core.GetAttributeYesNoValue(childSourceLineNumbers, attrib);
2545 break;
2546 default:
2547 this.Core.UnexpectedAttribute(node, attrib);
2548 break;
2549 }
2550 }
2551 else
2552 {
2553 this.Core.ParseExtensionAttribute(node, attrib);
2554 }
2555 }
2556
2557 if (customAction && "Custom" == actionName)
2558 {
2559 this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Action"));
2560 }
2561 else if (showDialog && "Show" == actionName)
2562 {
2563 this.Core.Write(ErrorMessages.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Dialog"));
2564 }
2565
2566 if (CompilerConstants.IntegerNotSet != sequence)
2567 {
2568 if (CompilerConstants.IntegerNotSet != exitSequence)
2569 {
2570 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(childSourceLineNumbers, child.Name.LocalName, "Sequence", "OnExit"));
2571 }
2572 else if (null != beforeAction || null != afterAction)
2573 {
2574 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(childSourceLineNumbers, child.Name.LocalName, "Sequence", "Before", "After"));
2575 }
2576 }
2577 else // sequence not specified use OnExit (which may also be not set).
2578 {
2579 sequence = exitSequence;
2580 }
2581
2582 if (null != beforeAction && null != afterAction)
2583 {
2584 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(childSourceLineNumbers, child.Name.LocalName, "After", "Before"));
2585 }
2586 else if ((customAction || showDialog || specialAction) && !suppress && CompilerConstants.IntegerNotSet == sequence && null == beforeAction && null == afterAction)
2587 {
2588 this.Core.Write(ErrorMessages.NeedSequenceBeforeOrAfter(childSourceLineNumbers, child.Name.LocalName));
2589 }
2590
2591 // action that is scheduled to occur before/after itself
2592 if (beforeAction == actionName)
2593 {
2594 this.Core.Write(ErrorMessages.ActionScheduledRelativeToItself(childSourceLineNumbers, child.Name.LocalName, "Before", beforeAction));
2595 }
2596 else if (afterAction == actionName)
2597 {
2598 this.Core.Write(ErrorMessages.ActionScheduledRelativeToItself(childSourceLineNumbers, child.Name.LocalName, "After", afterAction));
2599 }
2600
2601 // normal standard actions cannot be set overridable by the user (since they are overridable by default)
2602 if (overridable && WindowsInstallerStandard.IsStandardAction(actionName) && !specialAction)
2603 {
2604 this.Core.Write(ErrorMessages.UnexpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Overridable"));
2605 }
2606
2607 // suppress cannot be specified at the same time as Before, After, or Sequence
2608 if (suppress && (null != afterAction || null != beforeAction || CompilerConstants.IntegerNotSet != sequence || overridable))
2609 {
2610 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(childSourceLineNumbers, child.Name.LocalName, "Suppress", "Before", "After", "Sequence", "Overridable"));
2611 }
2612
2613 this.Core.ParseForExtensionElements(child);
2614
2615 // add the row and any references needed
2616 if (!this.Core.EncounteredError)
2617 {
2618 if (suppress)
2619 {
2620 this.Core.AddSymbol(new WixSuppressActionSymbol(childSourceLineNumbers, new Identifier(AccessModifier.Global, sequenceTable, actionName))
2621 {
2622 SequenceTable = sequenceTable,
2623 Action = actionName
2624 });
2625 }
2626 else
2627 {
2628 var symbol = this.Core.AddSymbol(new WixActionSymbol(childSourceLineNumbers, new Identifier(AccessModifier.Global, sequenceTable, actionName))
2629 {
2630 SequenceTable = sequenceTable,
2631 Action = actionName,
2632 Condition = condition,
2633 Before = beforeAction,
2634 After = afterAction,
2635 Overridable = overridable,
2636 });
2637
2638 if (CompilerConstants.IntegerNotSet != sequence)
2639 {
2640 symbol.Sequence = sequence;
2641 }
2642 }
2643 }
2644 }
2645
2646 this.Core.InnerTextDisallowed(node);
2647 }
2648
2649
2650 /// <summary>
2651 /// Parses a service config element.
2652 /// </summary>
2653 /// <param name="node">Element to parse.</param>
2654 /// <param name="componentId">Identifier of parent component.</param>
2655 /// <param name="serviceName">Optional element containing parent's service name.</param>
2656 private void ParseServiceConfigElement(XElement node, string componentId, string serviceName)
2657 {
2658 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2659 Identifier id = null;
2660 string delayedAutoStart = null;
2661 string failureActionsWhen = null;
2662 var name = serviceName;
2663 var install = false;
2664 var reinstall = false;
2665 var uninstall = false;
2666 string preShutdownDelay = null;
2667 string requiredPrivileges = null;
2668 string sid = null;
2669
2670 this.Core.Write(WarningMessages.ServiceConfigFamilyNotSupported(sourceLineNumbers, node.Name.LocalName));
2671
2672 foreach (var attrib in node.Attributes())
2673 {
2674 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
2675 {
2676 switch (attrib.Name.LocalName)
2677 {
2678 case "Id":
2679 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
2680 break;
2681 case "DelayedAutoStart":
2682 delayedAutoStart = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
2683 switch (delayedAutoStart)
2684 {
2685 case "no":
2686 delayedAutoStart = "0";
2687 break;
2688 case "yes":
2689 delayedAutoStart = "1";
2690 break;
2691 default:
2692 // allow everything else to pass through that are hopefully "formatted" Properties.
2693 break;
2694 }
2695 break;
2696 case "FailureActionsWhen":
2697 failureActionsWhen = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
2698 switch (failureActionsWhen)
2699 {
2700 case "failedToStop":
2701 failureActionsWhen = "0";
2702 break;
2703 case "failedToStopOrReturnedError":
2704 failureActionsWhen = "1";
2705 break;
2706 default:
2707 // allow everything else to pass through that are hopefully "formatted" Properties.
2708 break;
2709 }
2710 break;
2711 case "OnInstall":
2712 install = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
2713 //if (YesNoType.Yes == install)
2714 //{
2715 // events |= MsiInterop.MsidbServiceConfigEventInstall;
2716 //}
2717 break;
2718 case "OnReinstall":
2719 reinstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
2720 //if (YesNoType.Yes == reinstall)
2721 //{
2722 // events |= MsiInterop.MsidbServiceConfigEventReinstall;
2723 //}
2724 break;
2725 case "OnUninstall":
2726 uninstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
2727 //if (YesNoType.Yes == uninstall)
2728 //{
2729 // events |= MsiInterop.MsidbServiceConfigEventUninstall;
2730 //}
2731 break;
2732 default:
2733 this.Core.UnexpectedAttribute(node, attrib);
2734 break;
2735 case "PreShutdownDelay":
2736 preShutdownDelay = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty);
2737 break;
2738 case "ServiceName":
2739 if (!String.IsNullOrEmpty(serviceName))
2740 {
2741 this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "ServiceInstall"));
2742 }
2743
2744 name = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
2745 break;
2746 case "ServiceSid":
2747 sid = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
2748 switch (sid)
2749 {
2750 case "none":
2751 sid = "0";
2752 break;
2753 case "restricted":
2754 sid = "3";
2755 break;
2756 case "unrestricted":
2757 sid = "1";
2758 break;
2759 default:
2760 // allow everything else to pass through that are hopefully "formatted" Properties.
2761 break;
2762 }
2763 break;
2764 }
2765 }
2766 else
2767 {
2768 this.Core.ParseExtensionAttribute(node, attrib);
2769 }
2770 }
2771
2772 // Get the ServiceConfig required privilegs.
2773 foreach (var child in node.Elements())
2774 {
2775 if (CompilerCore.WixNamespace == child.Name.Namespace)
2776 {
2777 switch (child.Name.LocalName)
2778 {
2779 case "RequiredPrivilege":
2780 requiredPrivileges = this.ParseRequiredPrivilege(child, requiredPrivileges);
2781 break;
2782 default:
2783 this.Core.UnexpectedElement(node, child);
2784 break;
2785 }
2786 }
2787 else
2788 {
2789 this.Core.ParseExtensionElement(node, child);
2790 }
2791 }
2792
2793 if (String.IsNullOrEmpty(name))
2794 {
2795 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ServiceName"));
2796 }
2797 else if (null == id)
2798 {
2799 id = this.Core.CreateIdentifierFromFilename(name);
2800 }
2801
2802 if (!install && !reinstall && !uninstall)
2803 {
2804 this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "OnInstall", "OnReinstall", "OnUninstall"));
2805 }
2806
2807 if (String.IsNullOrEmpty(delayedAutoStart) && String.IsNullOrEmpty(failureActionsWhen) && String.IsNullOrEmpty(preShutdownDelay) && String.IsNullOrEmpty(requiredPrivileges) && String.IsNullOrEmpty(sid))
2808 {
2809 this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "DelayedAutoStart", "FailureActionsWhen", "PreShutdownDelay", "ServiceSid", "RequiredPrivilege"));
2810 }
2811
2812 if (!this.Core.EncounteredError)
2813 {
2814 if (!String.IsNullOrEmpty(delayedAutoStart))
2815 {
2816 this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".DS")))
2817 {
2818 Name = name,
2819 OnInstall = install,
2820 OnReinstall = reinstall,
2821 OnUninstall = uninstall,
2822 ConfigType = MsiServiceConfigType.DelayedAutoStart,
2823 Argument = delayedAutoStart,
2824 ComponentRef = componentId,
2825 });
2826 }
2827
2828 if (!String.IsNullOrEmpty(failureActionsWhen))
2829 {
2830 this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".FA")))
2831 {
2832 Name = name,
2833 OnInstall = install,
2834 OnReinstall = reinstall,
2835 OnUninstall = uninstall,
2836 ConfigType = MsiServiceConfigType.FailureActionsFlag,
2837 Argument = failureActionsWhen,
2838 ComponentRef = componentId,
2839 });
2840 }
2841
2842 if (!String.IsNullOrEmpty(sid))
2843 {
2844 this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".SS")))
2845 {
2846 Name = name,
2847 OnInstall = install,
2848 OnReinstall = reinstall,
2849 OnUninstall = uninstall,
2850 ConfigType = MsiServiceConfigType.ServiceSidInfo,
2851 Argument = sid,
2852 ComponentRef = componentId,
2853 });
2854 }
2855
2856 if (!String.IsNullOrEmpty(requiredPrivileges))
2857 {
2858 this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".RP")))
2859 {
2860 Name = name,
2861 OnInstall = install,
2862 OnReinstall = reinstall,
2863 OnUninstall = uninstall,
2864 ConfigType = MsiServiceConfigType.RequiredPrivilegesInfo,
2865 Argument = requiredPrivileges,
2866 ComponentRef = componentId,
2867 });
2868 }
2869
2870 if (!String.IsNullOrEmpty(preShutdownDelay))
2871 {
2872 this.Core.AddSymbol(new MsiServiceConfigSymbol(sourceLineNumbers, new Identifier(id.Access, String.Concat(id.Id, ".PD")))
2873 {
2874 Name = name,
2875 OnInstall = install,
2876 OnReinstall = reinstall,
2877 OnUninstall = uninstall,
2878 ConfigType = MsiServiceConfigType.PreshutdownInfo,
2879 Argument = preShutdownDelay,
2880 ComponentRef = componentId,
2881 });
2882 }
2883 }
2884 }
2885
2886 private string ParseRequiredPrivilege(XElement node, string requiredPrivileges)
2887 {
2888 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2889 string privilege = null;
2890
2891 foreach (var attrib in node.Attributes())
2892 {
2893 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
2894 {
2895 switch (attrib.Name.LocalName)
2896 {
2897 case "Name":
2898 privilege = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
2899 switch (privilege)
2900 {
2901 case "assignPrimaryToken":
2902 privilege = "SeAssignPrimaryTokenPrivilege";
2903 break;
2904 case "audit":
2905 privilege = "SeAuditPrivilege";
2906 break;
2907 case "backup":
2908 privilege = "SeBackupPrivilege";
2909 break;
2910 case "changeNotify":
2911 privilege = "SeChangeNotifyPrivilege";
2912 break;
2913 case "createGlobal":
2914 privilege = "SeCreateGlobalPrivilege";
2915 break;
2916 case "createPagefile":
2917 privilege = "SeCreatePagefilePrivilege";
2918 break;
2919 case "createPermanent":
2920 privilege = "SeCreatePermanentPrivilege";
2921 break;
2922 case "createSymbolicLink":
2923 privilege = "SeCreateSymbolicLinkPrivilege";
2924 break;
2925 case "createToken":
2926 privilege = "SeCreateTokenPrivilege";
2927 break;
2928 case "debug":
2929 privilege = "SeDebugPrivilege";
2930 break;
2931 case "enableDelegation":
2932 privilege = "SeEnableDelegationPrivilege";
2933 break;
2934 case "impersonate":
2935 privilege = "SeImpersonatePrivilege";
2936 break;
2937 case "increaseBasePriority":
2938 privilege = "SeIncreaseBasePriorityPrivilege";
2939 break;
2940 case "increaseQuota":
2941 privilege = "SeIncreaseQuotaPrivilege";
2942 break;
2943 case "increaseWorkingSet":
2944 privilege = "SeIncreaseWorkingSetPrivilege";
2945 break;
2946 case "loadDriver":
2947 privilege = "SeLoadDriverPrivilege";
2948 break;
2949 case "lockMemory":
2950 privilege = "SeLockMemoryPrivilege";
2951 break;
2952 case "machineAccount":
2953 privilege = "SeMachineAccountPrivilege";
2954 break;
2955 case "manageVolume":
2956 privilege = "SeManageVolumePrivilege";
2957 break;
2958 case "profileSingleProcess":
2959 privilege = "SeProfileSingleProcessPrivilege";
2960 break;
2961 case "relabel":
2962 privilege = "SeRelabelPrivilege";
2963 break;
2964 case "remoteShutdown":
2965 privilege = "SeRemoteShutdownPrivilege";
2966 break;
2967 case "restore":
2968 privilege = "SeRestorePrivilege";
2969 break;
2970 case "security":
2971 privilege = "SeSecurityPrivilege";
2972 break;
2973 case "shutdown":
2974 privilege = "SeShutdownPrivilege";
2975 break;
2976 case "syncAgent":
2977 privilege = "SeSyncAgentPrivilege";
2978 break;
2979 case "systemEnvironment":
2980 privilege = "SeSystemEnvironmentPrivilege";
2981 break;
2982 case "systemProfile":
2983 privilege = "SeSystemProfilePrivilege";
2984 break;
2985 case "systemTime":
2986 case "modifySystemTime":
2987 privilege = "SeSystemtimePrivilege";
2988 break;
2989 case "takeOwnership":
2990 privilege = "SeTakeOwnershipPrivilege";
2991 break;
2992 case "tcb":
2993 case "trustedComputerBase":
2994 privilege = "SeTcbPrivilege";
2995 break;
2996 case "timeZone":
2997 case "modifyTimeZone":
2998 privilege = "SeTimeZonePrivilege";
2999 break;
3000 case "trustedCredManAccess":
3001 case "trustedCredentialManagerAccess":
3002 privilege = "SeTrustedCredManAccessPrivilege";
3003 break;
3004 case "undock":
3005 privilege = "SeUndockPrivilege";
3006 break;
3007 case "unsolicitedInput":
3008 privilege = "SeUnsolicitedInputPrivilege";
3009 break;
3010 default:
3011 // allow everything else to pass through that are hopefully "formatted" Properties.
3012 break;
3013 }
3014 break;
3015 default:
3016 this.Core.UnexpectedAttribute(node, attrib);
3017 break;
3018 }
3019 }
3020 }
3021
3022 if (privilege == null)
3023 {
3024 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
3025 }
3026
3027 this.Core.ParseForExtensionElements(node);
3028
3029 return (requiredPrivileges == null) ? privilege : String.Concat(requiredPrivileges, "[~]", privilege);
3030 }
3031
3032 /// <summary>
3033 /// Parses a service config failure actions element.
3034 /// </summary>
3035 /// <param name="node">Element to parse.</param>
3036 /// <param name="componentId">Identifier of parent component.</param>
3037 /// <param name="serviceName">Optional element containing parent's service name.</param>
3038 private void ParseServiceConfigFailureActionsElement(XElement node, string componentId, string serviceName)
3039 {
3040 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
3041 Identifier id = null;
3042 var name = serviceName;
3043 var install = false;
3044 var reinstall = false;
3045 var uninstall = false;
3046 int? resetPeriod = null;
3047 string rebootMessage = null;
3048 string command = null;
3049 string actions = null;
3050 string actionsDelays = null;
3051
3052 this.Core.Write(WarningMessages.ServiceConfigFamilyNotSupported(sourceLineNumbers, node.Name.LocalName));
3053
3054 foreach (var attrib in node.Attributes())
3055 {
3056 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
3057 {
3058 switch (attrib.Name.LocalName)
3059 {
3060 case "Id":
3061 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
3062 break;
3063 case "Command":
3064 command = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty);
3065 break;
3066 case "OnInstall":
3067 install = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
3068 break;
3069 case "OnReinstall":
3070 reinstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
3071 break;
3072 case "OnUninstall":
3073 uninstall = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
3074 break;
3075 case "RebootMessage":
3076 rebootMessage = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty);
3077 break;
3078 case "ResetPeriod":
3079 resetPeriod = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue);
3080 break;
3081 case "ServiceName":
3082 if (!String.IsNullOrEmpty(serviceName))
3083 {
3084 this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "ServiceInstall"));
3085 }
3086
3087 name = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3088 break;
3089 default:
3090 this.Core.UnexpectedAttribute(node, attrib);
3091 break;
3092 }
3093 }
3094 else
3095 {
3096 this.Core.ParseExtensionAttribute(node, attrib);
3097 }
3098 }
3099
3100 // Get the ServiceConfigFailureActions actions.
3101 foreach (var child in node.Elements())
3102 {
3103 if (CompilerCore.WixNamespace == child.Name.Namespace)
3104 {
3105 switch (child.Name.LocalName)
3106 {
3107 case "Failure":
3108 string action = null;
3109 string delay = null;
3110 var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
3111
3112 foreach (var childAttrib in child.Attributes())
3113 {
3114 if (String.IsNullOrEmpty(childAttrib.Name.NamespaceName) || CompilerCore.WixNamespace == childAttrib.Name.Namespace)
3115 {
3116 switch (childAttrib.Name.LocalName)
3117 {
3118 case "Action":
3119 action = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib);
3120 switch (action)
3121 {
3122 case "none":
3123 action = "0";
3124 break;
3125 case "restartComputer":
3126 action = "2";
3127 break;
3128 case "restartService":
3129 action = "1";
3130 break;
3131 case "runCommand":
3132 action = "3";
3133 break;
3134 default:
3135 // allow everything else to pass through that are hopefully "formatted" Properties.
3136 break;
3137 }
3138 break;
3139 case "Delay":
3140 delay = this.Core.GetAttributeValue(childSourceLineNumbers, childAttrib);
3141 break;
3142 default:
3143 this.Core.UnexpectedAttribute(child, childAttrib);
3144 break;
3145 }
3146 }
3147 }
3148
3149 if (String.IsNullOrEmpty(action))
3150 {
3151 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, child.Name.LocalName, "Action"));
3152 }
3153
3154 if (String.IsNullOrEmpty(delay))
3155 {
3156 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, child.Name.LocalName, "Delay"));
3157 }
3158
3159 if (!String.IsNullOrEmpty(actions))
3160 {
3161 actions = String.Concat(actions, "[~]");
3162 }
3163 actions = String.Concat(actions, action);
3164
3165 if (!String.IsNullOrEmpty(actionsDelays))
3166 {
3167 actionsDelays = String.Concat(actionsDelays, "[~]");
3168 }
3169 actionsDelays = String.Concat(actionsDelays, delay);
3170 break;
3171 default:
3172 this.Core.UnexpectedElement(node, child);
3173 break;
3174 }
3175 }
3176 else
3177 {
3178 this.Core.ParseExtensionElement(node, child);
3179 }
3180 }
3181
3182 if (String.IsNullOrEmpty(name))
3183 {
3184 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ServiceName"));
3185 }
3186 else if (null == id)
3187 {
3188 id = this.Core.CreateIdentifierFromFilename(name);
3189 }
3190
3191 if (!install && !reinstall && !uninstall)
3192 {
3193 this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "OnInstall", "OnReinstall", "OnUninstall"));
3194 }
3195
3196 if (!this.Core.EncounteredError)
3197 {
3198 this.Core.AddSymbol(new MsiServiceConfigFailureActionsSymbol(sourceLineNumbers, id)
3199 {
3200 Name = name,
3201 OnInstall = install,
3202 OnReinstall = reinstall,
3203 OnUninstall = uninstall,
3204 ResetPeriod = resetPeriod,
3205 RebootMessage = rebootMessage,
3206 Command = command,
3207 Actions = actions,
3208 DelayActions = actionsDelays,
3209 ComponentRef = componentId,
3210 });
3211 }
3212 }
3213
3214 /// <summary>
3215 /// Parses a service control element.
3216 /// </summary>
3217 /// <param name="node">Element to parse.</param>
3218 /// <param name="componentId">Identifier of parent component.</param>
3219 private void ParseServiceControlElement(XElement node, string componentId)
3220 {
3221 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
3222 string arguments = null;
3223 Identifier id = null;
3224 string name = null;
3225 var installRemove = false;
3226 var uninstallRemove = false;
3227 var installStart = false;
3228 var uninstallStart = false;
3229 var installStop = false;
3230 var uninstallStop = false;
3231 bool? wait = null;
3232
3233 foreach (var attrib in node.Attributes())
3234 {
3235 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
3236 {
3237 switch (attrib.Name.LocalName)
3238 {
3239 case "Id":
3240 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
3241 break;
3242 case "Name":
3243 name = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3244 break;
3245 case "Remove":
3246 var removeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3247 switch (removeValue)
3248 {
3249 case "install":
3250 installRemove = true;
3251 break;
3252 case "uninstall":
3253 uninstallRemove = true;
3254 break;
3255 case "both":
3256 installRemove = true;
3257 uninstallRemove = true;
3258 break;
3259 case "":
3260 break;
3261 }
3262 break;
3263 case "Start":
3264 var startValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3265 switch (startValue)
3266 {
3267 case "install":
3268 installStart = true;
3269 break;
3270 case "uninstall":
3271 uninstallStart = true;
3272 break;
3273 case "both":
3274 installStart = true;
3275 uninstallStart = true;
3276 break;
3277 case "":
3278 break;
3279 }
3280 break;
3281 case "Stop":
3282 var stopValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3283 switch (stopValue)
3284 {
3285 case "install":
3286 installStop = true;
3287 break;
3288 case "uninstall":
3289 uninstallStop = true;
3290 break;
3291 case "both":
3292 installStop = true;
3293 uninstallStop = true;
3294 break;
3295 case "":
3296 break;
3297 }
3298 break;
3299 case "Wait":
3300 wait = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
3301 break;
3302 default:
3303 this.Core.UnexpectedAttribute(node, attrib);
3304 break;
3305 }
3306 }
3307 else
3308 {
3309 this.Core.ParseExtensionAttribute(node, attrib);
3310 }
3311 }
3312
3313 if (null == id)
3314 {
3315 id = this.Core.CreateIdentifierFromFilename(name);
3316 }
3317
3318 if (null == name)
3319 {
3320 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
3321 }
3322
3323 // get the ServiceControl arguments
3324 foreach (var child in node.Elements())
3325 {
3326 if (CompilerCore.WixNamespace == child.Name.Namespace)
3327 {
3328 switch (child.Name.LocalName)
3329 {
3330 case "ServiceArgument":
3331 arguments = this.ParseServiceArgument(child, arguments);
3332 break;
3333 default:
3334 this.Core.UnexpectedElement(node, child);
3335 break;
3336 }
3337 }
3338 else
3339 {
3340 this.Core.ParseExtensionElement(node, child);
3341 }
3342 }
3343
3344 if (!this.Core.EncounteredError)
3345 {
3346 this.Core.AddSymbol(new ServiceControlSymbol(sourceLineNumbers, id)
3347 {
3348 Name = name,
3349 InstallRemove = installRemove,
3350 UninstallRemove = uninstallRemove,
3351 InstallStart = installStart,
3352 UninstallStart = uninstallStart,
3353 InstallStop = installStop,
3354 UninstallStop = uninstallStop,
3355 Arguments = arguments,
3356 Wait = wait,
3357 ComponentRef = componentId
3358 });
3359 }
3360 }
3361
3362 private string ParseServiceArgument(XElement node, string arguments)
3363 {
3364 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
3365 string argument = null;
3366
3367 foreach (var attrib in node.Attributes())
3368 {
3369 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
3370 {
3371 switch (attrib.Name.LocalName)
3372 {
3373 case "Value":
3374 argument = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3375 break;
3376 default:
3377 this.Core.UnexpectedAttribute(node, attrib);
3378 break;
3379 }
3380 }
3381 }
3382
3383 if (argument == null)
3384 {
3385 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value"));
3386 }
3387
3388 this.Core.ParseForExtensionElements(node);
3389
3390 return (arguments == null) ? argument : String.Concat(arguments, "[~]", argument);
3391 }
3392
3393 /// <summary>
3394 /// Parses a service dependency element.
3395 /// </summary>
3396 /// <param name="node">Element to parse.</param>
3397 /// <returns>Parsed sevice dependency name.</returns>
3398 private string ParseServiceDependencyElement(XElement node)
3399 {
3400 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
3401 string dependency = null;
3402 var group = false;
3403
3404 foreach (var attrib in node.Attributes())
3405 {
3406 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
3407 {
3408 switch (attrib.Name.LocalName)
3409 {
3410 case "Id":
3411 dependency = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3412 break;
3413 case "Group":
3414 group = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
3415 break;
3416 default:
3417 this.Core.UnexpectedAttribute(node, attrib);
3418 break;
3419 }
3420 }
3421 else
3422 {
3423 this.Core.ParseExtensionAttribute(node, attrib);
3424 }
3425 }
3426
3427 if (null == dependency)
3428 {
3429 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
3430 }
3431
3432 this.Core.ParseForExtensionElements(node);
3433
3434 return group ? String.Concat("+", dependency) : dependency;
3435 }
3436
3437 /// <summary>
3438 /// Parses a service install element.
3439 /// </summary>
3440 /// <param name="node">Element to parse.</param>
3441 /// <param name="componentId">Identifier of parent component.</param>
3442 /// <param name="win64Component"></param>
3443 private void ParseServiceInstallElement(XElement node, string componentId, bool win64Component)
3444 {
3445 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
3446 Identifier id = null;
3447 string account = null;
3448 string arguments = null;
3449 string dependencies = null;
3450 string description = null;
3451 string displayName = null;
3452 var eraseDescription = false;
3453 string loadOrderGroup = null;
3454 string name = null;
3455 string password = null;
3456
3457 var serviceType = ServiceType.OwnProcess;
3458 var startType = ServiceStartType.Demand;
3459 var errorControl = ServiceErrorControl.Normal;
3460 var interactive = false;
3461 var vital = false;
3462
3463 foreach (var attrib in node.Attributes())
3464 {
3465 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
3466 {
3467 switch (attrib.Name.LocalName)
3468 {
3469 case "Id":
3470 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
3471 break;
3472 case "Account":
3473 account = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3474 break;
3475 case "Arguments":
3476 arguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3477 break;
3478 case "Description":
3479 description = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3480 break;
3481 case "DisplayName":
3482 displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3483 break;
3484 case "EraseDescription":
3485 eraseDescription = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
3486 break;
3487 case "ErrorControl":
3488 var errorControlValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3489 switch (errorControlValue)
3490 {
3491 case "ignore":
3492 errorControl = ServiceErrorControl.Ignore;
3493 break;
3494 case "normal":
3495 errorControl = ServiceErrorControl.Normal;
3496 break;
3497 case "critical":
3498 errorControl = ServiceErrorControl.Critical;
3499 break;
3500 case "": // error case handled by GetAttributeValue()
3501 break;
3502 default:
3503 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, errorControlValue, "ignore", "normal", "critical"));
3504 break;
3505 }
3506 break;
3507 case "Interactive":
3508 interactive = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
3509 break;
3510 case "LoadOrderGroup":
3511 loadOrderGroup = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3512 break;
3513 case "Name":
3514 name = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3515 break;
3516 case "Password":
3517 password = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3518 break;
3519 case "Start":
3520 var startValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3521 switch (startValue)
3522 {
3523 case "auto":
3524 startType = ServiceStartType.Auto;
3525 break;
3526 case "demand":
3527 startType = ServiceStartType.Demand;
3528 break;
3529 case "disabled":
3530 startType = ServiceStartType.Disabled;
3531 break;
3532 case "boot":
3533 case "system":
3534 this.Core.Write(ErrorMessages.ValueNotSupported(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, startValue));
3535 break;
3536 case "":
3537 break;
3538 default:
3539 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, startValue, "auto", "demand", "disabled"));
3540 break;
3541 }
3542 break;
3543 case "Type":
3544 var typeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3545 switch (typeValue)
3546 {
3547 case "ownProcess":
3548 serviceType = ServiceType.OwnProcess;
3549 break;
3550 case "shareProcess":
3551 serviceType = ServiceType.ShareProcess;
3552 break;
3553 case "kernelDriver":
3554 case "systemDriver":
3555 this.Core.Write(ErrorMessages.ValueNotSupported(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, typeValue));
3556 break;
3557 case "":
3558 break;
3559 default:
3560 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, node.Name.LocalName, typeValue, "ownProcess", "shareProcess"));
3561 break;
3562 }
3563 break;
3564 case "Vital":
3565 vital = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
3566 break;
3567 default:
3568 this.Core.UnexpectedAttribute(node, attrib);
3569 break;
3570 }
3571 }
3572 else
3573 {
3574 this.Core.ParseExtensionAttribute(node, attrib);
3575 }
3576 }
3577
3578 if (String.IsNullOrEmpty(name))
3579 {
3580 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
3581 }
3582 else if (null == id)
3583 {
3584 id = this.Core.CreateIdentifierFromFilename(name);
3585 }
3586
3587 if (0 == startType)
3588 {
3589 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Start"));
3590 }
3591
3592 if (eraseDescription)
3593 {
3594 description = "[~]";
3595 }
3596
3597 // get the ServiceInstall dependencies and config
3598 foreach (var child in node.Elements())
3599 {
3600 if (CompilerCore.WixNamespace == child.Name.Namespace)
3601 {
3602 switch (child.Name.LocalName)
3603 {
3604 case "PermissionEx":
3605 this.ParsePermissionExElement(child, id.Id, "ServiceInstall");
3606 break;
3607 case "ServiceConfig":
3608 this.ParseServiceConfigElement(child, componentId, name);
3609 break;
3610 case "ServiceConfigFailureActions":
3611 this.ParseServiceConfigFailureActionsElement(child, componentId, name);
3612 break;
3613 case "ServiceDependency":
3614 dependencies = String.Concat(dependencies, this.ParseServiceDependencyElement(child), "[~]");
3615 break;
3616 default:
3617 this.Core.UnexpectedElement(node, child);
3618 break;
3619 }
3620 }
3621 else
3622 {
3623 var context = new Dictionary<string, string>() { { "ServiceInstallId", id?.Id }, { "ServiceInstallName", name }, { "ServiceInstallComponentId", componentId }, { "Win64", win64Component.ToString() } };
3624 this.Core.ParseExtensionElement(node, child, context);
3625 }
3626 }
3627
3628 if (null != dependencies)
3629 {
3630 dependencies = String.Concat(dependencies, "[~]");
3631 }
3632
3633 if (!this.Core.EncounteredError)
3634 {
3635 this.Core.AddSymbol(new ServiceInstallSymbol(sourceLineNumbers, id)
3636 {
3637 Name = name,
3638 DisplayName = displayName,
3639 ServiceType = serviceType,
3640 StartType = startType,
3641 ErrorControl = errorControl,
3642 LoadOrderGroup = loadOrderGroup,
3643 Dependencies = dependencies,
3644 StartName = account,
3645 Password = password,
3646 Arguments = arguments,
3647 ComponentRef = componentId,
3648 Description = description,
3649 Interactive = interactive,
3650 Vital = vital
3651 });
3652 }
3653 }
3654
3655 /// <summary>
3656 /// Parses a SetDirectory element.
3657 /// </summary>
3658 /// <param name="node">Element to parse.</param>
3659 private void ParseSetDirectoryElement(XElement node)
3660 {
3661 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
3662 string actionName = null;
3663 string id = null;
3664 string condition = null;
3665 var executionType = CustomActionExecutionType.Immediate;
3666 var sequences = new[] { SequenceTable.InstallUISequence, SequenceTable.InstallExecuteSequence }; // default to "both"
3667 string value = null;
3668
3669 foreach (var attrib in node.Attributes())
3670 {
3671 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
3672 {
3673 switch (attrib.Name.LocalName)
3674 {
3675 case "Action":
3676 actionName = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3677 break;
3678 case "Condition":
3679 condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3680 break;
3681 case "Id":
3682 id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3683 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, id);
3684 break;
3685 case "Sequence":
3686 var sequenceValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3687 switch (sequenceValue)
3688 {
3689 case "execute":
3690 sequences = new[] { SequenceTable.InstallExecuteSequence };
3691 break;
3692 case "first":
3693 executionType = CustomActionExecutionType.FirstSequence;
3694 break;
3695 case "ui":
3696 sequences = new[] { SequenceTable.InstallUISequence };
3697 break;
3698 case "both":
3699 break;
3700 case "":
3701 break;
3702 default:
3703 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, sequenceValue, "execute", "ui", "both"));
3704 break;
3705 }
3706 break;
3707 case "Value":
3708 value = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3709 break;
3710 default:
3711 this.Core.UnexpectedAttribute(node, attrib);
3712 break;
3713 }
3714 }
3715 else
3716 {
3717 this.Core.ParseExtensionAttribute(node, attrib);
3718 }
3719 }
3720
3721 if (null == id)
3722 {
3723 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
3724 }
3725 else if (String.IsNullOrEmpty(actionName))
3726 {
3727 actionName = String.Concat("Set", id);
3728 }
3729
3730 if (null == value)
3731 {
3732 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value"));
3733 }
3734
3735 this.Core.ParseForExtensionElements(node);
3736
3737 if (!this.Core.EncounteredError)
3738 {
3739 this.Core.AddSymbol(new CustomActionSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, actionName))
3740 {
3741 ExecutionType = executionType,
3742 SourceType = CustomActionSourceType.Directory,
3743 TargetType = CustomActionTargetType.TextData,
3744 Source = id,
3745 Target = value
3746 });
3747
3748 foreach (var sequence in sequences)
3749 {
3750 this.Core.ScheduleActionSymbol(sourceLineNumbers, AccessModifier.Global, sequence, actionName, condition, afterAction: "CostInitialize");
3751 }
3752 }
3753 }
3754
3755 /// <summary>
3756 /// Parses a SetProperty element.
3757 /// </summary>
3758 /// <param name="node">Element to parse.</param>
3759 private void ParseSetPropertyElement(XElement node)
3760 {
3761 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
3762 string actionName = null;
3763 string id = null;
3764 string condition = null;
3765 string afterAction = null;
3766 string beforeAction = null;
3767 var executionType = CustomActionExecutionType.Immediate;
3768 var sequences = new[] { SequenceTable.InstallUISequence, SequenceTable.InstallExecuteSequence }; // default to "both"
3769 string value = null;
3770
3771 foreach (var attrib in node.Attributes())
3772 {
3773 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
3774 {
3775 switch (attrib.Name.LocalName)
3776 {
3777 case "Action":
3778 actionName = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3779 break;
3780 case "Id":
3781 id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3782 break;
3783 case "Condition":
3784 condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3785 break;
3786 case "After":
3787 afterAction = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3788 break;
3789 case "Before":
3790 beforeAction = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3791 break;
3792 case "Sequence":
3793 var sequenceValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3794 switch (sequenceValue)
3795 {
3796 case "execute":
3797 sequences = new[] { SequenceTable.InstallExecuteSequence };
3798 break;
3799 case "first":
3800 executionType = CustomActionExecutionType.FirstSequence;
3801 break;
3802 case "ui":
3803 sequences = new[] { SequenceTable.InstallUISequence };
3804 break;
3805 case "both":
3806 break;
3807 case "":
3808 break;
3809 default:
3810 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, sequenceValue, "execute", "ui", "both"));
3811 break;
3812 }
3813 break;
3814 case "Value":
3815 value = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty);
3816 break;
3817 default:
3818 this.Core.UnexpectedAttribute(node, attrib);
3819 break;
3820 }
3821 }
3822 else
3823 {
3824 this.Core.ParseExtensionAttribute(node, attrib);
3825 }
3826 }
3827
3828 if (null == id)
3829 {
3830 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
3831 }
3832 else if (String.IsNullOrEmpty(actionName))
3833 {
3834 actionName = String.Concat("Set", id);
3835 }
3836
3837 if (null == value)
3838 {
3839 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value"));
3840 }
3841
3842 if (null != beforeAction && null != afterAction)
3843 {
3844 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "After", "Before"));
3845 }
3846 else if (null == beforeAction && null == afterAction)
3847 {
3848 this.Core.Write(ErrorMessages.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "After", "Before", "Id"));
3849 }
3850
3851 this.Core.ParseForExtensionElements(node);
3852
3853 // add the row and any references needed
3854 if (!this.Core.EncounteredError)
3855 {
3856 // action that is scheduled to occur before/after itself
3857 if (beforeAction == actionName)
3858 {
3859 this.Core.Write(ErrorMessages.ActionScheduledRelativeToItself(sourceLineNumbers, node.Name.LocalName, "Before", beforeAction));
3860 }
3861 else if (afterAction == actionName)
3862 {
3863 this.Core.Write(ErrorMessages.ActionScheduledRelativeToItself(sourceLineNumbers, node.Name.LocalName, "After", afterAction));
3864 }
3865
3866 this.Core.AddSymbol(new CustomActionSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, actionName))
3867 {
3868 ExecutionType = executionType,
3869 SourceType = CustomActionSourceType.Property,
3870 TargetType = CustomActionTargetType.TextData,
3871 Source = id,
3872 Target = value,
3873 });
3874
3875 foreach (var sequence in sequences)
3876 {
3877 this.Core.ScheduleActionSymbol(sourceLineNumbers, AccessModifier.Global, sequence, actionName, condition, beforeAction, afterAction);
3878 }
3879 }
3880 }
3881
3882 /// <summary>
3883 /// Parses a SFP catalog element.
3884 /// </summary>
3885 /// <param name="node">Element to parse.</param>
3886 /// <param name="parentSFPCatalog">Parent SFPCatalog.</param>
3887 private void ParseSFPFileElement(XElement node, string parentSFPCatalog)
3888 {
3889 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
3890 string id = null;
3891
3892 foreach (var attrib in node.Attributes())
3893 {
3894 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
3895 {
3896 switch (attrib.Name.LocalName)
3897 {
3898 case "Id":
3899 id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3900 break;
3901 default:
3902 this.Core.UnexpectedAttribute(node, attrib);
3903 break;
3904 }
3905 }
3906 else
3907 {
3908 this.Core.ParseExtensionAttribute(node, attrib);
3909 }
3910 }
3911
3912 if (null == id)
3913 {
3914 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
3915 }
3916
3917 this.Core.ParseForExtensionElements(node);
3918
3919 if (!this.Core.EncounteredError)
3920 {
3921 this.Core.AddSymbol(new FileSFPCatalogSymbol(sourceLineNumbers)
3922 {
3923 FileRef = id,
3924 SFPCatalogRef = parentSFPCatalog
3925 });
3926 }
3927 }
3928
3929 /// <summary>
3930 /// Parses a SFP catalog element.
3931 /// </summary>
3932 /// <param name="node">Element to parse.</param>
3933 /// <param name="parentSFPCatalog">Parent SFPCatalog.</param>
3934 private void ParseSFPCatalogElement(XElement node, ref string parentSFPCatalog)
3935 {
3936 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
3937 string parentName = null;
3938 string dependency = null;
3939 string name = null;
3940 string sourceFile = null;
3941
3942 foreach (var attrib in node.Attributes())
3943 {
3944 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
3945 {
3946 switch (attrib.Name.LocalName)
3947 {
3948 case "Dependency":
3949 dependency = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3950 break;
3951 case "Name":
3952 name = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false);
3953 parentSFPCatalog = name;
3954 break;
3955 case "SourceFile":
3956 sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
3957 break;
3958 default:
3959 this.Core.UnexpectedAttribute(node, attrib);
3960 break;
3961 }
3962 }
3963 else
3964 {
3965 this.Core.ParseExtensionAttribute(node, attrib);
3966 }
3967 }
3968
3969 if (null == name)
3970 {
3971 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
3972 }
3973
3974 if (null == sourceFile)
3975 {
3976 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile"));
3977 }
3978
3979 foreach (var child in node.Elements())
3980 {
3981 if (CompilerCore.WixNamespace == child.Name.Namespace)
3982 {
3983 switch (child.Name.LocalName)
3984 {
3985 case "SFPCatalog":
3986 this.ParseSFPCatalogElement(child, ref parentName);
3987 if (null != dependency && parentName == dependency)
3988 {
3989 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Dependency"));
3990 }
3991 dependency = parentName;
3992 break;
3993 case "SFPFile":
3994 this.ParseSFPFileElement(child, name);
3995 break;
3996 default:
3997 this.Core.UnexpectedElement(node, child);
3998 break;
3999 }
4000 }
4001 else
4002 {
4003 this.Core.ParseExtensionElement(node, child);
4004 }
4005 }
4006
4007 if (null == dependency)
4008 {
4009 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Dependency"));
4010 }
4011
4012 if (!this.Core.EncounteredError)
4013 {
4014 this.Core.AddSymbol(new SFPCatalogSymbol(sourceLineNumbers)
4015 {
4016 SFPCatalog = name,
4017 Catalog = sourceFile,
4018 Dependency = dependency
4019 });
4020 }
4021 }
4022
4023 /// <summary>
4024 /// Parses a shortcut element.
4025 /// </summary>
4026 /// <param name="node">Element to parse.</param>
4027 /// <param name="componentId">Identifer for parent component.</param>
4028 /// <param name="parentElementLocalName">Local name of parent element.</param>
4029 /// <param name="defaultTarget">Default identifier of parent (which is usually the target).</param>
4030 /// <param name="parentKeyPath">Flag to indicate whether the parent element is the keypath of a component or not (will only be true for file parent elements).</param>
4031 private void ParseShortcutElement(XElement node, string componentId, string parentElementLocalName, string defaultTarget, YesNoType parentKeyPath)
4032 {
4033 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
4034 Identifier id = null;
4035 var advertise = false;
4036 string arguments = null;
4037 string description = null;
4038 string descriptionResourceDll = null;
4039 int? descriptionResourceId = null;
4040 string directoryId = null;
4041 string subdirectory = null;
4042 string displayResourceDll = null;
4043 int? displayResourceId = null;
4044 int? hotkey = null;
4045 string icon = null;
4046 int? iconIndex = null;
4047 string name = null;
4048 string shortName = null;
4049 ShortcutShowType? show = null;
4050 string target = null;
4051 string workingDirectoryId = null;
4052 string workingSubdirectory = null;
4053
4054 foreach (var attrib in node.Attributes())
4055 {
4056 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
4057 {
4058 switch (attrib.Name.LocalName)
4059 {
4060 case "Id":
4061 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
4062 break;
4063 case "Advertise":
4064 advertise = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
4065 break;
4066 case "Arguments":
4067 arguments = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4068 break;
4069 case "Description":
4070 description = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4071 break;
4072 case "DescriptionResourceDll":
4073 descriptionResourceDll = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4074 break;
4075 case "DescriptionResourceId":
4076 descriptionResourceId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue);
4077 break;
4078 case "Directory":
4079 directoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
4080 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, directoryId);
4081 break;
4082 case "Subdirectory":
4083 subdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true);
4084 break;
4085 case "DisplayResourceDll":
4086 displayResourceDll = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4087 break;
4088 case "DisplayResourceId":
4089 displayResourceId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue);
4090 break;
4091 case "Hotkey":
4092 hotkey = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue);
4093 break;
4094 case "Icon":
4095 icon = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
4096 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Icon, icon);
4097 break;
4098 case "IconIndex":
4099 iconIndex = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int16.MinValue + 1, Int16.MaxValue);
4100 break;
4101 case "Name":
4102 name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false);
4103 break;
4104 case "ShortName":
4105 shortName = this.Core.GetAttributeShortFilename(sourceLineNumbers, attrib, false);
4106 break;
4107 case "Show":
4108 var showValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4109 switch (showValue)
4110 {
4111 case "normal":
4112 show = ShortcutShowType.Normal;
4113 break;
4114 case "maximized":
4115 show = ShortcutShowType.Maximized;
4116 break;
4117 case "minimized":
4118 show = ShortcutShowType.Minimized;
4119 break;
4120 case "":
4121 break;
4122 default:
4123 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Show", showValue, "normal", "maximized", "minimized"));
4124 break;
4125 }
4126 break;
4127 case "Target":
4128 target = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4129 break;
4130 case "WorkingDirectory":
4131 workingDirectoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
4132 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, workingDirectoryId);
4133 break;
4134 case "WorkingSubdirectory":
4135 workingSubdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true);
4136 break;
4137 default:
4138 this.Core.UnexpectedAttribute(node, attrib);
4139 break;
4140 }
4141 }
4142 else
4143 {
4144 this.Core.ParseExtensionAttribute(node, attrib);
4145 }
4146 }
4147
4148 if (advertise && null != target)
4149 {
4150 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Target", "Advertise", "yes"));
4151 }
4152
4153 if (null == directoryId)
4154 {
4155 if ("Component" == parentElementLocalName)
4156 {
4157 directoryId = defaultTarget;
4158 }
4159 else
4160 {
4161 this.Core.Write(ErrorMessages.ExpectedAttributeWhenElementNotUnderElement(sourceLineNumbers, node.Name.LocalName, "Directory", "Component"));
4162 }
4163 }
4164
4165 directoryId = this.HandleSubdirectory(sourceLineNumbers, node, directoryId, subdirectory, "Directory", "Subdirectory");
4166
4167 if (null != descriptionResourceDll)
4168 {
4169 if (!descriptionResourceId.HasValue)
4170 {
4171 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DescriptionResourceDll", "DescriptionResourceId"));
4172 }
4173 }
4174 else
4175 {
4176 if (descriptionResourceId.HasValue)
4177 {
4178 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DescriptionResourceId", "DescriptionResourceDll"));
4179 }
4180 }
4181
4182 if (null != displayResourceDll)
4183 {
4184 if (!displayResourceId.HasValue)
4185 {
4186 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisplayResourceDll", "DisplayResourceId"));
4187 }
4188 }
4189 else
4190 {
4191 if (displayResourceId.HasValue)
4192 {
4193 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisplayResourceId", "DisplayResourceDll"));
4194 }
4195 }
4196
4197 if (null == name)
4198 {
4199 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
4200 }
4201
4202 workingDirectoryId = this.HandleSubdirectory(sourceLineNumbers, node, workingDirectoryId, workingSubdirectory, "WorkingDirectory", "WorkingSubdirectory");
4203
4204 if ("Component" != parentElementLocalName && null != target)
4205 {
4206 this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "Target", parentElementLocalName));
4207 }
4208
4209 if (null == id)
4210 {
4211 id = this.Core.CreateIdentifier("sct", directoryId, LowercaseOrNull(name));
4212 }
4213
4214 foreach (var child in node.Elements())
4215 {
4216 if (CompilerCore.WixNamespace == child.Name.Namespace)
4217 {
4218 switch (child.Name.LocalName)
4219 {
4220 case "Icon":
4221 icon = this.ParseIconElement(child);
4222 break;
4223 case "ShortcutProperty":
4224 this.ParseShortcutPropertyElement(child, id.Id);
4225 break;
4226 default:
4227 this.Core.UnexpectedElement(node, child);
4228 break;
4229 }
4230 }
4231 else
4232 {
4233 this.Core.ParseExtensionElement(node, child);
4234 }
4235 }
4236
4237 if (!this.Core.EncounteredError)
4238 {
4239 if (advertise)
4240 {
4241 if (YesNoType.Yes != parentKeyPath && "Component" != parentElementLocalName)
4242 {
4243 this.Core.Write(WarningMessages.UnclearShortcut(sourceLineNumbers, id.Id, componentId, defaultTarget));
4244 }
4245
4246 target = Guid.Empty.ToString("B");
4247 }
4248 else if (null != target)
4249 {
4250 }
4251 else if ("Component" == parentElementLocalName || "CreateFolder" == parentElementLocalName)
4252 {
4253 target = "[" + defaultTarget + "]";
4254 }
4255 else if ("File" == parentElementLocalName)
4256 {
4257 target = "[#" + defaultTarget + "]";
4258 }
4259
4260 this.Core.AddSymbol(new ShortcutSymbol(sourceLineNumbers, id)
4261 {
4262 DirectoryRef = directoryId,
4263 Name = name,
4264 ShortName = shortName,
4265 ComponentRef = componentId,
4266 Target = target,
4267 Arguments = arguments,
4268 Description = description,
4269 Hotkey = hotkey,
4270 IconRef = icon,
4271 IconIndex = iconIndex,
4272 Show = show,
4273 WorkingDirectory = workingDirectoryId,
4274 DisplayResourceDll = displayResourceDll,
4275 DisplayResourceId = displayResourceId,
4276 DescriptionResourceDll = descriptionResourceDll,
4277 DescriptionResourceId = descriptionResourceId,
4278 });
4279 }
4280 }
4281
4282 /// <summary>
4283 /// Parses a shortcut property element.
4284 /// </summary>
4285 /// <param name="node">Element to parse.</param>
4286 /// <param name="shortcutId"></param>
4287 private void ParseShortcutPropertyElement(XElement node, string shortcutId)
4288 {
4289 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
4290 Identifier id = null;
4291 string key = null;
4292 string value = null;
4293
4294 foreach (var attrib in node.Attributes())
4295 {
4296 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
4297 {
4298 switch (attrib.Name.LocalName)
4299 {
4300 case "Id":
4301 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
4302 break;
4303 case "Key":
4304 key = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4305 break;
4306 case "Value":
4307 value = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4308 break;
4309 default:
4310 this.Core.UnexpectedAttribute(node, attrib);
4311 break;
4312 }
4313 }
4314 else
4315 {
4316 this.Core.ParseExtensionAttribute(node, attrib);
4317 }
4318 }
4319
4320 if (String.IsNullOrEmpty(key))
4321 {
4322 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key"));
4323 }
4324 else if (null == id)
4325 {
4326 id = this.Core.CreateIdentifier("scp", shortcutId, key.ToUpperInvariant());
4327 }
4328
4329 if (String.IsNullOrEmpty(value))
4330 {
4331 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value"));
4332 }
4333
4334 this.Core.ParseForExtensionElements(node);
4335
4336 if (!this.Core.EncounteredError)
4337 {
4338 this.Core.AddSymbol(new MsiShortcutPropertySymbol(sourceLineNumbers, id)
4339 {
4340 ShortcutRef = shortcutId,
4341 PropertyKey = key,
4342 PropVariantValue = value
4343 });
4344 }
4345 }
4346
4347 /// <summary>
4348 /// Parses a typelib element.
4349 /// </summary>
4350 /// <param name="node">Element to parse.</param>
4351 /// <param name="componentId">Identifier of parent component.</param>
4352 /// <param name="fileServer">Identifier of file that acts as typelib server.</param>
4353 /// <param name="win64Component">true if the component is 64-bit.</param>
4354 private void ParseTypeLibElement(XElement node, string componentId, string fileServer, bool win64Component)
4355 {
4356 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
4357 string id = null;
4358 var advertise = YesNoType.NotSet;
4359 var cost = CompilerConstants.IntegerNotSet;
4360 string description = null;
4361 var flags = 0;
4362 string helpDirectoryId = null;
4363 string helpSubdirectory = null;
4364 var language = CompilerConstants.IntegerNotSet;
4365 var majorVersion = CompilerConstants.IntegerNotSet;
4366 var minorVersion = CompilerConstants.IntegerNotSet;
4367 var resourceId = CompilerConstants.LongNotSet;
4368
4369 foreach (var attrib in node.Attributes())
4370 {
4371 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
4372 {
4373 switch (attrib.Name.LocalName)
4374 {
4375 case "Id":
4376 id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
4377 break;
4378 case "Advertise":
4379 advertise = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
4380 break;
4381 case "Control":
4382 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
4383 {
4384 flags |= 2;
4385 }
4386 break;
4387 case "Cost":
4388 cost = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int32.MaxValue);
4389 break;
4390 case "Description":
4391 description = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4392 break;
4393 case "HasDiskImage":
4394 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
4395 {
4396 flags |= 8;
4397 }
4398 break;
4399 case "HelpDirectory":
4400 helpDirectoryId = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
4401 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, helpDirectoryId);
4402 break;
4403 case "HelpSubdirectory":
4404 helpSubdirectory = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, allowRelative: true);
4405 break;
4406 case "Hidden":
4407 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
4408 {
4409 flags |= 4;
4410 }
4411 break;
4412 case "Language":
4413 language = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue);
4414 break;
4415 case "MajorVersion":
4416 majorVersion = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, UInt16.MaxValue);
4417 break;
4418 case "MinorVersion":
4419 minorVersion = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Byte.MaxValue);
4420 break;
4421 case "ResourceId":
4422 resourceId = this.Core.GetAttributeLongValue(sourceLineNumbers, attrib, Int32.MinValue, Int32.MaxValue);
4423 break;
4424 case "Restricted":
4425 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
4426 {
4427 flags |= 1;
4428 }
4429 break;
4430 default:
4431 this.Core.UnexpectedAttribute(node, attrib);
4432 break;
4433 }
4434 }
4435 else
4436 {
4437 this.Core.ParseExtensionAttribute(node, attrib);
4438 }
4439 }
4440
4441 if (null == id)
4442 {
4443 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
4444 }
4445
4446 if (CompilerConstants.IntegerNotSet == language)
4447 {
4448 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Language"));
4449 language = CompilerConstants.IllegalInteger;
4450 }
4451
4452 helpDirectoryId = this.HandleSubdirectory(sourceLineNumbers, node, helpDirectoryId, helpSubdirectory, "HelpDirectory", "HelpSubdirectory");
4453
4454 // build up the typelib version string for the registry if the major or minor version was specified
4455 string registryVersion = null;
4456 if (CompilerConstants.IntegerNotSet != majorVersion || CompilerConstants.IntegerNotSet != minorVersion)
4457 {
4458 if (CompilerConstants.IntegerNotSet != majorVersion)
4459 {
4460 registryVersion = majorVersion.ToString("x", CultureInfo.InvariantCulture.NumberFormat);
4461 }
4462 else
4463 {
4464 registryVersion = "0";
4465 }
4466
4467 if (CompilerConstants.IntegerNotSet != minorVersion)
4468 {
4469 registryVersion = String.Concat(registryVersion, ".", minorVersion.ToString("x", CultureInfo.InvariantCulture.NumberFormat));
4470 }
4471 else
4472 {
4473 registryVersion = String.Concat(registryVersion, ".0");
4474 }
4475 }
4476
4477 // if the advertise state has not been set, default to non-advertised
4478 if (YesNoType.NotSet == advertise)
4479 {
4480 advertise = YesNoType.No;
4481 }
4482
4483 foreach (var child in node.Elements())
4484 {
4485 if (CompilerCore.WixNamespace == child.Name.Namespace)
4486 {
4487 switch (child.Name.LocalName)
4488 {
4489 case "AppId":
4490 this.ParseAppIdElement(child, componentId, YesNoType.NotSet, fileServer, id, registryVersion);
4491 break;
4492 case "Class":
4493 this.ParseClassElement(child, componentId, YesNoType.NotSet, fileServer, id, registryVersion, null);
4494 break;
4495 case "Interface":
4496 this.ParseInterfaceElement(child, componentId, null, null, id, registryVersion);
4497 break;
4498 default:
4499 this.Core.UnexpectedElement(node, child);
4500 break;
4501 }
4502 }
4503 else
4504 {
4505 this.Core.ParseExtensionElement(node, child);
4506 }
4507 }
4508
4509
4510 if (YesNoType.Yes == advertise)
4511 {
4512 if (CompilerConstants.LongNotSet != resourceId)
4513 {
4514 this.Core.Write(ErrorMessages.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "ResourceId"));
4515 }
4516
4517 if (0 != flags)
4518 {
4519 if (0x1 == (flags & 0x1))
4520 {
4521 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Restricted", "Advertise", "yes"));
4522 }
4523
4524 if (0x2 == (flags & 0x2))
4525 {
4526 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Control", "Advertise", "yes"));
4527 }
4528
4529 if (0x4 == (flags & 0x4))
4530 {
4531 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Hidden", "Advertise", "yes"));
4532 }
4533
4534 if (0x8 == (flags & 0x8))
4535 {
4536 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "HasDiskImage", "Advertise", "yes"));
4537 }
4538 }
4539
4540 if (!this.Core.EncounteredError)
4541 {
4542 var symbol = this.Core.AddSymbol(new TypeLibSymbol(sourceLineNumbers)
4543 {
4544 LibId = id,
4545 Language = language,
4546 ComponentRef = componentId,
4547 Description = description,
4548 DirectoryRef = helpDirectoryId,
4549 FeatureRef = Guid.Empty.ToString("B")
4550 });
4551
4552 if (CompilerConstants.IntegerNotSet != majorVersion || CompilerConstants.IntegerNotSet != minorVersion)
4553 {
4554 symbol.Version = (CompilerConstants.IntegerNotSet != majorVersion ? majorVersion * 256 : 0) + (CompilerConstants.IntegerNotSet != minorVersion ? minorVersion : 0);
4555 }
4556
4557 if (CompilerConstants.IntegerNotSet != cost)
4558 {
4559 symbol.Cost = cost;
4560 }
4561 }
4562 }
4563 else if (YesNoType.No == advertise)
4564 {
4565 if (CompilerConstants.IntegerNotSet != cost && CompilerConstants.IllegalInteger != cost)
4566 {
4567 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Cost", "Advertise", "no"));
4568 }
4569
4570 if (null == fileServer)
4571 {
4572 this.Core.Write(ErrorMessages.MissingTypeLibFile(sourceLineNumbers, node.Name.LocalName, "File"));
4573 }
4574
4575 if (null == registryVersion)
4576 {
4577 this.Core.Write(ErrorMessages.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "MajorVersion", "MinorVersion", "Advertise", "no"));
4578 }
4579
4580 // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion], (Default) = [Description]
4581 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}", id, registryVersion), null, description, componentId);
4582
4583 // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion]\[Language]\[win16|win32|win64], (Default) = [TypeLibPath]\[ResourceId]
4584 var path = String.Concat("[#", fileServer, "]");
4585 if (CompilerConstants.LongNotSet != resourceId)
4586 {
4587 path = String.Concat(path, Path.DirectorySeparatorChar, resourceId.ToString(CultureInfo.InvariantCulture.NumberFormat));
4588 }
4589 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}\{2}\{3}", id, registryVersion, language, (win64Component ? "win64" : "win32")), null, path, componentId);
4590
4591 // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion]\FLAGS, (Default) = [TypeLibFlags]
4592 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}\FLAGS", id, registryVersion), null, flags.ToString(CultureInfo.InvariantCulture.NumberFormat), componentId);
4593
4594 if (null != helpDirectoryId)
4595 {
4596 // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion]\HELPDIR, (Default) = [HelpDirectory]
4597 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}\HELPDIR", id, registryVersion), null, String.Concat("[", helpDirectoryId, "]"), componentId);
4598 }
4599 }
4600 }
4601
4602 /// <summary>
4603 /// Parses an upgrade element.
4604 /// </summary>
4605 /// <param name="node">Element to parse.</param>
4606 private void ParseUpgradeElement(XElement node)
4607 {
4608 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
4609 string id = null;
4610
4611 foreach (var attrib in node.Attributes())
4612 {
4613 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
4614 {
4615 switch (attrib.Name.LocalName)
4616 {
4617 case "Id":
4618 id = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
4619 break;
4620 default:
4621 this.Core.UnexpectedAttribute(node, attrib);
4622 break;
4623 }
4624 }
4625 else
4626 {
4627 this.Core.ParseExtensionAttribute(node, attrib);
4628 }
4629 }
4630
4631 if (null == id)
4632 {
4633 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
4634 }
4635
4636 // process the UpgradeVersion children here
4637 foreach (var child in node.Elements())
4638 {
4639 if (CompilerCore.WixNamespace == child.Name.Namespace)
4640 {
4641 var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
4642
4643 switch (child.Name.LocalName)
4644 {
4645 case "Property":
4646 this.ParsePropertyElement(child);
4647 this.Core.Write(WarningMessages.DeprecatedUpgradeProperty(childSourceLineNumbers));
4648 break;
4649 case "UpgradeVersion":
4650 this.ParseUpgradeVersionElement(child, id);
4651 break;
4652 default:
4653 this.Core.UnexpectedElement(node, child);
4654 break;
4655 }
4656 }
4657 else
4658 {
4659 this.Core.ParseExtensionElement(node, child);
4660 }
4661 }
4662
4663 // No rows created here. All row creation is done in ParseUpgradeVersionElement.
4664 }
4665
4666 /// <summary>
4667 /// Parse upgrade version element.
4668 /// </summary>
4669 /// <param name="node">Element to parse.</param>
4670 /// <param name="upgradeId">Upgrade code.</param>
4671 private void ParseUpgradeVersionElement(XElement node, string upgradeId)
4672 {
4673 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
4674
4675 string actionProperty = null;
4676 string language = null;
4677 string maximum = null;
4678 string minimum = null;
4679 var excludeLanguages = false;
4680 var ignoreFailures = false;
4681 var includeMax = false;
4682 var includeMin = true;
4683 var migrateFeatures = false;
4684 var onlyDetect = false;
4685 string removeFeatures = null;
4686
4687 foreach (var attrib in node.Attributes())
4688 {
4689 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
4690 {
4691 switch (attrib.Name.LocalName)
4692 {
4693 case "ExcludeLanguages":
4694 excludeLanguages = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
4695 break;
4696 case "IgnoreRemoveFailure":
4697 ignoreFailures = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
4698 break;
4699 case "IncludeMaximum":
4700 includeMax = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
4701 break;
4702 case "IncludeMinimum": // this is "yes" by default
4703 includeMin = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
4704 break;
4705 case "Language":
4706 language = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4707 break;
4708 case "Minimum":
4709 minimum = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib);
4710 break;
4711 case "Maximum":
4712 maximum = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib);
4713 break;
4714 case "MigrateFeatures":
4715 migrateFeatures = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
4716 break;
4717 case "OnlyDetect":
4718 onlyDetect = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
4719 break;
4720 case "Property":
4721 actionProperty = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
4722 break;
4723 case "RemoveFeatures":
4724 removeFeatures = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4725 break;
4726 default:
4727 this.Core.UnexpectedAttribute(node, attrib);
4728 break;
4729 }
4730 }
4731 else
4732 {
4733 this.Core.ParseExtensionAttribute(node, attrib);
4734 }
4735 }
4736
4737 if (null == actionProperty)
4738 {
4739 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property"));
4740 }
4741 else if (actionProperty.ToUpper(CultureInfo.InvariantCulture) != actionProperty)
4742 {
4743 this.Core.Write(ErrorMessages.SecurePropertyNotUppercase(sourceLineNumbers, node.Name.LocalName, "Property", actionProperty));
4744 }
4745
4746 if (null == minimum && null == maximum)
4747 {
4748 this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "Minimum", "Maximum"));
4749 }
4750
4751 this.Core.ParseForExtensionElements(node);
4752
4753 if (!this.Core.EncounteredError)
4754 {
4755 this.Core.AddSymbol(new UpgradeSymbol(sourceLineNumbers)
4756 {
4757 UpgradeCode = upgradeId,
4758 VersionMin = minimum,
4759 VersionMax = maximum,
4760 Language = language,
4761 ExcludeLanguages = excludeLanguages,
4762 IgnoreRemoveFailures = ignoreFailures,
4763 VersionMaxInclusive = includeMax,
4764 VersionMinInclusive = includeMin,
4765 MigrateFeatures = migrateFeatures,
4766 OnlyDetect = onlyDetect,
4767 Remove = removeFeatures,
4768 ActionProperty = actionProperty
4769 });
4770
4771 // Ensure that RemoveExistingProducts is authored in InstallExecuteSequence
4772 // if at least one row in Upgrade table lacks the OnlyDetect attribute.
4773 if (!onlyDetect)
4774 {
4775 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixAction, "InstallExecuteSequence", "RemoveExistingProducts");
4776 }
4777 }
4778 }
4779
4780 /// <summary>
4781 /// Parses a verb element.
4782 /// </summary>
4783 /// <param name="node">Element to parse.</param>
4784 /// <param name="extension">Extension verb is releated to.</param>
4785 /// <param name="progId">Optional progId for extension.</param>
4786 /// <param name="componentId">Identifier for parent component.</param>
4787 /// <param name="advertise">Flag if verb is advertised.</param>
4788 private void ParseVerbElement(XElement node, string extension, string progId, string componentId, YesNoType advertise)
4789 {
4790 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
4791 string id = null;
4792 string argument = null;
4793 string command = null;
4794 var sequence = CompilerConstants.IntegerNotSet;
4795 string targetFile = null;
4796 string targetProperty = null;
4797
4798 foreach (var attrib in node.Attributes())
4799 {
4800 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
4801 {
4802 switch (attrib.Name.LocalName)
4803 {
4804 case "Id":
4805 id = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4806 break;
4807 case "Argument":
4808 argument = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4809 break;
4810 case "Command":
4811 command = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4812 break;
4813 case "Sequence":
4814 sequence = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue);
4815 break;
4816 case "TargetFile":
4817 targetFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4818 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.File, targetFile);
4819 break;
4820 case "TargetProperty":
4821 targetProperty = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
4822 break;
4823 default:
4824 this.Core.UnexpectedAttribute(node, attrib);
4825 break;
4826 }
4827 }
4828 else
4829 {
4830 this.Core.ParseExtensionAttribute(node, attrib);
4831 }
4832 }
4833
4834 if (null == id)
4835 {
4836 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
4837 }
4838
4839 if (null != targetFile && null != targetProperty)
4840 {
4841 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "TargetFile", "TargetProperty"));
4842 }
4843
4844 this.Core.ParseForExtensionElements(node);
4845
4846 if (YesNoType.Yes == advertise)
4847 {
4848 if (null != targetFile)
4849 {
4850 this.Core.Write(ErrorMessages.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "TargetFile"));
4851 }
4852
4853 if (null != targetProperty)
4854 {
4855 this.Core.Write(ErrorMessages.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "TargetProperty"));
4856 }
4857
4858 if (!this.Core.EncounteredError)
4859 {
4860 var symbol = this.Core.AddSymbol(new VerbSymbol(sourceLineNumbers)
4861 {
4862 ExtensionRef = extension,
4863 Verb = id,
4864 Command = command,
4865 Argument = argument,
4866 });
4867
4868 if (CompilerConstants.IntegerNotSet != sequence)
4869 {
4870 symbol.Sequence = sequence;
4871 }
4872 }
4873 }
4874 else if (YesNoType.No == advertise)
4875 {
4876 if (CompilerConstants.IntegerNotSet != sequence)
4877 {
4878 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Sequence", "Advertise", "no"));
4879 }
4880
4881 if (null == targetFile && null == targetProperty)
4882 {
4883 this.Core.Write(ErrorMessages.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "TargetFile", "TargetProperty", "Advertise", "no"));
4884 }
4885
4886 string target = null;
4887 if (null != targetFile)
4888 {
4889 target = String.Concat("\"[#", targetFile, "]\"");
4890 }
4891 else if (null != targetProperty)
4892 {
4893 target = String.Concat("\"[", targetProperty, "]\"");
4894 }
4895
4896 if (null != argument)
4897 {
4898 target = String.Concat(target, " ", argument);
4899 }
4900
4901 var prefix = progId ?? String.Concat(".", extension);
4902
4903 if (null != command)
4904 {
4905 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(prefix, "\\shell\\", id), String.Empty, command, componentId);
4906 }
4907
4908 this.Core.CreateRegistryRow(sourceLineNumbers, RegistryRootType.ClassesRoot, String.Concat(prefix, "\\shell\\", id, "\\command"), String.Empty, target, componentId);
4909 }
4910 }
4911
4912 /// <summary>
4913 /// Parses a WixVariable element.
4914 /// </summary>
4915 /// <param name="node">Element to parse.</param>
4916 private void ParseWixVariableElement(XElement node)
4917 {
4918 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
4919 Identifier id = null;
4920 var overridable = false;
4921 string value = null;
4922
4923 foreach (var attrib in node.Attributes())
4924 {
4925 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
4926 {
4927 switch (attrib.Name.LocalName)
4928 {
4929 case "Id":
4930 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
4931 break;
4932 case "Overridable":
4933 overridable = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib));
4934 break;
4935 case "Value":
4936 value = this.Core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty);
4937 break;
4938 default:
4939 this.Core.UnexpectedAttribute(node, attrib);
4940 break;
4941 }
4942 }
4943 else
4944 {
4945 this.Core.ParseExtensionAttribute(node, attrib);
4946 }
4947 }
4948
4949 if (null == id)
4950 {
4951 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
4952 }
4953
4954 if (null == value)
4955 {
4956 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value"));
4957 }
4958
4959 this.Core.ParseForExtensionElements(node);
4960
4961 if (!this.Core.EncounteredError)
4962 {
4963 this.Core.AddSymbol(new WixVariableSymbol(sourceLineNumbers, id)
4964 {
4965 Value = value,
4966 Overridable = overridable
4967 });
4968 }
4969 }
4970
4971 private CompressionLevel? ParseCompressionLevel(SourceLineNumber sourceLineNumbers, XAttribute attribute)
4972 {
4973 var compressionLevel = this.Core.GetAttributeValue(sourceLineNumbers, attribute);
4974 switch (compressionLevel)
4975 {
4976 case "high":
4977 return CompressionLevel.High;
4978 case "low":
4979 return CompressionLevel.Low;
4980 case "medium":
4981 return CompressionLevel.Medium;
4982 case "mszip":
4983 return CompressionLevel.Mszip;
4984 case "none":
4985 return CompressionLevel.None;
4986 case "":
4987 break;
4988 default:
4989 this.Core.Write(ErrorMessages.IllegalCompressionLevel(sourceLineNumbers, compressionLevel));
4990 break;
4991 }
4992
4993 return null;
4994 }
4995 }
4996}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.Globalization;
9 using System.Xml.Linq;
10 using WixToolset.Data;
11 using WixToolset.Data.Symbols;
12 using WixToolset.Extensibility;
13
14 /// <summary>
15 /// Compiler of the WiX toolset.
16 /// </summary>
17 internal partial class Compiler : ICompiler
18 {
19 /// <summary>
20 /// Parses an patch element.
21 /// </summary>
22 /// <param name="node">The element to parse.</param>
23 private void ParsePatchElement(XElement node)
24 {
25 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
26 string patchId = null;
27 string codepage = null;
28 ////bool versionMismatches = false;
29 ////bool productMismatches = false;
30 var allowRemoval = false;
31 string classification = null;
32 string clientPatchId = null;
33 string description = null;
34 string displayName = null;
35 string comments = null;
36 string manufacturer = null;
37 var minorUpdateTargetRTM = YesNoType.NotSet;
38 string moreInfoUrl = null;
39 var optimizeCA = CompilerConstants.IntegerNotSet;
40 var optimizedInstallMode = YesNoType.NotSet;
41 string targetProductName = null;
42 // string replaceGuids = String.Empty;
43 var apiPatchingSymbolFlags = 0;
44 var optimizePatchSizeForLargeFiles = false;
45
46 foreach (var attrib in node.Attributes())
47 {
48 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
49 {
50 switch (attrib.Name.LocalName)
51 {
52 case "Id":
53 patchId = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, true);
54 break;
55 case "Codepage":
56 codepage = this.Core.GetAttributeLocalizableCodePageValue(sourceLineNumbers, attrib);
57 break;
58 case "AllowMajorVersionMismatches":
59 ////versionMismatches = (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib));
60 break;
61 case "AllowProductCodeMismatches":
62 ////productMismatches = (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib));
63 break;
64 case "AllowRemoval":
65 allowRemoval = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib));
66 break;
67 case "Classification":
68 classification = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
69 break;
70 case "ClientPatchId":
71 clientPatchId = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
72 break;
73 case "Description":
74 description = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
75 break;
76 case "DisplayName":
77 displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
78 break;
79 case "Comments":
80 comments = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
81 break;
82 case "Manufacturer":
83 manufacturer = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
84 break;
85 case "MinorUpdateTargetRTM":
86 minorUpdateTargetRTM = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
87 break;
88 case "MoreInfoURL":
89 moreInfoUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
90 break;
91 case "OptimizedInstallMode":
92 optimizedInstallMode = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
93 break;
94 case "TargetProductName":
95 targetProductName = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
96 break;
97 case "ApiPatchingSymbolNoImagehlpFlag":
98 apiPatchingSymbolFlags |= (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? (int)PatchSymbolFlags.PatchSymbolNoImagehlp : 0;
99 break;
100 case "ApiPatchingSymbolNoFailuresFlag":
101 apiPatchingSymbolFlags |= (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? (int)PatchSymbolFlags.PatchSymbolNoFailures : 0;
102 break;
103 case "ApiPatchingSymbolUndecoratedTooFlag":
104 apiPatchingSymbolFlags |= (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? (int)PatchSymbolFlags.PatchSymbolUndecoratedToo : 0;
105 break;
106 case "OptimizePatchSizeForLargeFiles":
107 optimizePatchSizeForLargeFiles = (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib));
108 break;
109 default:
110 this.Core.UnexpectedAttribute(node, attrib);
111 break;
112 }
113 }
114 else
115 {
116 this.Core.ParseExtensionAttribute(node, attrib);
117 }
118 }
119
120 if (patchId == null || patchId == "*")
121 {
122 // auto-generate at compile time, since this value gets dispersed to several locations
123 patchId = Common.GenerateGuid();
124 }
125 this.activeName = patchId;
126
127 if (null == this.activeName)
128 {
129 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
130 }
131 if (null == classification)
132 {
133 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Classification"));
134 }
135 if (null == clientPatchId)
136 {
137 clientPatchId = String.Concat("_", new Guid(patchId).ToString("N", CultureInfo.InvariantCulture).ToUpper(CultureInfo.InvariantCulture));
138 }
139 if (null == description)
140 {
141 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Description"));
142 }
143 if (null == displayName)
144 {
145 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisplayName"));
146 }
147 if (null == manufacturer)
148 {
149 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Manufacturer"));
150 }
151
152 this.Core.CreateActiveSection(this.activeName, SectionType.Patch, this.Context.CompilationId);
153
154 foreach (var child in node.Elements())
155 {
156 if (CompilerCore.WixNamespace == child.Name.Namespace)
157 {
158 switch (child.Name.LocalName)
159 {
160 case "PatchInformation":
161 this.ParsePatchInformationElement(child);
162 break;
163 case "Media":
164 this.ParseMediaElement(child, patchId);
165 break;
166 case "OptimizeCustomActions":
167 optimizeCA = this.ParseOptimizeCustomActionsElement(child);
168 break;
169 case "PatchFamily":
170 this.ParsePatchFamilyElement(child, ComplexReferenceParentType.Patch, patchId);
171 break;
172 case "PatchFamilyRef":
173 this.ParsePatchFamilyRefElement(child, ComplexReferenceParentType.Patch, patchId);
174 break;
175 case "PatchFamilyGroup":
176 this.ParsePatchFamilyGroupElement(child, ComplexReferenceParentType.Patch, patchId);
177 break;
178 case "PatchFamilyGroupRef":
179 this.ParsePatchFamilyGroupRefElement(child, ComplexReferenceParentType.Patch, patchId);
180 break;
181 case "PatchProperty":
182 this.ParsePatchPropertyElement(child, true);
183 break;
184 case "TargetProductCodes":
185 this.ParseTargetProductCodesElement(child);
186 break;
187 default:
188 this.Core.UnexpectedElement(node, child);
189 break;
190 }
191 }
192 else
193 {
194 this.Core.ParseExtensionElement(node, child);
195 }
196 }
197
198 if (!this.Core.EncounteredError)
199 {
200 this.Core.AddSymbol(new WixPatchSymbol(sourceLineNumbers, new Identifier(AccessModifier.Global, patchId))
201 {
202 Codepage = codepage,
203 ClientPatchId = clientPatchId,
204 OptimizePatchSizeForLargeFiles = optimizePatchSizeForLargeFiles,
205 ApiPatchingSymbolFlags = apiPatchingSymbolFlags,
206 });
207
208 if (allowRemoval)
209 {
210 this.AddMsiPatchMetadata(sourceLineNumbers, null, "AllowRemoval", allowRemoval ? "1" : "0");
211 }
212
213 if (null != classification)
214 {
215 this.AddMsiPatchMetadata(sourceLineNumbers, null, "Classification", classification);
216 }
217
218 // always generate the CreationTimeUTC
219 {
220 this.AddMsiPatchMetadata(sourceLineNumbers, null, "CreationTimeUTC", DateTime.UtcNow.ToString("MM-dd-yy HH:mm", CultureInfo.InvariantCulture));
221 }
222
223 if (null != description)
224 {
225 this.AddMsiPatchMetadata(sourceLineNumbers, null, "Description", description);
226 }
227
228 if (null != displayName)
229 {
230 this.AddMsiPatchMetadata(sourceLineNumbers, null, "DisplayName", displayName);
231 }
232
233 if (null != manufacturer)
234 {
235 this.AddMsiPatchMetadata(sourceLineNumbers, null, "ManufacturerName", manufacturer);
236 }
237
238 if (YesNoType.NotSet != minorUpdateTargetRTM)
239 {
240 this.AddMsiPatchMetadata(sourceLineNumbers, null, "MinorUpdateTargetRTM", YesNoType.Yes == minorUpdateTargetRTM ? "1" : "0");
241 }
242
243 if (null != moreInfoUrl)
244 {
245 this.AddMsiPatchMetadata(sourceLineNumbers, null, "MoreInfoURL", moreInfoUrl);
246 }
247
248 if (CompilerConstants.IntegerNotSet != optimizeCA)
249 {
250 this.AddMsiPatchMetadata(sourceLineNumbers, null, "OptimizeCA", optimizeCA.ToString(CultureInfo.InvariantCulture));
251 }
252
253 if (YesNoType.NotSet != optimizedInstallMode)
254 {
255 this.AddMsiPatchMetadata(sourceLineNumbers, null, "OptimizedInstallMode", YesNoType.Yes == optimizedInstallMode ? "1" : "0");
256 }
257
258 if (null != targetProductName)
259 {
260 this.AddMsiPatchMetadata(sourceLineNumbers, null, "TargetProductName", targetProductName);
261 }
262
263 if (null != comments)
264 {
265 this.AddMsiPatchMetadata(sourceLineNumbers, null, "Comments", comments);
266 }
267 }
268 // TODO: do something with versionMismatches and productMismatches
269 }
270
271 /// <summary>
272 /// Parses the OptimizeCustomActions element.
273 /// </summary>
274 /// <param name="node">Element to parse.</param>
275 /// <returns>The combined integer value for callers to store as appropriate.</returns>
276 private int ParseOptimizeCustomActionsElement(XElement node)
277 {
278 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
279 var optimizeCA = OptimizeCAFlags.None;
280
281 foreach (var attrib in node.Attributes())
282 {
283 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
284 {
285 switch (attrib.Name.LocalName)
286 {
287 case "SkipAssignment":
288 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
289 {
290 optimizeCA |= OptimizeCAFlags.SkipAssignment;
291 }
292 break;
293 case "SkipImmediate":
294 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
295 {
296 optimizeCA |= OptimizeCAFlags.SkipImmediate;
297 }
298 break;
299 case "SkipDeferred":
300 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
301 {
302 optimizeCA |= OptimizeCAFlags.SkipDeferred;
303 }
304 break;
305 default:
306 this.Core.UnexpectedAttribute(node, attrib);
307 break;
308 }
309 }
310 else
311 {
312 this.Core.ParseExtensionAttribute(node, attrib);
313 }
314 }
315
316 return (int)optimizeCA;
317 }
318
319 /// <summary>
320 /// Parses a PatchFamily element.
321 /// </summary>
322 /// <param name="node">The element to parse.</param>
323 /// <param name="parentType"></param>
324 /// <param name="parentId"></param>
325 private void ParsePatchFamilyElement(XElement node, ComplexReferenceParentType parentType, string parentId)
326 {
327 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
328 Identifier id = null;
329 string productCode = null;
330 string version = null;
331 var attributes = 0;
332
333 foreach (var attrib in node.Attributes())
334 {
335 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
336 {
337 switch (attrib.Name.LocalName)
338 {
339 case "Id":
340 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
341 break;
342 case "ProductCode":
343 productCode = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
344 break;
345 case "Version":
346 version = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib);
347 break;
348 case "Supersede":
349 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
350 {
351 attributes |= 0x1;
352 }
353 break;
354 default:
355 this.Core.UnexpectedAttribute(node, attrib);
356 break;
357 }
358 }
359 else
360 {
361 this.Core.ParseExtensionAttribute(node, attrib);
362 }
363 }
364
365 if (null == id)
366 {
367 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
368 id = Identifier.Invalid;
369 }
370
371 if (String.IsNullOrEmpty(version))
372 {
373 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version"));
374 }
375 else if (!CompilerCore.IsValidProductVersion(version))
376 {
377 this.Core.Write(ErrorMessages.InvalidProductVersion(sourceLineNumbers, version));
378 }
379
380 // find unexpected child elements
381 foreach (var child in node.Elements())
382 {
383 if (CompilerCore.WixNamespace == child.Name.Namespace)
384 {
385 switch (child.Name.LocalName)
386 {
387 case "All":
388 this.ParseAllElement(child);
389 break;
390 case "BinaryRef":
391 this.ParsePatchChildRefElement(child, "Binary");
392 break;
393 case "ComponentRef":
394 this.ParsePatchChildRefElement(child, "Component");
395 break;
396 case "CustomActionRef":
397 this.ParsePatchChildRefElement(child, "CustomAction");
398 break;
399 case "DirectoryRef":
400 this.ParsePatchChildRefElement(child, "Directory");
401 break;
402 case "DigitalCertificateRef":
403 this.ParsePatchChildRefElement(child, "MsiDigitalCertificate");
404 break;
405 case "FeatureRef":
406 this.ParsePatchChildRefElement(child, "Feature");
407 break;
408 case "IconRef":
409 this.ParsePatchChildRefElement(child, "Icon");
410 break;
411 case "PropertyRef":
412 this.ParsePatchChildRefElement(child, "Property");
413 break;
414 case "SoftwareTagRef":
415 this.ParseTagRefElement(child);
416 break;
417 case "UIRef":
418 this.ParsePatchChildRefElement(child, "WixUI");
419 break;
420 default:
421 this.Core.UnexpectedElement(node, child);
422 break;
423 }
424 }
425 else
426 {
427 this.Core.ParseExtensionElement(node, child);
428 }
429 }
430
431
432 if (!this.Core.EncounteredError)
433 {
434 this.Core.AddSymbol(new MsiPatchSequenceSymbol(sourceLineNumbers)
435 {
436 PatchFamily = id.Id,
437 ProductCode = productCode,
438 Sequence = version,
439 Attributes = attributes
440 });
441
442 if (ComplexReferenceParentType.Unknown != parentType)
443 {
444 this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.PatchFamily, id.Id, ComplexReferenceParentType.Patch == parentType);
445 }
446 }
447 }
448
449 /// <summary>
450 /// Parses a PatchFamilyGroup element.
451 /// </summary>
452 /// <param name="node">Element to parse.</param>
453 /// <param name="parentType"></param>
454 /// <param name="parentId"></param>
455 private void ParsePatchFamilyGroupElement(XElement node, ComplexReferenceParentType parentType, string parentId)
456 {
457 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
458 Identifier id = null;
459
460 foreach (var attrib in node.Attributes())
461 {
462 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
463 {
464 switch (attrib.Name.LocalName)
465 {
466 case "Id":
467 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
468 break;
469 default:
470 this.Core.UnexpectedAttribute(node, attrib);
471 break;
472 }
473 }
474 else
475 {
476 this.Core.ParseExtensionAttribute(node, attrib);
477 }
478 }
479
480 if (null == id)
481 {
482 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
483 id = Identifier.Invalid;
484 }
485
486 foreach (var child in node.Elements())
487 {
488 if (CompilerCore.WixNamespace == child.Name.Namespace)
489 {
490 switch (child.Name.LocalName)
491 {
492 case "PatchFamily":
493 this.ParsePatchFamilyElement(child, ComplexReferenceParentType.PatchFamilyGroup, id.Id);
494 break;
495 case "PatchFamilyRef":
496 this.ParsePatchFamilyRefElement(child, ComplexReferenceParentType.PatchFamilyGroup, id.Id);
497 break;
498 case "PatchFamilyGroupRef":
499 this.ParsePatchFamilyGroupRefElement(child, ComplexReferenceParentType.PatchFamilyGroup, id.Id);
500 break;
501 default:
502 this.Core.UnexpectedElement(node, child);
503 break;
504 }
505 }
506 else
507 {
508 this.Core.ParseExtensionElement(node, child);
509 }
510 }
511
512 if (!this.Core.EncounteredError)
513 {
514 this.Core.AddSymbol(new WixPatchFamilyGroupSymbol(sourceLineNumbers, id));
515
516 //Add this PatchFamilyGroup and its parent in WixGroup.
517 this.Core.CreateWixGroupRow(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.PatchFamilyGroup, id.Id);
518 }
519 }
520
521 /// <summary>
522 /// Parses a PatchFamilyGroup reference element.
523 /// </summary>
524 /// <param name="node">Element to parse.</param>
525 /// <param name="parentType">The type of parent.</param>
526 /// <param name="parentId">Identifier of parent element.</param>
527 private void ParsePatchFamilyGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId)
528 {
529 Debug.Assert(ComplexReferenceParentType.PatchFamilyGroup == parentType || ComplexReferenceParentType.Patch == parentType);
530
531 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
532 string id = null;
533
534 foreach (var attrib in node.Attributes())
535 {
536 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
537 {
538 switch (attrib.Name.LocalName)
539 {
540 case "Id":
541 id = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
542 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixPatchFamilyGroup, id);
543 break;
544 default:
545 this.Core.UnexpectedAttribute(node, attrib);
546 break;
547 }
548 }
549 else
550 {
551 this.Core.ParseExtensionAttribute(node, attrib);
552 }
553 }
554
555 if (null == id)
556 {
557 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
558 }
559
560 this.Core.ParseForExtensionElements(node);
561
562 if (!this.Core.EncounteredError)
563 {
564 this.Core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.PatchFamilyGroup, id, true);
565 }
566 }
567
568 /// <summary>
569 /// Parses a TargetProductCodes element.
570 /// </summary>
571 /// <param name="node">The element to parse.</param>
572 private void ParseTargetProductCodesElement(XElement node)
573 {
574 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
575 var replace = false;
576 var targetProductCodes = new List<string>();
577
578 foreach (var attrib in node.Attributes())
579 {
580 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
581 {
582 switch (attrib.Name.LocalName)
583 {
584 case "Replace":
585 replace = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
586 break;
587 default:
588 this.Core.UnexpectedAttribute(node, attrib);
589 break;
590 }
591 }
592 else
593 {
594 this.Core.ParseExtensionAttribute(node, attrib);
595 }
596 }
597
598 foreach (var child in node.Elements())
599 {
600 if (CompilerCore.WixNamespace == child.Name.Namespace)
601 {
602 switch (child.Name.LocalName)
603 {
604 case "TargetProductCode":
605 var id = this.ParseTargetProductCodeElement(child);
606 if (0 == String.CompareOrdinal("*", id))
607 {
608 this.Core.Write(ErrorMessages.IllegalAttributeValueWhenNested(sourceLineNumbers, child.Name.LocalName, "Id", id, node.Name.LocalName));
609 }
610 else
611 {
612 targetProductCodes.Add(id);
613 }
614 break;
615 default:
616 this.Core.UnexpectedElement(node, child);
617 break;
618 }
619 }
620 else
621 {
622 this.Core.ParseExtensionElement(node, child);
623 }
624 }
625
626 if (!this.Core.EncounteredError)
627 {
628 // By default, target ProductCodes should be added.
629 if (!replace)
630 {
631 this.Core.AddSymbol(new WixPatchTargetSymbol(sourceLineNumbers)
632 {
633 ProductCode = "*"
634 });
635 }
636
637 foreach (var targetProductCode in targetProductCodes)
638 {
639 this.Core.AddSymbol(new WixPatchTargetSymbol(sourceLineNumbers)
640 {
641 ProductCode = targetProductCode
642 });
643 }
644 }
645 }
646
647 private void AddMsiPatchMetadata(SourceLineNumber sourceLineNumbers, string company, string property, string value)
648 {
649 this.Core.AddSymbol(new MsiPatchMetadataSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, company, property))
650 {
651 Company = company,
652 Property = property,
653 Value = value
654 });
655 }
656 }
657}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.Xml.Linq;
9 using WixToolset.Data;
10 using WixToolset.Data.Symbols;
11 using WixToolset.Extensibility;
12
13 /// <summary>
14 /// Compiler of the WiX toolset.
15 /// </summary>
16 internal partial class Compiler : ICompiler
17 {
18 /// <summary>
19 /// Parses a patch creation element.
20 /// </summary>
21 /// <param name="node">The element to parse.</param>
22 private void ParsePatchCreationElement(XElement node)
23 {
24 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
25 var clean = true; // Default is to clean
26 var codepage = 0;
27 string outputPath = null;
28 var productMismatches = false;
29 var replaceGuids = String.Empty;
30 string sourceList = null;
31 string symbolFlags = null;
32 var targetProducts = String.Empty;
33 var versionMismatches = false;
34 var wholeFiles = false;
35
36 foreach (var attrib in node.Attributes())
37 {
38 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
39 {
40 switch (attrib.Name.LocalName)
41 {
42 case "Id":
43 this.activeName = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
44 break;
45 case "AllowMajorVersionMismatches":
46 versionMismatches = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
47 break;
48 case "AllowProductCodeMismatches":
49 productMismatches = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
50 break;
51 case "CleanWorkingFolder":
52 clean = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
53 break;
54 case "Codepage":
55 codepage = this.Core.GetAttributeCodePageValue(sourceLineNumbers, attrib);
56 break;
57 case "OutputPath":
58 outputPath = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
59 break;
60 case "SourceList":
61 sourceList = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
62 break;
63 case "SymbolFlags":
64 symbolFlags = String.Format(CultureInfo.InvariantCulture, "0x{0:x8}", this.Core.GetAttributeLongValue(sourceLineNumbers, attrib, 0, UInt32.MaxValue));
65 break;
66 case "WholeFilesOnly":
67 wholeFiles = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
68 break;
69 default:
70 this.Core.UnexpectedAttribute(node, attrib);
71 break;
72 }
73 }
74 else
75 {
76 this.Core.ParseExtensionAttribute(node, attrib);
77 }
78 }
79
80 if (null == this.activeName)
81 {
82 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
83 }
84
85 this.Core.CreateActiveSection(this.activeName, SectionType.PatchCreation, this.Context.CompilationId);
86
87 foreach (var child in node.Elements())
88 {
89 if (CompilerCore.WixNamespace == child.Name.Namespace)
90 {
91 switch (child.Name.LocalName)
92 {
93 case "Family":
94 this.ParseFamilyElement(child);
95 break;
96 case "PatchInformation":
97 this.ParsePatchInformationElement(child);
98 break;
99 case "PatchMetadata":
100 this.ParsePatchMetadataElement(child);
101 break;
102 case "PatchProperty":
103 this.ParsePatchPropertyElement(child, false);
104 break;
105 case "PatchSequence":
106 this.ParsePatchSequenceElement(child);
107 break;
108 case "ReplacePatch":
109 replaceGuids = String.Concat(replaceGuids, this.ParseReplacePatchElement(child));
110 break;
111 case "TargetProductCode":
112 var targetProduct = this.ParseTargetProductCodeElement(child);
113 if (0 < targetProducts.Length)
114 {
115 targetProducts = String.Concat(targetProducts, ";");
116 }
117 targetProducts = String.Concat(targetProducts, targetProduct);
118 break;
119 default:
120 this.Core.UnexpectedElement(node, child);
121 break;
122 }
123 }
124 else
125 {
126 this.Core.ParseExtensionElement(node, child);
127 }
128 }
129
130 this.AddPrivateProperty(sourceLineNumbers, "PatchGUID", this.activeName);
131 this.AddPrivateProperty(sourceLineNumbers, "AllowProductCodeMismatches", productMismatches ? "1" : "0");
132 this.AddPrivateProperty(sourceLineNumbers, "AllowProductVersionMajorMismatches", versionMismatches ? "1" : "0");
133 this.AddPrivateProperty(sourceLineNumbers, "DontRemoveTempFolderWhenFinished", clean ? "0" : "1");
134 this.AddPrivateProperty(sourceLineNumbers, "IncludeWholeFilesOnly", wholeFiles ? "1" : "0");
135
136 if (null != symbolFlags)
137 {
138 this.AddPrivateProperty(sourceLineNumbers, "ApiPatchingSymbolFlags", symbolFlags);
139 }
140
141 if (0 < replaceGuids.Length)
142 {
143 this.AddPrivateProperty(sourceLineNumbers, "ListOfPatchGUIDsToReplace", replaceGuids);
144 }
145
146 if (0 < targetProducts.Length)
147 {
148 this.AddPrivateProperty(sourceLineNumbers, "ListOfTargetProductCodes", targetProducts);
149 }
150
151 if (null != outputPath)
152 {
153 this.AddPrivateProperty(sourceLineNumbers, "PatchOutputPath", outputPath);
154 }
155
156 if (null != sourceList)
157 {
158 this.AddPrivateProperty(sourceLineNumbers, "PatchSourceList", sourceList);
159 }
160 }
161
162 /// <summary>
163 /// Parses a family element.
164 /// </summary>
165 /// <param name="node">The element to parse.</param>
166 private void ParseFamilyElement(XElement node)
167 {
168 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
169 var diskId = CompilerConstants.IntegerNotSet;
170 string diskPrompt = null;
171 string mediaSrcProp = null;
172 string name = null;
173 var sequenceStart = CompilerConstants.IntegerNotSet;
174 string volumeLabel = null;
175
176 foreach (var attrib in node.Attributes())
177 {
178 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
179 {
180 switch (attrib.Name.LocalName)
181 {
182 case "DiskId":
183 diskId = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int16.MaxValue);
184 break;
185 case "DiskPrompt":
186 diskPrompt = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
187 break;
188 case "MediaSrcProp":
189 mediaSrcProp = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
190 break;
191 case "Name":
192 name = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
193 break;
194 case "SequenceStart":
195 sequenceStart = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, Int32.MaxValue);
196 break;
197 case "VolumeLabel":
198 volumeLabel = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
199 break;
200 default:
201 this.Core.UnexpectedAttribute(node, attrib);
202 break;
203 }
204 }
205 else
206 {
207 this.Core.ParseExtensionAttribute(node, attrib);
208 }
209 }
210
211 if (null == name)
212 {
213 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
214 }
215 else if (0 < name.Length)
216 {
217 if (8 < name.Length) // check the length
218 {
219 this.Core.Write(ErrorMessages.FamilyNameTooLong(sourceLineNumbers, node.Name.LocalName, "Name", name, name.Length));
220 }
221 else // check for illegal characters
222 {
223 foreach (var character in name)
224 {
225 if (!Char.IsLetterOrDigit(character) && '_' != character)
226 {
227 this.Core.Write(ErrorMessages.IllegalFamilyName(sourceLineNumbers, node.Name.LocalName, "Name", name));
228 }
229 }
230 }
231 }
232
233 foreach (var child in node.Elements())
234 {
235 if (CompilerCore.WixNamespace == child.Name.Namespace)
236 {
237 switch (child.Name.LocalName)
238 {
239 case "UpgradeImage":
240 this.ParseUpgradeImageElement(child, name);
241 break;
242 case "ExternalFile":
243 this.ParseExternalFileElement(child, name);
244 break;
245 case "ProtectFile":
246 this.ParseProtectFileElement(child, name);
247 break;
248 default:
249 this.Core.UnexpectedElement(node, child);
250 break;
251 }
252 }
253 else
254 {
255 this.Core.ParseExtensionElement(node, child);
256 }
257 }
258
259 if (!this.Core.EncounteredError)
260 {
261 var symbol = this.Core.AddSymbol(new ImageFamiliesSymbol(sourceLineNumbers)
262 {
263 Family = name,
264 MediaSrcPropName = mediaSrcProp,
265 DiskPrompt = diskPrompt,
266 VolumeLabel = volumeLabel
267 });
268
269 if (CompilerConstants.IntegerNotSet != diskId)
270 {
271 symbol.MediaDiskId = diskId;
272 }
273
274 if (CompilerConstants.IntegerNotSet != sequenceStart)
275 {
276 symbol.FileSequenceStart = sequenceStart;
277 }
278 }
279 }
280
281 /// <summary>
282 /// Parses an upgrade image element.
283 /// </summary>
284 /// <param name="node">The element to parse.</param>
285 /// <param name="family">The family for this element.</param>
286 private void ParseUpgradeImageElement(XElement node, string family)
287 {
288 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
289 string sourceFile = null;
290 string sourcePatch = null;
291 var symbols = new List<string>();
292 string upgrade = null;
293
294 foreach (var attrib in node.Attributes())
295 {
296 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
297 {
298 switch (attrib.Name.LocalName)
299 {
300 case "Id":
301 upgrade = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
302 if (13 < upgrade.Length)
303 {
304 this.Core.Write(ErrorMessages.IdentifierTooLongError(sourceLineNumbers, node.Name.LocalName, "Id", upgrade, 13));
305 }
306 break;
307 case "SourceFile":
308 sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
309 break;
310 case "SourcePatch":
311 sourcePatch = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
312 break;
313 default:
314 this.Core.UnexpectedAttribute(node, attrib);
315 break;
316 }
317 }
318 else
319 {
320 this.Core.ParseExtensionAttribute(node, attrib);
321 }
322 }
323
324 if (null == upgrade)
325 {
326 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
327 }
328
329 if (null == sourceFile)
330 {
331 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile"));
332 }
333
334 foreach (var child in node.Elements())
335 {
336 if (CompilerCore.WixNamespace == child.Name.Namespace)
337 {
338 switch (child.Name.LocalName)
339 {
340 case "SymbolPath":
341 symbols.Add(this.ParseSymbolPathElement(child));
342 break;
343 case "TargetImage":
344 this.ParseTargetImageElement(child, upgrade, family);
345 break;
346 case "UpgradeFile":
347 this.ParseUpgradeFileElement(child, upgrade);
348 break;
349 default:
350 this.Core.UnexpectedElement(node, child);
351 break;
352 }
353 }
354 else
355 {
356 this.Core.ParseExtensionElement(node, child);
357 }
358 }
359
360 if (!this.Core.EncounteredError)
361 {
362 this.Core.AddSymbol(new UpgradedImagesSymbol(sourceLineNumbers)
363 {
364 Upgraded = upgrade,
365 MsiPath = sourceFile,
366 PatchMsiPath = sourcePatch,
367 SymbolPaths = String.Join(";", symbols),
368 Family = family
369 });
370 }
371 }
372
373 /// <summary>
374 /// Parses an upgrade file element.
375 /// </summary>
376 /// <param name="node">The element to parse.</param>
377 /// <param name="upgrade">The upgrade key for this element.</param>
378 private void ParseUpgradeFileElement(XElement node, string upgrade)
379 {
380 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
381 var allowIgnoreOnError = false;
382 string file = null;
383 var ignore = false;
384 var symbols = new List<string>();
385 var wholeFile = false;
386
387 foreach (var attrib in node.Attributes())
388 {
389 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
390 {
391 switch (attrib.Name.LocalName)
392 {
393 case "AllowIgnoreOnError":
394 allowIgnoreOnError = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
395 break;
396 case "File":
397 file = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
398 break;
399 case "Ignore":
400 ignore = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
401 break;
402 case "WholeFile":
403 wholeFile = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
404 break;
405 default:
406 this.Core.UnexpectedAttribute(node, attrib);
407 break;
408 }
409 }
410 else
411 {
412 this.Core.ParseExtensionAttribute(node, attrib);
413 }
414 }
415
416 if (null == file)
417 {
418 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "File"));
419 }
420
421 foreach (var child in node.Elements())
422 {
423 if (CompilerCore.WixNamespace == child.Name.Namespace)
424 {
425 switch (child.Name.LocalName)
426 {
427 case "SymbolPath":
428 symbols.Add(this.ParseSymbolPathElement(child));
429 break;
430 default:
431 this.Core.UnexpectedElement(node, child);
432 break;
433 }
434 }
435 else
436 {
437 this.Core.ParseExtensionElement(node, child);
438 }
439 }
440
441 if (!this.Core.EncounteredError)
442 {
443 if (ignore)
444 {
445 this.Core.AddSymbol(new UpgradedFilesToIgnoreSymbol(sourceLineNumbers)
446 {
447 Upgraded = upgrade,
448 FTK = file
449 });
450 }
451 else
452 {
453 this.Core.AddSymbol(new UpgradedFilesOptionalDataSymbol(sourceLineNumbers)
454 {
455 Upgraded = upgrade,
456 FTK = file,
457 SymbolPaths = String.Join(";", symbols),
458 AllowIgnoreOnPatchError = allowIgnoreOnError,
459 IncludeWholeFile = wholeFile
460 });
461 }
462 }
463 }
464
465 /// <summary>
466 /// Parses a target image element.
467 /// </summary>
468 /// <param name="node">The element to parse.</param>
469 /// <param name="upgrade">The upgrade key for this element.</param>
470 /// <param name="family">The family key for this element.</param>
471 private void ParseTargetImageElement(XElement node, string upgrade, string family)
472 {
473 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
474 var ignore = false;
475 var order = CompilerConstants.IntegerNotSet;
476 string sourceFile = null;
477 string symbols = null;
478 string target = null;
479 string validation = null;
480
481 foreach (var attrib in node.Attributes())
482 {
483 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
484 {
485 switch (attrib.Name.LocalName)
486 {
487 case "Id":
488 target = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
489 if (target.Length > 13)
490 {
491 this.Core.Write(ErrorMessages.IdentifierTooLongError(sourceLineNumbers, node.Name.LocalName, "Id", target, 13));
492 }
493 break;
494 case "IgnoreMissingFiles":
495 ignore = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
496 break;
497 case "Order":
498 order = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int32.MinValue + 2, Int32.MaxValue);
499 break;
500 case "SourceFile":
501 sourceFile = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
502 break;
503 case "Validation":
504 validation = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
505 break;
506 default:
507 this.Core.UnexpectedAttribute(node, attrib);
508 break;
509 }
510 }
511 else
512 {
513 this.Core.ParseExtensionAttribute(node, attrib);
514 }
515 }
516
517 if (null == target)
518 {
519 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
520 }
521
522 if (null == sourceFile)
523 {
524 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile"));
525 }
526
527 if (CompilerConstants.IntegerNotSet == order)
528 {
529 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Order"));
530 }
531
532 foreach (var child in node.Elements())
533 {
534 if (CompilerCore.WixNamespace == child.Name.Namespace)
535 {
536 switch (child.Name.LocalName)
537 {
538 case "SymbolPath":
539 if (null != symbols)
540 {
541 symbols = String.Concat(symbols, ";", this.ParseSymbolPathElement(child));
542 }
543 else
544 {
545 symbols = this.ParseSymbolPathElement(child);
546 }
547 break;
548 case "TargetFile":
549 this.ParseTargetFileElement(child, target, family);
550 break;
551 default:
552 this.Core.UnexpectedElement(node, child);
553 break;
554 }
555 }
556 else
557 {
558 this.Core.ParseExtensionElement(node, child);
559 }
560 }
561
562 if (!this.Core.EncounteredError)
563 {
564 this.Core.AddSymbol(new TargetImagesSymbol(sourceLineNumbers)
565 {
566 Target = target,
567 MsiPath = sourceFile,
568 SymbolPaths = symbols,
569 Upgraded = upgrade,
570 Order = order,
571 ProductValidateFlags = validation,
572 IgnoreMissingSrcFiles = ignore
573 });
574 }
575 }
576
577 /// <summary>
578 /// Parses an upgrade file element.
579 /// </summary>
580 /// <param name="node">The element to parse.</param>
581 /// <param name="target">The upgrade key for this element.</param>
582 /// <param name="family">The family key for this element.</param>
583 private void ParseTargetFileElement(XElement node, string target, string family)
584 {
585 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
586 string file = null;
587 string ignoreLengths = null;
588 string ignoreOffsets = null;
589 string protectLengths = null;
590 string protectOffsets = null;
591 string symbols = null;
592
593 foreach (var attrib in node.Attributes())
594 {
595 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
596 {
597 switch (attrib.Name.LocalName)
598 {
599 case "Id":
600 file = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
601 break;
602 default:
603 this.Core.UnexpectedAttribute(node, attrib);
604 break;
605 }
606 }
607 else
608 {
609 this.Core.ParseExtensionAttribute(node, attrib);
610 }
611 }
612
613 if (null == file)
614 {
615 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
616 }
617
618 foreach (var child in node.Elements())
619 {
620 if (CompilerCore.WixNamespace == child.Name.Namespace)
621 {
622 switch (child.Name.LocalName)
623 {
624 case "IgnoreRange":
625 this.ParseRangeElement(child, ref ignoreOffsets, ref ignoreLengths);
626 break;
627 case "ProtectRange":
628 this.ParseRangeElement(child, ref protectOffsets, ref protectLengths);
629 break;
630 case "SymbolPath":
631 symbols = this.ParseSymbolPathElement(child);
632 break;
633 default:
634 this.Core.UnexpectedElement(node, child);
635 break;
636 }
637 }
638 else
639 {
640 this.Core.ParseExtensionElement(node, child);
641 }
642 }
643
644 if (!this.Core.EncounteredError)
645 {
646 var symbol = this.Core.AddSymbol(new TargetFilesOptionalDataSymbol(sourceLineNumbers)
647 {
648 Target = target,
649 FTK = file,
650 SymbolPaths = symbols,
651 IgnoreOffsets = ignoreOffsets,
652 IgnoreLengths = ignoreLengths,
653 });
654
655 if (null != protectOffsets)
656 {
657 symbol.RetainOffsets = protectOffsets;
658
659 this.Core.AddSymbol(new FamilyFileRangesSymbol(sourceLineNumbers)
660 {
661 Family = family,
662 FTK = file,
663 RetainOffsets = protectOffsets,
664 RetainLengths = protectLengths,
665 });
666 }
667 }
668 }
669
670 /// <summary>
671 /// Parses an external file element.
672 /// </summary>
673 /// <param name="node">The element to parse.</param>
674 /// <param name="family">The family for this element.</param>
675 private void ParseExternalFileElement(XElement node, string family)
676 {
677 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
678 string file = null;
679 string ignoreLengths = null;
680 string ignoreOffsets = null;
681 var order = CompilerConstants.IntegerNotSet;
682 string protectLengths = null;
683 string protectOffsets = null;
684 string source = null;
685 string symbols = null;
686
687 foreach (var attrib in node.Attributes())
688 {
689 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
690 {
691 switch (attrib.Name.LocalName)
692 {
693 case "File":
694 file = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
695 break;
696 case "Order":
697 order = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, Int32.MinValue + 2, Int32.MaxValue);
698 break;
699 case "Source":
700 source = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
701 break;
702 default:
703 this.Core.UnexpectedAttribute(node, attrib);
704 break;
705 }
706 }
707 else
708 {
709 this.Core.ParseExtensionAttribute(node, attrib);
710 }
711 }
712
713 if (null == file)
714 {
715 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "File"));
716 }
717
718 if (null == source)
719 {
720 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Source"));
721 }
722
723 if (CompilerConstants.IntegerNotSet == order)
724 {
725 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Order"));
726 }
727
728 foreach (var child in node.Elements())
729 {
730 if (CompilerCore.WixNamespace == child.Name.Namespace)
731 {
732 switch (child.Name.LocalName)
733 {
734 case "IgnoreRange":
735 this.ParseRangeElement(child, ref ignoreOffsets, ref ignoreLengths);
736 break;
737 case "ProtectRange":
738 this.ParseRangeElement(child, ref protectOffsets, ref protectLengths);
739 break;
740 case "SymbolPath":
741 symbols = this.ParseSymbolPathElement(child);
742 break;
743 default:
744 this.Core.UnexpectedElement(node, child);
745 break;
746 }
747 }
748 else
749 {
750 this.Core.ParseExtensionElement(node, child);
751 }
752 }
753
754 if (!this.Core.EncounteredError)
755 {
756 var symbol = this.Core.AddSymbol(new ExternalFilesSymbol(sourceLineNumbers)
757 {
758 Family = family,
759 FTK = file,
760 FilePath = source,
761 SymbolPaths = symbols,
762 IgnoreOffsets = ignoreOffsets,
763 IgnoreLengths = ignoreLengths,
764 });
765
766 if (null != protectOffsets)
767 {
768 symbol.RetainOffsets = protectOffsets;
769 }
770
771 if (CompilerConstants.IntegerNotSet != order)
772 {
773 symbol.Order = order;
774 }
775
776 if (null != protectOffsets)
777 {
778 this.Core.AddSymbol(new FamilyFileRangesSymbol(sourceLineNumbers)
779 {
780 Family = family,
781 FTK = file,
782 RetainOffsets = protectOffsets,
783 RetainLengths = protectLengths,
784 });
785 }
786 }
787 }
788
789 /// <summary>
790 /// Parses a protect file element.
791 /// </summary>
792 /// <param name="node">The element to parse.</param>
793 /// <param name="family">The family for this element.</param>
794 private void ParseProtectFileElement(XElement node, string family)
795 {
796 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
797 string file = null;
798 string protectLengths = null;
799 string protectOffsets = null;
800
801 foreach (var attrib in node.Attributes())
802 {
803 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
804 {
805 switch (attrib.Name.LocalName)
806 {
807 case "File":
808 file = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
809 break;
810 default:
811 this.Core.UnexpectedAttribute(node, attrib);
812 break;
813 }
814 }
815 else
816 {
817 this.Core.ParseExtensionAttribute(node, attrib);
818 }
819 }
820
821 if (null == file)
822 {
823 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "File"));
824 }
825
826 foreach (var child in node.Elements())
827 {
828 if (CompilerCore.WixNamespace == child.Name.Namespace)
829 {
830 switch (child.Name.LocalName)
831 {
832 case "ProtectRange":
833 this.ParseRangeElement(child, ref protectOffsets, ref protectLengths);
834 break;
835 default:
836 this.Core.UnexpectedElement(node, child);
837 break;
838 }
839 }
840 else
841 {
842 this.Core.ParseExtensionElement(node, child);
843 }
844 }
845
846 if (null == protectOffsets || null == protectLengths)
847 {
848 this.Core.Write(ErrorMessages.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "ProtectRange"));
849 }
850
851 if (!this.Core.EncounteredError)
852 {
853 this.Core.AddSymbol(new FamilyFileRangesSymbol(sourceLineNumbers)
854 {
855 Family = family,
856 FTK = file,
857 RetainOffsets = protectOffsets,
858 RetainLengths = protectLengths
859 });
860 }
861 }
862
863 /// <summary>
864 /// Parses a range element (ProtectRange, IgnoreRange, etc).
865 /// </summary>
866 /// <param name="node">The element to parse.</param>
867 /// <param name="offsets">Reference to the offsets string.</param>
868 /// <param name="lengths">Reference to the lengths string.</param>
869 private void ParseRangeElement(XElement node, ref string offsets, ref string lengths)
870 {
871 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
872 string length = null;
873 string offset = null;
874
875 foreach (var attrib in node.Attributes())
876 {
877 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
878 {
879 switch (attrib.Name.LocalName)
880 {
881 case "Length":
882 length = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
883 break;
884 case "Offset":
885 offset = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
886 break;
887 default:
888 this.Core.UnexpectedAttribute(node, attrib);
889 break;
890 }
891 }
892 else
893 {
894 this.Core.ParseExtensionAttribute(node, attrib);
895 }
896 }
897
898 if (null == length)
899 {
900 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Length"));
901 }
902
903 if (null == offset)
904 {
905 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Offset"));
906 }
907
908 this.Core.ParseForExtensionElements(node);
909
910 if (null != lengths)
911 {
912 lengths = String.Concat(lengths, ",", length);
913 }
914 else
915 {
916 lengths = length;
917 }
918
919 if (null != offsets)
920 {
921 offsets = String.Concat(offsets, ",", offset);
922 }
923 else
924 {
925 offsets = offset;
926 }
927 }
928
929 /// <summary>
930 /// Parses a patch metadata element.
931 /// </summary>
932 /// <param name="node">Element to parse.</param>
933 private void ParsePatchMetadataElement(XElement node)
934 {
935 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
936 var allowRemoval = YesNoType.NotSet;
937 string classification = null;
938 string creationTimeUtc = null;
939 string description = null;
940 string displayName = null;
941 string manufacturerName = null;
942 string minorUpdateTargetRTM = null;
943 string moreInfoUrl = null;
944 var optimizeCA = CompilerConstants.IntegerNotSet;
945 var optimizedInstallMode = YesNoType.NotSet;
946 string targetProductName = null;
947
948 foreach (var attrib in node.Attributes())
949 {
950 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
951 {
952 switch (attrib.Name.LocalName)
953 {
954 case "AllowRemoval":
955 allowRemoval = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
956 break;
957 case "Classification":
958 classification = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
959 break;
960 case "CreationTimeUTC":
961 creationTimeUtc = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
962 break;
963 case "Description":
964 description = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
965 break;
966 case "DisplayName":
967 displayName = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
968 break;
969 case "ManufacturerName":
970 manufacturerName = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
971 break;
972 case "MinorUpdateTargetRTM":
973 minorUpdateTargetRTM = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
974 break;
975 case "MoreInfoURL":
976 moreInfoUrl = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
977 break;
978 case "OptimizedInstallMode":
979 optimizedInstallMode = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
980 break;
981 case "TargetProductName":
982 targetProductName = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
983 break;
984 default:
985 this.Core.UnexpectedAttribute(node, attrib);
986 break;
987 }
988 }
989 else
990 {
991 this.Core.ParseExtensionAttribute(node, attrib);
992 }
993 }
994
995 if (YesNoType.NotSet == allowRemoval)
996 {
997 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "AllowRemoval"));
998 }
999
1000 if (null == classification)
1001 {
1002 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Classification"));
1003 }
1004
1005 if (null == description)
1006 {
1007 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Description"));
1008 }
1009
1010 if (null == displayName)
1011 {
1012 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisplayName"));
1013 }
1014
1015 if (null == manufacturerName)
1016 {
1017 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ManufacturerName"));
1018 }
1019
1020 if (null == moreInfoUrl)
1021 {
1022 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "MoreInfoURL"));
1023 }
1024
1025 if (null == targetProductName)
1026 {
1027 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "TargetProductName"));
1028 }
1029
1030 foreach (var child in node.Elements())
1031 {
1032 if (CompilerCore.WixNamespace == child.Name.Namespace)
1033 {
1034 switch (child.Name.LocalName)
1035 {
1036 case "CustomProperty":
1037 this.ParseCustomPropertyElement(child);
1038 break;
1039 case "OptimizeCustomActions":
1040 optimizeCA = this.ParseOptimizeCustomActionsElement(child);
1041 break;
1042 default:
1043 this.Core.UnexpectedElement(node, child);
1044 break;
1045 }
1046 }
1047 else
1048 {
1049 this.Core.ParseExtensionElement(node, child);
1050 }
1051 }
1052
1053 if (!this.Core.EncounteredError)
1054 {
1055 if (YesNoType.NotSet != allowRemoval)
1056 {
1057 this.AddPatchMetadata(sourceLineNumbers, null, "AllowRemoval", YesNoType.Yes == allowRemoval ? "1" : "0");
1058 }
1059
1060 if (null != classification)
1061 {
1062 this.AddPatchMetadata(sourceLineNumbers, null, "Classification", classification);
1063 }
1064
1065 if (null != creationTimeUtc)
1066 {
1067 this.AddPatchMetadata(sourceLineNumbers, null, "CreationTimeUTC", creationTimeUtc);
1068 }
1069
1070 if (null != description)
1071 {
1072 this.AddPatchMetadata(sourceLineNumbers, null, "Description", description);
1073 }
1074
1075 if (null != displayName)
1076 {
1077 this.AddPatchMetadata(sourceLineNumbers, null, "DisplayName", displayName);
1078 }
1079
1080 if (null != manufacturerName)
1081 {
1082 this.AddPatchMetadata(sourceLineNumbers, null, "ManufacturerName", manufacturerName);
1083 }
1084
1085 if (null != minorUpdateTargetRTM)
1086 {
1087 this.AddPatchMetadata(sourceLineNumbers, null, "MinorUpdateTargetRTM", minorUpdateTargetRTM);
1088 }
1089
1090 if (null != moreInfoUrl)
1091 {
1092 this.AddPatchMetadata(sourceLineNumbers, null, "MoreInfoURL", moreInfoUrl);
1093 }
1094
1095 if (CompilerConstants.IntegerNotSet != optimizeCA)
1096 {
1097 this.AddPatchMetadata(sourceLineNumbers, null, "OptimizeCA", optimizeCA.ToString(CultureInfo.InvariantCulture));
1098 }
1099
1100 if (YesNoType.NotSet != optimizedInstallMode)
1101 {
1102 this.AddPatchMetadata(sourceLineNumbers, null, "OptimizedInstallMode", YesNoType.Yes == optimizedInstallMode ? "1" : "0");
1103 }
1104
1105 if (null != targetProductName)
1106 {
1107 this.AddPatchMetadata(sourceLineNumbers, null, "TargetProductName", targetProductName);
1108 }
1109 }
1110 }
1111
1112 /// <summary>
1113 /// Parses a custom property element for the PatchMetadata table.
1114 /// </summary>
1115 /// <param name="node">Element to parse.</param>
1116 private void ParseCustomPropertyElement(XElement node)
1117 {
1118 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1119 string company = null;
1120 string property = null;
1121 string value = null;
1122
1123 foreach (var attrib in node.Attributes())
1124 {
1125 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
1126 {
1127 switch (attrib.Name.LocalName)
1128 {
1129 case "Company":
1130 company = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1131 break;
1132 case "Property":
1133 property = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1134 break;
1135 case "Value":
1136 value = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1137 break;
1138 default:
1139 this.Core.UnexpectedAttribute(node, attrib);
1140 break;
1141 }
1142 }
1143 else
1144 {
1145 this.Core.ParseExtensionAttribute(node, attrib);
1146 }
1147 }
1148
1149 if (null == company)
1150 {
1151 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Company"));
1152 }
1153
1154 if (null == property)
1155 {
1156 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property"));
1157 }
1158
1159 if (null == value)
1160 {
1161 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value"));
1162 }
1163
1164 this.Core.ParseForExtensionElements(node);
1165
1166 if (!this.Core.EncounteredError)
1167 {
1168 this.AddPatchMetadata(sourceLineNumbers, company, property, value);
1169 }
1170 }
1171
1172 /// <summary>
1173 /// Parses a patch sequence element.
1174 /// </summary>
1175 /// <param name="node">The element to parse.</param>
1176 private void ParsePatchSequenceElement(XElement node)
1177 {
1178 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1179 string family = null;
1180 string target = null;
1181 string sequence = null;
1182 var attributes = 0;
1183
1184 foreach (var attrib in node.Attributes())
1185 {
1186 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
1187 {
1188 switch (attrib.Name.LocalName)
1189 {
1190 case "PatchFamily":
1191 family = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
1192 break;
1193 case "ProductCode":
1194 if (null != target)
1195 {
1196 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Target", "TargetImage"));
1197 }
1198 target = this.Core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
1199 break;
1200 case "Target":
1201 if (null != target)
1202 {
1203 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "TargetImage", "ProductCode"));
1204 }
1205 this.Core.Write(WarningMessages.DeprecatedPatchSequenceTargetAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName));
1206 target = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1207 break;
1208 case "TargetImage":
1209 if (null != target)
1210 {
1211 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Target", "ProductCode"));
1212 }
1213 target = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1214 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.TargetImages, target);
1215 break;
1216 case "Sequence":
1217 sequence = this.Core.GetAttributeVersionValue(sourceLineNumbers, attrib);
1218 break;
1219 case "Supersede":
1220 if (YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
1221 {
1222 attributes |= 0x1;
1223 }
1224 break;
1225 default:
1226 this.Core.UnexpectedAttribute(node, attrib);
1227 break;
1228 }
1229 }
1230 else
1231 {
1232 this.Core.ParseExtensionAttribute(node, attrib);
1233 }
1234 }
1235
1236 if (null == family)
1237 {
1238 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "PatchFamily"));
1239 }
1240
1241 this.Core.ParseForExtensionElements(node);
1242
1243 if (!this.Core.EncounteredError)
1244 {
1245 this.Core.AddSymbol(new PatchSequenceSymbol(sourceLineNumbers)
1246 {
1247 PatchFamily = family,
1248 Target = target,
1249 Sequence = sequence,
1250 Supersede = attributes,
1251 });
1252 }
1253 }
1254
1255 private void AddPatchMetadata(SourceLineNumber sourceLineNumbers, string company, string property, string value)
1256 {
1257 this.Core.AddSymbol(new PatchMetadataSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, company, property))
1258 {
1259 Company = company,
1260 Property = property,
1261 Value = value,
1262 });
1263 }
1264 }
1265}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System;
6 using System.Xml.Linq;
7 using WixToolset.Data;
8 using WixToolset.Data.Symbols;
9
10 /// <summary>
11 /// Compiler of the WiX toolset.
12 /// </summary>
13 internal partial class Compiler : ICompiler
14 {
15 /// <summary>
16 /// Parses a Tag element for Software Id Tag registration under a Bundle element.
17 /// </summary>
18 /// <param name="node">The element to parse.</param>
19 private void ParseBundleTagElement(XElement node)
20 {
21 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
22 string name = null;
23 string regid = null;
24 string installPath = null;
25
26 foreach (var attrib in node.Attributes())
27 {
28 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
29 {
30 switch (attrib.Name.LocalName)
31 {
32 case "Name":
33 name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false);
34 break;
35 case "Regid":
36 regid = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
37 break;
38 case "InstallDirectory":
39 case "Bitness":
40 this.Core.Write(ErrorMessages.ExpectedParentWithAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Package"));
41 break;
42 case "InstallPath":
43 installPath = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
44 break;
45 default:
46 this.Core.UnexpectedAttribute(node, attrib);
47 break;
48 }
49 }
50 else
51 {
52 this.Core.ParseExtensionAttribute(node, attrib);
53 }
54 }
55
56 this.Core.ParseForExtensionElements(node);
57
58 if (String.IsNullOrEmpty(name))
59 {
60 name = node.Parent?.Attribute("Name")?.Value;
61
62 if (String.IsNullOrEmpty(name))
63 {
64 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
65 }
66 }
67
68 if (!String.IsNullOrEmpty(name) && !this.Core.IsValidLongFilename(name))
69 {
70 this.Core.Write(CompilerErrors.IllegalName(sourceLineNumbers, node.Name.LocalName, name));
71 }
72
73 if (String.IsNullOrEmpty(regid))
74 {
75 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Regid"));
76 }
77 else if (regid.Equals("example.com", StringComparison.OrdinalIgnoreCase))
78 {
79 this.Core.Write(CompilerErrors.ExampleRegid(sourceLineNumbers, regid));
80 }
81
82 if (String.IsNullOrEmpty(installPath))
83 {
84 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "InstallPath"));
85 }
86
87 if (!this.Core.EncounteredError)
88 {
89 this.Core.AddSymbol(new WixBundleTagSymbol(sourceLineNumbers)
90 {
91 Filename = String.Concat(name, ".swidtag"),
92 Regid = regid,
93 Name = name,
94 InstallPath = installPath
95 });
96 }
97 }
98
99 /// <summary>
100 /// Parses a Tag element for Software Id Tag registration under a Package element.
101 /// </summary>
102 /// <param name="node">The element to parse.</param>
103 private void ParsePackageTagElement(XElement node)
104 {
105 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
106 Identifier id = null;
107 string name = null;
108 string regid = null;
109 string feature = null;
110 string installDirectory = null;
111 var win64 = this.Context.IsCurrentPlatform64Bit;
112
113 foreach (var attrib in node.Attributes())
114 {
115 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
116 {
117 switch (attrib.Name.LocalName)
118 {
119 case "Id":
120 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
121 break;
122 case "Name":
123 name = this.Core.GetAttributeLongFilename(sourceLineNumbers, attrib, false);
124 break;
125 case "Regid":
126 regid = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
127 break;
128 case "Feature":
129 feature = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
130 break;
131 case "InstallDirectory":
132 installDirectory = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
133 break;
134 case "InstallPath":
135 this.Core.Write(ErrorMessages.ExpectedParentWithAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Bundle"));
136 break;
137 case "Bitness":
138 var bitnessValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
139 switch (bitnessValue)
140 {
141 case "always32":
142 win64 = false;
143 break;
144 case "always64":
145 win64 = true;
146 break;
147 case "default":
148 case "":
149 break;
150 default:
151 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, bitnessValue, "default", "always32", "always64"));
152 break;
153 }
154 break;
155 default:
156 this.Core.UnexpectedAttribute(node, attrib);
157 break;
158 }
159 }
160 else
161 {
162 this.Core.ParseExtensionAttribute(node, attrib);
163 }
164 }
165
166 this.Core.ParseForExtensionElements(node);
167
168 if (String.IsNullOrEmpty(name))
169 {
170 name = node.Parent?.Attribute("Name")?.Value;
171
172 if (String.IsNullOrEmpty(name))
173 {
174 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
175 }
176 }
177
178 if (!String.IsNullOrEmpty(name) && !this.Core.IsValidLongFilename(name))
179 {
180 this.Core.Write(CompilerErrors.IllegalName(sourceLineNumbers, node.Name.LocalName, name));
181 }
182
183 if (String.IsNullOrEmpty(regid))
184 {
185 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Regid"));
186 }
187 else if (regid.Equals("example.com", StringComparison.OrdinalIgnoreCase))
188 {
189 this.Core.Write(CompilerErrors.ExampleRegid(sourceLineNumbers, regid));
190 return;
191 }
192 else if (id == null)
193 {
194 id = this.CreateTagId(regid);
195 }
196
197 if (String.IsNullOrEmpty(installDirectory))
198 {
199 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "InstallDirectory"));
200 }
201
202 if (!this.Core.EncounteredError)
203 {
204 var fileName = String.Concat(name, ".swidtag");
205
206 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Directory, installDirectory);
207 this.Core.AddSymbol(new DirectorySymbol(sourceLineNumbers, id)
208 {
209 Name = "swidtag",
210 ParentDirectoryRef = installDirectory,
211 ComponentGuidGenerationSeed = "4BAD0C8B-3AF0-BFE3-CC83-094749A1C4B1"
212 });
213
214 this.Core.AddSymbol(new ComponentSymbol(sourceLineNumbers, id)
215 {
216 ComponentId = "*",
217 DirectoryRef = id.Id,
218 KeyPath = id.Id,
219 KeyPathType = ComponentKeyPathType.File,
220 Location = ComponentLocation.LocalOnly,
221 Win64 = win64
222 });
223
224 this.Core.AddSymbol(new FileSymbol(sourceLineNumbers, id)
225 {
226 ComponentRef = id.Id,
227 Name = fileName,
228 DiskId = 1,
229 Attributes = FileSymbolAttributes.ReadOnly,
230 });
231
232 if (!String.IsNullOrEmpty(feature))
233 {
234 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Feature, feature);
235 }
236 else
237 {
238 feature = "WixSwidTag";
239 this.Core.AddSymbol(new FeatureSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, feature))
240 {
241 Title = "ISO/IEC 19770-2",
242 Level = 1,
243 InstallDefault = FeatureInstallDefault.Local,
244 Display = 0,
245 DisallowAdvertise = true,
246 DisallowAbsent = true,
247 });
248 }
249 this.Core.CreateComplexReference(sourceLineNumbers, ComplexReferenceParentType.Feature, feature, null, ComplexReferenceChildType.Component, id.Id, true);
250
251 this.Core.EnsureTable(sourceLineNumbers, "SoftwareIdentificationTag");
252 this.Core.AddSymbol(new WixProductTagSymbol(sourceLineNumbers, id)
253 {
254 FileRef = id.Id,
255 Regid = regid,
256 Name = name
257 });
258 }
259 }
260
261 /// <summary>
262 /// Parses a TagRef element for Software Id Tag registration under a PatchFamily element.
263 /// </summary>
264 /// <param name="node">The element to parse.</param>
265 private void ParseTagRefElement(XElement node)
266 {
267 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
268 string regid = null;
269
270 foreach (var attrib in node.Attributes())
271 {
272 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
273 {
274 switch (attrib.Name.LocalName)
275 {
276 case "Regid":
277 regid = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
278 break;
279 default:
280 this.Core.UnexpectedAttribute(node, attrib);
281 break;
282 }
283 }
284 else
285 {
286 this.Core.ParseExtensionAttribute(node, attrib);
287 }
288 }
289
290 this.Core.ParseForExtensionElements(node);
291
292 if (String.IsNullOrEmpty(regid))
293 {
294 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Regid"));
295 }
296 else if (regid.Equals("example.com", StringComparison.OrdinalIgnoreCase))
297 {
298 this.Core.Write(CompilerErrors.ExampleRegid(sourceLineNumbers, regid));
299 }
300
301 if (!this.Core.EncounteredError)
302 {
303 var id = this.CreateTagId(regid);
304
305 this.Core.AddSymbol(new WixPatchRefSymbol(sourceLineNumbers, id)
306 {
307 Table = SymbolDefinitions.Component.Name,
308 PrimaryKeys = id.Id
309 });
310 }
311 }
312
313 private Identifier CreateTagId(string regid) => this.Core.CreateIdentifier("tag", regid, ".product.tag");
314 }
315}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System;
6 using System.Collections;
7 using System.Xml.Linq;
8 using WixToolset.Data;
9 using WixToolset.Data.Symbols;
10 using WixToolset.Data.WindowsInstaller;
11 using WixToolset.Extensibility;
12
13 /// <summary>
14 /// Compiler of the WiX toolset.
15 /// </summary>
16 internal partial class Compiler : ICompiler
17 {
18 // NameToBit arrays
19 private static readonly string[] TextControlAttributes = { "Transparent", "NoPrefix", "NoWrap", "FormatSize", "UserLanguage" };
20 private static readonly string[] HyperlinkControlAttributes = { "Transparent" };
21 private static readonly string[] EditControlAttributes = { "Multiline", null, null, null, null, "Password" };
22 private static readonly string[] ProgressControlAttributes = { "ProgressBlocks" };
23 private static readonly string[] VolumeControlAttributes = { "Removable", "Fixed", "Remote", "CDROM", "RAMDisk", "Floppy", "ShowRollbackCost" };
24 private static readonly string[] ListboxControlAttributes = { "Sorted", null, null, null, "UserLanguage" };
25 private static readonly string[] ListviewControlAttributes = { "Sorted", null, null, null, "FixedSize", "Icon16", "Icon32" };
26 private static readonly string[] ComboboxControlAttributes = { "Sorted", "ComboList", null, null, "UserLanguage" };
27 private static readonly string[] RadioControlAttributes = { "Image", "PushLike", "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32", null, "HasBorder" };
28 private static readonly string[] ButtonControlAttributes = { "Image", null, "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32", "ElevationShield" };
29 private static readonly string[] IconControlAttributes = { "Image", null, null, null, "FixedSize", "Icon16", "Icon32" };
30 private static readonly string[] BitmapControlAttributes = { "Image", null, null, null, "FixedSize" };
31 private static readonly string[] CheckboxControlAttributes = { null, "PushLike", "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32" };
32
33 /// <summary>
34 /// Parses UI elements.
35 /// </summary>
36 /// <param name="node">Element to parse.</param>
37 private void ParseUIElement(XElement node)
38 {
39 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
40 Identifier id = null;
41 var embeddedUICount = 0;
42
43 foreach (var attrib in node.Attributes())
44 {
45 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
46 {
47 switch (attrib.Name.LocalName)
48 {
49 case "Id":
50 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
51 break;
52 default:
53 this.Core.UnexpectedAttribute(node, attrib);
54 break;
55 }
56 }
57 else
58 {
59 this.Core.ParseExtensionAttribute(node, attrib);
60 }
61 }
62
63 foreach (var child in node.Elements())
64 {
65 if (CompilerCore.WixNamespace == child.Name.Namespace)
66 {
67 switch (child.Name.LocalName)
68 {
69 case "BillboardAction":
70 this.ParseBillboardActionElement(child);
71 break;
72 case "ComboBox":
73 this.ParseControlGroupElement(child, SymbolDefinitionType.ComboBox, "ListItem");
74 break;
75 case "Dialog":
76 this.ParseDialogElement(child);
77 break;
78 case "DialogRef":
79 this.ParseSimpleRefElement(child, SymbolDefinitions.Dialog);
80 break;
81 case "EmbeddedUI":
82 if (0 < embeddedUICount) // there can be only one embedded UI
83 {
84 var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
85 this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, child.Name.LocalName));
86 }
87 this.ParseEmbeddedUIElement(child);
88 ++embeddedUICount;
89 break;
90 case "Error":
91 this.ParseErrorElement(child);
92 break;
93 case "ListBox":
94 this.ParseControlGroupElement(child, SymbolDefinitionType.ListBox, "ListItem");
95 break;
96 case "ListView":
97 this.ParseControlGroupElement(child, SymbolDefinitionType.ListView, "ListItem");
98 break;
99 case "ProgressText":
100 this.ParseActionTextElement(child);
101 break;
102 case "Publish":
103 var order = 0;
104 this.ParsePublishElement(child, null, null, ref order);
105 break;
106 case "RadioButtonGroup":
107 var radioButtonType = this.ParseRadioButtonGroupElement(child, null, RadioButtonType.NotSet);
108 if (RadioButtonType.Bitmap == radioButtonType || RadioButtonType.Icon == radioButtonType)
109 {
110 var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
111 this.Core.Write(ErrorMessages.RadioButtonBitmapAndIconDisallowed(childSourceLineNumbers));
112 }
113 break;
114 case "TextStyle":
115 this.ParseTextStyleElement(child);
116 break;
117 case "UIText":
118 this.ParseUITextElement(child);
119 break;
120
121 // the following are available indentically under the UI and Product elements for document organization use only
122 case "AdminUISequence":
123 this.ParseSequenceElement(child, SequenceTable.AdminUISequence);
124 break;
125 case "InstallUISequence":
126 this.ParseSequenceElement(child, SequenceTable.InstallUISequence);
127 break;
128 case "Binary":
129 this.ParseBinaryElement(child);
130 break;
131 case "Property":
132 this.ParsePropertyElement(child);
133 break;
134 case "PropertyRef":
135 this.ParseSimpleRefElement(child, SymbolDefinitions.Property);
136 break;
137 case "UIRef":
138 this.ParseSimpleRefElement(child, SymbolDefinitions.WixUI);
139 break;
140
141 default:
142 this.Core.UnexpectedElement(node, child);
143 break;
144 }
145 }
146 else
147 {
148 this.Core.ParseExtensionElement(node, child);
149 }
150 }
151
152 if (null != id && !this.Core.EncounteredError)
153 {
154 this.Core.AddSymbol(new WixUISymbol(sourceLineNumbers, id));
155 }
156 }
157
158 /// <summary>
159 /// Parses a list item element.
160 /// </summary>
161 /// <param name="node">Element to parse.</param>
162 /// <param name="symbolType">Type of symbol to create.</param>
163 /// <param name="property">Identifier of property referred to by list item.</param>
164 /// <param name="order">Relative order of list items.</param>
165 private void ParseListItemElement(XElement node, SymbolDefinitionType symbolType, string property, ref int order)
166 {
167 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
168 string icon = null;
169 string text = null;
170 string value = null;
171
172 foreach (var attrib in node.Attributes())
173 {
174 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
175 {
176 switch (attrib.Name.LocalName)
177 {
178 case "Icon":
179 if (SymbolDefinitionType.ListView == symbolType)
180 {
181 icon = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
182 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Binary, icon);
183 }
184 else
185 {
186 this.Core.Write(ErrorMessages.IllegalAttributeExceptOnElement(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "ListView"));
187 }
188 break;
189 case "Text":
190 text = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
191 break;
192 case "Value":
193 value = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
194 break;
195 default:
196 this.Core.UnexpectedAttribute(node, attrib);
197 break;
198 }
199 }
200 else
201 {
202 this.Core.ParseExtensionAttribute(node, attrib);
203 }
204 }
205
206 if (null == value)
207 {
208 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value"));
209 }
210
211 this.Core.ParseForExtensionElements(node);
212
213 if (!this.Core.EncounteredError)
214 {
215 switch (symbolType)
216 {
217 case SymbolDefinitionType.ComboBox:
218 this.Core.AddSymbol(new ComboBoxSymbol(sourceLineNumbers)
219 {
220 Property = property,
221 Order = ++order,
222 Value = value,
223 Text = text,
224 });
225 break;
226 case SymbolDefinitionType.ListBox:
227 this.Core.AddSymbol(new ListBoxSymbol(sourceLineNumbers)
228 {
229 Property = property,
230 Order = ++order,
231 Value = value,
232 Text = text,
233 });
234 break;
235 case SymbolDefinitionType.ListView:
236 var symbol = this.Core.AddSymbol(new ListViewSymbol(sourceLineNumbers)
237 {
238 Property = property,
239 Order = ++order,
240 Value = value,
241 Text = text,
242 });
243
244 if (null != icon)
245 {
246 symbol.BinaryRef = icon;
247 }
248 break;
249 default:
250 throw new ArgumentOutOfRangeException(nameof(symbolType));
251 }
252 }
253 }
254
255 /// <summary>
256 /// Parses a radio button element.
257 /// </summary>
258 /// <param name="node">Element to parse.</param>
259 /// <param name="property">Identifier of property referred to by radio button.</param>
260 /// <param name="order">Relative order of radio buttons.</param>
261 /// <returns>Type of this radio button.</returns>
262 private RadioButtonType ParseRadioButtonElement(XElement node, string property, ref int order)
263 {
264 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
265 var type = RadioButtonType.NotSet;
266 string value = null;
267 string x = null;
268 string y = null;
269 string width = null;
270 string height = null;
271 string text = null;
272 string tooltip = null;
273 string help = null;
274
275 foreach (var attrib in node.Attributes())
276 {
277 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
278 {
279 switch (attrib.Name.LocalName)
280 {
281 case "Bitmap":
282 if (RadioButtonType.NotSet != type)
283 {
284 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Icon", "Text"));
285 }
286 text = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
287 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Binary, text);
288 type = RadioButtonType.Bitmap;
289 break;
290 case "Height":
291 height = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue);
292 break;
293 case "Help":
294 help = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
295 break;
296 case "Icon":
297 if (RadioButtonType.NotSet != type)
298 {
299 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Bitmap", "Text"));
300 }
301 text = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
302 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Binary, text);
303 type = RadioButtonType.Icon;
304 break;
305 case "Text":
306 if (RadioButtonType.NotSet != type)
307 {
308 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Bitmap", "Icon"));
309 }
310 text = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
311 type = RadioButtonType.Text;
312 break;
313 case "ToolTip":
314 tooltip = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
315 break;
316 case "Value":
317 value = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
318 break;
319 case "Width":
320 width = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue);
321 break;
322 case "X":
323 x = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue);
324 break;
325 case "Y":
326 y = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue);
327 break;
328 default:
329 this.Core.UnexpectedAttribute(node, attrib);
330 break;
331 }
332 }
333 else
334 {
335 this.Core.ParseExtensionAttribute(node, attrib);
336 }
337 }
338
339 if (null == value)
340 {
341 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value"));
342 }
343
344 if (null == x)
345 {
346 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "X"));
347 }
348
349 if (null == y)
350 {
351 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Y"));
352 }
353
354 if (null == width)
355 {
356 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Width"));
357 }
358
359 if (null == height)
360 {
361 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Height"));
362 }
363
364 this.Core.ParseForExtensionElements(node);
365
366 if (!this.Core.EncounteredError)
367 {
368 var symbol = this.Core.AddSymbol(new RadioButtonSymbol(sourceLineNumbers)
369 {
370 Property = property,
371 Order = ++order,
372 Value = value,
373 Text = text,
374 Help = (null != tooltip || null != help) ? String.Concat(tooltip, "|", help) : null
375 });
376
377 symbol.Set((int)RadioButtonSymbolFields.X, x);
378 symbol.Set((int)RadioButtonSymbolFields.Y, y);
379 symbol.Set((int)RadioButtonSymbolFields.Width, width);
380 symbol.Set((int)RadioButtonSymbolFields.Height, height);
381 }
382
383 return type;
384 }
385
386 /// <summary>
387 /// Parses a billboard element.
388 /// </summary>
389 /// <param name="node">Element to parse.</param>
390 private void ParseBillboardActionElement(XElement node)
391 {
392 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
393 string action = null;
394 var order = 0;
395
396 foreach (var attrib in node.Attributes())
397 {
398 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
399 {
400 switch (attrib.Name.LocalName)
401 {
402 case "Id":
403 action = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
404 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.WixAction, "InstallExecuteSequence", action);
405 break;
406 default:
407 this.Core.UnexpectedAttribute(node, attrib);
408 break;
409 }
410 }
411 else
412 {
413 this.Core.ParseExtensionAttribute(node, attrib);
414 }
415 }
416
417 if (null == action)
418 {
419 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
420 }
421
422 foreach (var child in node.Elements())
423 {
424 if (CompilerCore.WixNamespace == child.Name.Namespace)
425 {
426 switch (child.Name.LocalName)
427 {
428 case "Billboard":
429 order = order + 1;
430 this.ParseBillboardElement(child, action, order);
431 break;
432 default:
433 this.Core.UnexpectedElement(node, child);
434 break;
435 }
436 }
437 else
438 {
439 this.Core.ParseExtensionElement(node, child);
440 }
441 }
442 }
443
444 /// <summary>
445 /// Parses a billboard element.
446 /// </summary>
447 /// <param name="node">Element to parse.</param>
448 /// <param name="action">Action for the billboard.</param>
449 /// <param name="order">Order of the billboard.</param>
450 private void ParseBillboardElement(XElement node, string action, int order)
451 {
452 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
453 Identifier id = null;
454 string feature = null;
455
456 foreach (var attrib in node.Attributes())
457 {
458 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
459 {
460 switch (attrib.Name.LocalName)
461 {
462 case "Id":
463 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
464 break;
465 case "Feature":
466 feature = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
467 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Feature, feature);
468 break;
469 default:
470 this.Core.UnexpectedAttribute(node, attrib);
471 break;
472 }
473 }
474 else
475 {
476 this.Core.ParseExtensionAttribute(node, attrib);
477 }
478 }
479
480 if (null == id)
481 {
482 id = this.Core.CreateIdentifier("bil", action, order.ToString(), feature);
483 }
484
485 foreach (var child in node.Elements())
486 {
487 if (CompilerCore.WixNamespace == child.Name.Namespace)
488 {
489 switch (child.Name.LocalName)
490 {
491 case "Control":
492 // These are all thrown away.
493 ControlSymbol lastTabSymbol = null;
494 string firstControl = null;
495 string defaultControl = null;
496 string cancelControl = null;
497
498 this.ParseControlElement(child, id.Id, SymbolDefinitionType.BBControl, ref lastTabSymbol, ref firstControl, ref defaultControl, ref cancelControl);
499 break;
500 default:
501 this.Core.UnexpectedElement(node, child);
502 break;
503 }
504 }
505 else
506 {
507 this.Core.ParseExtensionElement(node, child);
508 }
509 }
510
511
512 if (!this.Core.EncounteredError)
513 {
514 this.Core.AddSymbol(new BillboardSymbol(sourceLineNumbers, id)
515 {
516 FeatureRef = feature,
517 Action = action,
518 Ordering = order
519 });
520 }
521 }
522
523 /// <summary>
524 /// Parses a control group element.
525 /// </summary>
526 /// <param name="node">Element to parse.</param>
527 /// <param name="symbolType">Symbol type referred to by control group.</param>
528 /// <param name="childTag">Expected child elements.</param>
529 private void ParseControlGroupElement(XElement node, SymbolDefinitionType symbolType, string childTag)
530 {
531 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
532 var order = 0;
533 string property = null;
534
535 foreach (var attrib in node.Attributes())
536 {
537 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
538 {
539 switch (attrib.Name.LocalName)
540 {
541 case "Property":
542 property = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
543 break;
544 default:
545 this.Core.UnexpectedAttribute(node, attrib);
546 break;
547 }
548 }
549 else
550 {
551 this.Core.ParseExtensionAttribute(node, attrib);
552 }
553 }
554
555 if (null == property)
556 {
557 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property"));
558 }
559
560 foreach (var child in node.Elements())
561 {
562 if (CompilerCore.WixNamespace == child.Name.Namespace)
563 {
564 if (childTag != child.Name.LocalName)
565 {
566 this.Core.UnexpectedElement(node, child);
567 }
568
569 switch (child.Name.LocalName)
570 {
571 case "ListItem":
572 this.ParseListItemElement(child, symbolType, property, ref order);
573 break;
574 case "Property":
575 this.ParsePropertyElement(child);
576 break;
577 default:
578 this.Core.UnexpectedElement(node, child);
579 break;
580 }
581 }
582 else
583 {
584 this.Core.ParseExtensionElement(node, child);
585 }
586 }
587
588 }
589
590 /// <summary>
591 /// Parses a radio button control group element.
592 /// </summary>
593 /// <param name="node">Element to parse.</param>
594 /// <param name="property">Property associated with this radio button group.</param>
595 /// <param name="groupType">Specifies the current type of radio buttons in the group.</param>
596 /// <returns>The current type of radio buttons in the group.</returns>
597 private RadioButtonType ParseRadioButtonGroupElement(XElement node, string property, RadioButtonType groupType)
598 {
599 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
600 var order = 0;
601
602 foreach (var attrib in node.Attributes())
603 {
604 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
605 {
606 switch (attrib.Name.LocalName)
607 {
608 case "Property":
609 property = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
610 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Property, property);
611 break;
612 default:
613 this.Core.UnexpectedAttribute(node, attrib);
614 break;
615 }
616 }
617 else
618 {
619 this.Core.ParseExtensionAttribute(node, attrib);
620 }
621 }
622
623 if (null == property)
624 {
625 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property"));
626 }
627
628 foreach (var child in node.Elements())
629 {
630 if (CompilerCore.WixNamespace == child.Name.Namespace)
631 {
632 switch (child.Name.LocalName)
633 {
634 case "RadioButton":
635 var type = this.ParseRadioButtonElement(child, property, ref order);
636 if (RadioButtonType.NotSet == groupType)
637 {
638 groupType = type;
639 }
640 else if (groupType != type)
641 {
642 var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
643 this.Core.Write(ErrorMessages.RadioButtonTypeInconsistent(childSourceLineNumbers));
644 }
645 break;
646 default:
647 this.Core.UnexpectedElement(node, child);
648 break;
649 }
650 }
651 else
652 {
653 this.Core.ParseExtensionElement(node, child);
654 }
655 }
656
657
658 return groupType;
659 }
660
661 /// <summary>
662 /// Parses an action text element.
663 /// </summary>
664 /// <param name="node">Element to parse.</param>
665 private void ParseActionTextElement(XElement node)
666 {
667 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
668 string action = null;
669 string message = null;
670 string template = null;
671
672 foreach (var attrib in node.Attributes())
673 {
674 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
675 {
676 switch (attrib.Name.LocalName)
677 {
678 case "Action":
679 action = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
680 break;
681 case "Message":
682 message = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
683 break;
684 case "Template":
685 template = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
686 break;
687 default:
688 this.Core.UnexpectedAttribute(node, attrib);
689 break;
690 }
691 }
692 else
693 {
694 this.Core.ParseExtensionAttribute(node, attrib);
695 }
696 }
697
698 if (null == action)
699 {
700 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Action"));
701 }
702
703 this.Core.ParseForExtensionElements(node);
704
705 if (!this.Core.EncounteredError)
706 {
707 this.Core.AddSymbol(new ActionTextSymbol(sourceLineNumbers)
708 {
709 Action = action,
710 Description = message,
711 Template = template,
712 });
713 }
714 }
715
716 /// <summary>
717 /// Parses an ui text element.
718 /// </summary>
719 /// <param name="node">Element to parse.</param>
720 private void ParseUITextElement(XElement node)
721 {
722 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
723 Identifier id = null;
724 string text = null;
725
726 foreach (var attrib in node.Attributes())
727 {
728 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
729 {
730 switch (attrib.Name.LocalName)
731 {
732 case "Id":
733 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
734 break;
735 case "Value":
736 text = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
737 break;
738 default:
739 this.Core.UnexpectedAttribute(node, attrib);
740 break;
741 }
742 }
743 else
744 {
745 this.Core.ParseExtensionAttribute(node, attrib);
746 }
747 }
748
749 if (null == id)
750 {
751 id = this.Core.CreateIdentifier("txt", text);
752 }
753
754 this.Core.ParseForExtensionElements(node);
755
756 if (!this.Core.EncounteredError)
757 {
758 this.Core.AddSymbol(new UITextSymbol(sourceLineNumbers, id)
759 {
760 Text = text,
761 });
762 }
763 }
764
765 /// <summary>
766 /// Parses a text style element.
767 /// </summary>
768 /// <param name="node">Element to parse.</param>
769 private void ParseTextStyleElement(XElement node)
770 {
771 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
772 Identifier id = null;
773 int? red = null;
774 int? green = null;
775 int? blue = null;
776 var bold = false;
777 var italic = false;
778 var strike = false;
779 var underline = false;
780 string faceName = null;
781 var size = "0";
782
783 foreach (var attrib in node.Attributes())
784 {
785 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
786 {
787 switch (attrib.Name.LocalName)
788 {
789 case "Id":
790 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
791 break;
792
793 // RGB Values
794 case "Red":
795 var redColor = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Byte.MaxValue);
796 if (CompilerConstants.IllegalInteger != redColor)
797 {
798 red = redColor;
799 }
800 break;
801 case "Green":
802 var greenColor = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Byte.MaxValue);
803 if (CompilerConstants.IllegalInteger != greenColor)
804 {
805 green = greenColor;
806 }
807 break;
808 case "Blue":
809 var blueColor = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Byte.MaxValue);
810 if (CompilerConstants.IllegalInteger != blueColor)
811 {
812 blue = blueColor;
813 }
814 break;
815
816 // Style values
817 case "Bold":
818 bold = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
819 break;
820 case "Italic":
821 italic = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
822 break;
823 case "Strike":
824 strike = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
825 break;
826 case "Underline":
827 underline = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
828 break;
829
830 // Font values
831 case "FaceName":
832 faceName = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
833 break;
834 case "Size":
835 size = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
836 break;
837
838 default:
839 this.Core.UnexpectedAttribute(node, attrib);
840 break;
841 }
842 }
843 else
844 {
845 this.Core.ParseExtensionAttribute(node, attrib);
846 }
847 }
848
849 if (null == id)
850 {
851 this.Core.CreateIdentifier("txs", faceName, size.ToString(), (red ?? 0).ToString(), (green ?? 0).ToString(), (blue ?? 0).ToString(), bold.ToString(), italic.ToString(), strike.ToString(), underline.ToString());
852 }
853
854 if (null == faceName)
855 {
856 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "FaceName"));
857 }
858
859 this.Core.ParseForExtensionElements(node);
860
861 if (!this.Core.EncounteredError)
862 {
863 this.Core.AddSymbol(new TextStyleSymbol(sourceLineNumbers, id)
864 {
865 FaceName = faceName,
866 LocalizedSize = size,
867 Red = red,
868 Green = green,
869 Blue = blue,
870 Bold = bold,
871 Italic = italic,
872 Strike = strike,
873 Underline = underline,
874 });
875 }
876 }
877
878 /// <summary>
879 /// Parses a dialog element.
880 /// </summary>
881 /// <param name="node">Element to parse.</param>
882 private void ParseDialogElement(XElement node)
883 {
884 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
885 Identifier id = null;
886 var hidden = false;
887 var modal = true;
888 var minimize = true;
889 var customPalette = false;
890 var errorDialog = false;
891 var keepModeless = false;
892 var height = 0;
893 string title = null;
894 var leftScroll = false;
895 var rightAligned = false;
896 var rightToLeft = false;
897 var systemModal = false;
898 var trackDiskSpace = false;
899 var width = 0;
900 var x = 50;
901 var y = 50;
902
903 foreach (var attrib in node.Attributes())
904 {
905 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
906 {
907 switch (attrib.Name.LocalName)
908 {
909 case "Id":
910 id = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
911 break;
912 case "Height":
913 height = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue);
914 break;
915 case "Title":
916 title = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
917 break;
918 case "Width":
919 width = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue);
920 break;
921 case "X":
922 x = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, 100);
923 break;
924 case "Y":
925 y = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, 100);
926 break;
927 case "CustomPalette":
928 customPalette = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
929 break;
930 case "ErrorDialog":
931 errorDialog = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
932 break;
933 case "Hidden":
934 hidden = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
935 break;
936 case "KeepModeless":
937 keepModeless = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
938 break;
939 case "LeftScroll":
940 leftScroll = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
941 break;
942 case "Modeless":
943 modal = YesNoType.Yes != this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
944 break;
945 case "NoMinimize":
946 minimize = YesNoType.Yes != this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
947 break;
948 case "RightAligned":
949 rightAligned = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
950 break;
951 case "RightToLeft":
952 rightToLeft = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
953 break;
954 case "SystemModal":
955 systemModal = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
956 break;
957 case "TrackDiskSpace":
958 trackDiskSpace = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
959 break;
960
961 default:
962 this.Core.UnexpectedAttribute(node, attrib);
963 break;
964 }
965 }
966 else
967 {
968 this.Core.ParseExtensionAttribute(node, attrib);
969 }
970 }
971
972 if (null == id)
973 {
974 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
975 id = Identifier.Invalid;
976 }
977
978 ControlSymbol lastTabSymbol = null;
979 string cancelControl = null;
980 string defaultControl = null;
981 string firstControl = null;
982
983 foreach (var child in node.Elements())
984 {
985 if (CompilerCore.WixNamespace == child.Name.Namespace)
986 {
987 switch (child.Name.LocalName)
988 {
989 case "Control":
990 this.ParseControlElement(child, id.Id, SymbolDefinitionType.Control, ref lastTabSymbol, ref firstControl, ref defaultControl, ref cancelControl);
991 break;
992 default:
993 this.Core.UnexpectedElement(node, child);
994 break;
995 }
996 }
997 else
998 {
999 this.Core.ParseExtensionElement(node, child);
1000 }
1001 }
1002
1003 if (null != lastTabSymbol && null != lastTabSymbol.Control)
1004 {
1005 if (firstControl != lastTabSymbol.Control)
1006 {
1007 lastTabSymbol.NextControlRef = firstControl;
1008 }
1009 }
1010
1011 if (null == firstControl)
1012 {
1013 this.Core.Write(ErrorMessages.NoFirstControlSpecified(sourceLineNumbers, id.Id));
1014 }
1015
1016 if (!this.Core.EncounteredError)
1017 {
1018 this.Core.AddSymbol(new DialogSymbol(sourceLineNumbers, id)
1019 {
1020 HCentering = x,
1021 VCentering = y,
1022 Width = width,
1023 Height = height,
1024 CustomPalette = customPalette,
1025 ErrorDialog = errorDialog,
1026 Visible = !hidden,
1027 Modal = modal,
1028 KeepModeless = keepModeless,
1029 LeftScroll = leftScroll,
1030 Minimize = minimize,
1031 RightAligned = rightAligned,
1032 RightToLeft = rightToLeft,
1033 SystemModal = systemModal,
1034 TrackDiskSpace = trackDiskSpace,
1035 Title = title,
1036 FirstControlRef = firstControl,
1037 DefaultControlRef = defaultControl,
1038 CancelControlRef = cancelControl,
1039 });
1040 }
1041 }
1042
1043 /// <summary>
1044 /// Parses a control element.
1045 /// </summary>
1046 /// <param name="node">Element to parse.</param>
1047 /// <param name="dialog">Identifier for parent dialog.</param>
1048 /// <param name="symbolType">Table control belongs in.</param>
1049 /// <param name="lastTabSymbol">Last control in the tab order.</param>
1050 /// <param name="firstControl">Name of the first control in the tab order.</param>
1051 /// <param name="defaultControl">Name of the default control.</param>
1052 /// <param name="cancelControl">Name of the candle control.</param>
1053 private void ParseControlElement(XElement node, string dialog, SymbolDefinitionType symbolType, ref ControlSymbol lastTabSymbol, ref string firstControl, ref string defaultControl, ref string cancelControl)
1054 {
1055 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1056 Identifier controlId = null;
1057 var bits = new BitArray(32);
1058 string checkBoxPropertyRef = null;
1059 string checkboxValue = null;
1060 string controlType = null;
1061 var disabled = false;
1062 string height = null;
1063 string help = null;
1064 var isCancel = false;
1065 var isDefault = false;
1066 var notTabbable = false;
1067 string property = null;
1068 var publishOrder = 0;
1069 string sourceFile = null;
1070 string text = null;
1071 string tooltip = null;
1072 var radioButtonsType = RadioButtonType.NotSet;
1073 string width = null;
1074 string x = null;
1075 string y = null;
1076
1077 string defaultCondition = null;
1078 string enableCondition = null;
1079 string disableCondition = null;
1080 string hideCondition = null;
1081 string showCondition = null;
1082
1083 var hidden = false;
1084 var sunken = false;
1085 var indirect = false;
1086 var integer = false;
1087 var rightToLeft = false;
1088 var rightAligned = false;
1089 var leftScroll = false;
1090
1091 // The rest of the method relies on the control's Type, so we have to get that first.
1092 var typeAttribute = node.Attribute("Type");
1093 if (null == typeAttribute)
1094 {
1095 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Type"));
1096 }
1097 else
1098 {
1099 controlType = this.Core.GetAttributeValue(sourceLineNumbers, typeAttribute);
1100 }
1101
1102 string[] specialAttributes;
1103 switch (controlType)
1104 {
1105 case "Billboard":
1106 specialAttributes = null;
1107 notTabbable = true;
1108 disabled = true;
1109
1110 this.Core.EnsureTable(sourceLineNumbers, WindowsInstallerTableDefinitions.Billboard);
1111 break;
1112 case "Bitmap":
1113 specialAttributes = BitmapControlAttributes;
1114 notTabbable = true;
1115 disabled = true;
1116 break;
1117 case "CheckBox":
1118 specialAttributes = CheckboxControlAttributes;
1119 break;
1120 case "ComboBox":
1121 specialAttributes = ComboboxControlAttributes;
1122 break;
1123 case "DirectoryCombo":
1124 specialAttributes = VolumeControlAttributes;
1125 break;
1126 case "DirectoryList":
1127 specialAttributes = null;
1128 break;
1129 case "Edit":
1130 specialAttributes = EditControlAttributes;
1131 break;
1132 case "GroupBox":
1133 specialAttributes = null;
1134 notTabbable = true;
1135 break;
1136 case "Hyperlink":
1137 specialAttributes = HyperlinkControlAttributes;
1138 break;
1139 case "Icon":
1140 specialAttributes = IconControlAttributes;
1141 notTabbable = true;
1142 disabled = true;
1143 break;
1144 case "Line":
1145 specialAttributes = null;
1146 notTabbable = true;
1147 disabled = true;
1148 break;
1149 case "ListBox":
1150 specialAttributes = ListboxControlAttributes;
1151 break;
1152 case "ListView":
1153 specialAttributes = ListviewControlAttributes;
1154 break;
1155 case "MaskedEdit":
1156 specialAttributes = EditControlAttributes;
1157 break;
1158 case "PathEdit":
1159 specialAttributes = EditControlAttributes;
1160 break;
1161 case "ProgressBar":
1162 specialAttributes = ProgressControlAttributes;
1163 notTabbable = true;
1164 disabled = true;
1165 break;
1166 case "PushButton":
1167 specialAttributes = ButtonControlAttributes;
1168 break;
1169 case "RadioButtonGroup":
1170 specialAttributes = RadioControlAttributes;
1171 break;
1172 case "ScrollableText":
1173 specialAttributes = null;
1174 break;
1175 case "SelectionTree":
1176 specialAttributes = null;
1177 break;
1178 case "Text":
1179 specialAttributes = TextControlAttributes;
1180 notTabbable = true;
1181 break;
1182 case "VolumeCostList":
1183 specialAttributes = VolumeControlAttributes;
1184 notTabbable = true;
1185 break;
1186 case "VolumeSelectCombo":
1187 specialAttributes = VolumeControlAttributes;
1188 break;
1189 default:
1190 specialAttributes = null;
1191 notTabbable = true;
1192 break;
1193 }
1194
1195 foreach (var attrib in node.Attributes())
1196 {
1197 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
1198 {
1199 switch (attrib.Name.LocalName)
1200 {
1201 case "Id":
1202 controlId = this.Core.GetAttributeIdentifier(sourceLineNumbers, attrib);
1203 break;
1204 case "Type": // already processed
1205 break;
1206 case "Cancel":
1207 isCancel = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1208 break;
1209 case "CheckBoxPropertyRef":
1210 checkBoxPropertyRef = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1211 break;
1212 case "CheckBoxValue":
1213 checkboxValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1214 break;
1215 case "Default":
1216 isDefault = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1217 break;
1218 case "DefaultCondition":
1219 defaultCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1220 break;
1221 case "EnableCondition":
1222 enableCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1223 break;
1224 case "DisableCondition":
1225 disableCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1226 break;
1227 case "HideCondition":
1228 hideCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1229 break;
1230 case "ShowCondition":
1231 showCondition = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1232 break;
1233 case "Height":
1234 height = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue);
1235 break;
1236 case "Help":
1237 help = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1238 break;
1239 case "IconSize":
1240 var iconSizeValue = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1241 if (null != specialAttributes)
1242 {
1243 switch (iconSizeValue)
1244 {
1245 case "16":
1246 this.Core.TrySetBitFromName(specialAttributes, "Icon16", YesNoType.Yes, bits, 16);
1247 break;
1248 case "32":
1249 this.Core.TrySetBitFromName(specialAttributes, "Icon32", YesNoType.Yes, bits, 16);
1250 break;
1251 case "48":
1252 this.Core.TrySetBitFromName(specialAttributes, "Icon16", YesNoType.Yes, bits, 16);
1253 this.Core.TrySetBitFromName(specialAttributes, "Icon32", YesNoType.Yes, bits, 16);
1254 break;
1255 case "":
1256 break;
1257 default:
1258 this.Core.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, iconSizeValue, "16", "32", "48"));
1259 break;
1260 }
1261 }
1262 else
1263 {
1264 this.Core.Write(ErrorMessages.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, iconSizeValue, "Type"));
1265 }
1266 break;
1267 case "Property":
1268 property = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1269 break;
1270 case "TabSkip":
1271 notTabbable = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1272 break;
1273 case "Text":
1274 text = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1275 break;
1276 case "ToolTip":
1277 tooltip = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1278 break;
1279 case "Width":
1280 width = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue);
1281 break;
1282 case "X":
1283 x = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue);
1284 break;
1285 case "Y":
1286 y = this.Core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, Int16.MaxValue);
1287 break;
1288 case "Disabled":
1289 disabled = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1290 break;
1291 case "Hidden":
1292 hidden = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1293 break;
1294 case "Sunken":
1295 sunken = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1296 break;
1297 case "Indirect":
1298 indirect = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1299 break;
1300 case "Integer":
1301 integer = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1302 break;
1303 case "RightToLeft":
1304 rightToLeft = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1305 break;
1306 case "RightAligned":
1307 rightAligned = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1308 break;
1309 case "LeftScroll":
1310 leftScroll = YesNoType.Yes == this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1311 break;
1312 default:
1313 var attribValue = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1314 if (null == specialAttributes || !this.Core.TrySetBitFromName(specialAttributes, attrib.Name.LocalName, attribValue, bits, 16))
1315 {
1316 this.Core.UnexpectedAttribute(node, attrib);
1317 }
1318 break;
1319 }
1320 }
1321 else
1322 {
1323 this.Core.ParseExtensionAttribute(node, attrib);
1324 }
1325 }
1326
1327 var attributes = this.Core.CreateIntegerFromBitArray(bits);
1328
1329 //if (disabled)
1330 //{
1331 // attributes |= WindowsInstallerConstants.MsidbControlAttributesEnabled; // bit will be inverted when stored
1332 //}
1333
1334 if (null == height)
1335 {
1336 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Height"));
1337 }
1338
1339 if (null == width)
1340 {
1341 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Width"));
1342 }
1343
1344 if (null == x)
1345 {
1346 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "X"));
1347 }
1348
1349 if (null == y)
1350 {
1351 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Y"));
1352 }
1353
1354 if (null == controlId)
1355 {
1356 controlId = this.Core.CreateIdentifier("ctl", dialog, x, y, height, width);
1357 }
1358
1359 if (isCancel)
1360 {
1361 cancelControl = controlId.Id;
1362 }
1363
1364 if (isDefault)
1365 {
1366 defaultControl = controlId.Id;
1367 }
1368
1369 foreach (var child in node.Elements())
1370 {
1371 if (CompilerCore.WixNamespace == child.Name.Namespace)
1372 {
1373 var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
1374 switch (child.Name.LocalName)
1375 {
1376 case "Binary":
1377 this.ParseBinaryElement(child);
1378 break;
1379 case "ComboBox":
1380 this.ParseControlGroupElement(child, SymbolDefinitionType.ComboBox, "ListItem");
1381 break;
1382 case "ListBox":
1383 this.ParseControlGroupElement(child, SymbolDefinitionType.ListBox, "ListItem");
1384 break;
1385 case "ListView":
1386 this.ParseControlGroupElement(child, SymbolDefinitionType.ListView, "ListItem");
1387 break;
1388 case "Property":
1389 this.ParsePropertyElement(child);
1390 break;
1391 case "Publish":
1392 this.ParsePublishElement(child, dialog ?? String.Empty, controlId.Id, ref publishOrder);
1393 break;
1394 case "RadioButtonGroup":
1395 radioButtonsType = this.ParseRadioButtonGroupElement(child, property, radioButtonsType);
1396 break;
1397 case "Subscribe":
1398 this.ParseSubscribeElement(child, dialog, controlId.Id);
1399 break;
1400 case "Text":
1401 foreach (var attrib in child.Attributes())
1402 {
1403 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
1404 {
1405 switch (attrib.Name.LocalName)
1406 {
1407 case "SourceFile":
1408 sourceFile = this.Core.GetAttributeValue(childSourceLineNumbers, attrib);
1409 break;
1410 case "Value":
1411 text = this.Core.GetAttributeValue(childSourceLineNumbers, attrib);
1412 break;
1413 default:
1414 this.Core.UnexpectedAttribute(child, attrib);
1415 break;
1416 }
1417 }
1418 else
1419 {
1420 this.Core.ParseExtensionAttribute(child, attrib);
1421 }
1422 }
1423
1424 this.Core.InnerTextDisallowed(child);
1425
1426 if (!String.IsNullOrEmpty(text) && null != sourceFile)
1427 {
1428 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(childSourceLineNumbers, child.Name.LocalName, "SourceFile", "Value"));
1429 }
1430 break;
1431 default:
1432 this.Core.UnexpectedElement(node, child);
1433 break;
1434 }
1435 }
1436 else
1437 {
1438 this.Core.ParseExtensionElement(node, child);
1439 }
1440 }
1441
1442 this.Core.InnerTextDisallowed(node);
1443
1444 // If the radio buttons have icons, then we need to add the icon attribute.
1445 switch (radioButtonsType)
1446 {
1447 case RadioButtonType.Bitmap:
1448 attributes |= WindowsInstallerConstants.MsidbControlAttributesBitmap;
1449 break;
1450 case RadioButtonType.Icon:
1451 attributes |= WindowsInstallerConstants.MsidbControlAttributesIcon;
1452 break;
1453 case RadioButtonType.Text:
1454 // Text is the default so nothing needs to be added bits
1455 break;
1456 }
1457
1458 // the logic for creating control rows is a little tricky because of the way tabable controls are set
1459 IntermediateSymbol symbol = null;
1460 if (!this.Core.EncounteredError)
1461 {
1462 if ("CheckBox" == controlType)
1463 {
1464 if (String.IsNullOrEmpty(property) && String.IsNullOrEmpty(checkBoxPropertyRef))
1465 {
1466 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "CheckBoxPropertyRef", true));
1467 }
1468 else if (!String.IsNullOrEmpty(property) && !String.IsNullOrEmpty(checkBoxPropertyRef))
1469 {
1470 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "CheckBoxPropertyRef"));
1471 }
1472 else if (!String.IsNullOrEmpty(property))
1473 {
1474 this.Core.AddSymbol(new CheckBoxSymbol(sourceLineNumbers)
1475 {
1476 Property = property,
1477 Value = checkboxValue,
1478 });
1479 }
1480 else
1481 {
1482 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.CheckBox, checkBoxPropertyRef);
1483 }
1484 }
1485
1486 var id = new Identifier(controlId.Access, dialog, controlId.Id);
1487
1488 if (SymbolDefinitionType.BBControl == symbolType)
1489 {
1490 var bbSymbol = this.Core.AddSymbol(new BBControlSymbol(sourceLineNumbers, id)
1491 {
1492 BillboardRef = dialog,
1493 BBControl = controlId.Id,
1494 Type = controlType,
1495 Attributes = attributes,
1496 Enabled = !disabled,
1497 Indirect = indirect,
1498 Integer = integer,
1499 LeftScroll = leftScroll,
1500 RightAligned = rightAligned,
1501 RightToLeft = rightToLeft,
1502 Sunken = sunken,
1503 Visible = !hidden,
1504 Text = text,
1505 SourceFile = String.IsNullOrEmpty(sourceFile) ? null : new IntermediateFieldPathValue { Path = sourceFile }
1506 });
1507
1508 bbSymbol.Set((int)BBControlSymbolFields.X, x);
1509 bbSymbol.Set((int)BBControlSymbolFields.Y, y);
1510 bbSymbol.Set((int)BBControlSymbolFields.Width, width);
1511 bbSymbol.Set((int)BBControlSymbolFields.Height, height);
1512
1513 symbol = bbSymbol;
1514 }
1515 else
1516 {
1517 var controlSymbol = this.Core.AddSymbol(new ControlSymbol(sourceLineNumbers, id)
1518 {
1519 DialogRef = dialog,
1520 Control = controlId.Id,
1521 Type = controlType,
1522 Attributes = attributes,
1523 Enabled = !disabled,
1524 Indirect = indirect,
1525 Integer = integer,
1526 LeftScroll = leftScroll,
1527 RightAligned = rightAligned,
1528 RightToLeft = rightToLeft,
1529 Sunken = sunken,
1530 Visible = !hidden,
1531 Property = !String.IsNullOrEmpty(property) ? property : checkBoxPropertyRef,
1532 Text = text,
1533 Help = (null == tooltip && null == help) ? null : String.Concat(tooltip, "|", help), // Separator is required, even if only one is non-null.};
1534 SourceFile = String.IsNullOrEmpty(sourceFile) ? null : new IntermediateFieldPathValue { Path = sourceFile }
1535 });
1536
1537 controlSymbol.Set((int)BBControlSymbolFields.X, x);
1538 controlSymbol.Set((int)BBControlSymbolFields.Y, y);
1539 controlSymbol.Set((int)BBControlSymbolFields.Width, width);
1540 controlSymbol.Set((int)BBControlSymbolFields.Height, height);
1541
1542 symbol = controlSymbol;
1543 }
1544
1545 if (!String.IsNullOrEmpty(defaultCondition))
1546 {
1547 this.Core.AddSymbol(new ControlConditionSymbol(sourceLineNumbers)
1548 {
1549 DialogRef = dialog,
1550 ControlRef = controlId.Id,
1551 Action = "Default",
1552 Condition = defaultCondition,
1553 });
1554 }
1555
1556 if (!String.IsNullOrEmpty(enableCondition))
1557 {
1558 this.Core.AddSymbol(new ControlConditionSymbol(sourceLineNumbers)
1559 {
1560 DialogRef = dialog,
1561 ControlRef = controlId.Id,
1562 Action = "Enable",
1563 Condition = enableCondition,
1564 });
1565 }
1566
1567 if (!String.IsNullOrEmpty(disableCondition))
1568 {
1569 this.Core.AddSymbol(new ControlConditionSymbol(sourceLineNumbers)
1570 {
1571 DialogRef = dialog,
1572 ControlRef = controlId.Id,
1573 Action = "Disable",
1574 Condition = disableCondition,
1575 });
1576 }
1577
1578 if (!String.IsNullOrEmpty(hideCondition))
1579 {
1580 this.Core.AddSymbol(new ControlConditionSymbol(sourceLineNumbers)
1581 {
1582 DialogRef = dialog,
1583 ControlRef = controlId.Id,
1584 Action = "Hide",
1585 Condition = hideCondition,
1586 });
1587 }
1588
1589 if (!String.IsNullOrEmpty(showCondition))
1590 {
1591 this.Core.AddSymbol(new ControlConditionSymbol(sourceLineNumbers)
1592 {
1593 DialogRef = dialog,
1594 ControlRef = controlId.Id,
1595 Action = "Show",
1596 Condition = showCondition,
1597 });
1598 }
1599 }
1600
1601 if (!notTabbable)
1602 {
1603 if (symbol is ControlSymbol controlSymbol)
1604 {
1605 if (null != lastTabSymbol)
1606 {
1607 lastTabSymbol.NextControlRef = controlSymbol.Control;
1608 }
1609 lastTabSymbol = controlSymbol;
1610 }
1611 else if (symbol != null)
1612 {
1613 this.Core.Write(ErrorMessages.TabbableControlNotAllowedInBillboard(sourceLineNumbers, node.Name.LocalName, controlType));
1614 }
1615
1616 if (null == firstControl)
1617 {
1618 firstControl = controlId.Id;
1619 }
1620 }
1621
1622 // bitmap and icon controls contain a foreign key into the binary table in the text column;
1623 // add a reference if the identifier of the binary entry is known during compilation
1624 if (("Bitmap" == controlType || "Icon" == controlType) && Common.IsIdentifier(text))
1625 {
1626 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Binary, text);
1627 }
1628 }
1629
1630 /// <summary>
1631 /// Parses a publish control event element.
1632 /// </summary>
1633 /// <param name="node">Element to parse.</param>
1634 /// <param name="dialog">Identifier of parent dialog.</param>
1635 /// <param name="control">Identifier of parent control.</param>
1636 /// <param name="order">Relative order of controls.</param>
1637 private void ParsePublishElement(XElement node, string dialog, string control, ref int order)
1638 {
1639 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1640 string argument = null;
1641 string condition = null;
1642 string controlEvent = null;
1643 string property = null;
1644
1645 // give this control event a unique ordering
1646 order++;
1647
1648 foreach (var attrib in node.Attributes())
1649 {
1650 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
1651 {
1652 switch (attrib.Name.LocalName)
1653 {
1654 case "Condition":
1655 condition = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1656 break;
1657 case "Control":
1658 if (null != control)
1659 {
1660 this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, node.Parent.Name.LocalName));
1661 }
1662 control = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
1663 break;
1664 case "Dialog":
1665 if (null != dialog)
1666 {
1667 this.Core.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, node.Parent.Name.LocalName));
1668 }
1669 dialog = this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
1670 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Dialog, dialog);
1671 break;
1672 case "Event":
1673 controlEvent = Compiler.UppercaseFirstChar(this.Core.GetAttributeValue(sourceLineNumbers, attrib));
1674 break;
1675 case "Order":
1676 order = this.Core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, 2147483647);
1677 break;
1678 case "Property":
1679 property = String.Concat("[", this.Core.GetAttributeValue(sourceLineNumbers, attrib), "]");
1680 break;
1681 case "Value":
1682 argument = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
1683 break;
1684 default:
1685 this.Core.UnexpectedAttribute(node, attrib);
1686 break;
1687 }
1688 }
1689 else
1690 {
1691 this.Core.ParseExtensionAttribute(node, attrib);
1692 }
1693 }
1694
1695 if (null == control)
1696 {
1697 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Control"));
1698 }
1699
1700 if (null == dialog)
1701 {
1702 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Dialog"));
1703 }
1704
1705 if (null == controlEvent && null == property) // need to specify at least one
1706 {
1707 this.Core.Write(ErrorMessages.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "Event", "Property"));
1708 }
1709 else if (null != controlEvent && null != property) // cannot specify both
1710 {
1711 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Event", "Property"));
1712 }
1713
1714 if (null == argument)
1715 {
1716 if (null != controlEvent)
1717 {
1718 this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value", "Event"));
1719 }
1720 else if (null != property)
1721 {
1722 // if this is setting a property to null, put a special value in the argument column
1723 argument = "{}";
1724 }
1725 }
1726
1727 this.Core.ParseForExtensionElements(node);
1728
1729 if (!this.Core.EncounteredError)
1730 {
1731 this.Core.AddSymbol(new ControlEventSymbol(sourceLineNumbers)
1732 {
1733 DialogRef = dialog,
1734 ControlRef = control,
1735 Event = controlEvent ?? property,
1736 Argument = argument,
1737 Condition = condition,
1738 Ordering = order
1739 });
1740 }
1741
1742 if ("DoAction" == controlEvent && null != argument)
1743 {
1744 // if we're not looking at a standard action or a formatted string then create a reference
1745 // to the custom action.
1746 if (!WindowsInstallerStandard.IsStandardAction(argument) && !this.Core.ContainsProperty(argument))
1747 {
1748 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.CustomAction, argument);
1749 }
1750 }
1751
1752 // if we're referring to a dialog but not through a property, add it to the references
1753 if (("NewDialog" == controlEvent || "SpawnDialog" == controlEvent || "SpawnWaitDialog" == controlEvent || "SelectionBrowse" == controlEvent) && Common.IsIdentifier(argument))
1754 {
1755 this.Core.CreateSimpleReference(sourceLineNumbers, SymbolDefinitions.Dialog, argument);
1756 }
1757 }
1758
1759 /// <summary>
1760 /// Parses a control subscription element.
1761 /// </summary>
1762 /// <param name="node">Element to parse.</param>
1763 /// <param name="dialog">Identifier of dialog.</param>
1764 /// <param name="control">Identifier of control.</param>
1765 private void ParseSubscribeElement(XElement node, string dialog, string control)
1766 {
1767 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1768 string controlAttribute = null;
1769 string eventMapping = null;
1770
1771 foreach (var attrib in node.Attributes())
1772 {
1773 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
1774 {
1775 switch (attrib.Name.LocalName)
1776 {
1777 case "Attribute":
1778 controlAttribute = Compiler.UppercaseFirstChar(this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib));
1779 break;
1780 case "Event":
1781 eventMapping = Compiler.UppercaseFirstChar(this.Core.GetAttributeIdentifierValue(sourceLineNumbers, attrib));
1782 break;
1783 default:
1784 this.Core.UnexpectedAttribute(node, attrib);
1785 break;
1786 }
1787 }
1788 else
1789 {
1790 this.Core.ParseExtensionAttribute(node, attrib);
1791 }
1792 }
1793
1794 this.Core.ParseForExtensionElements(node);
1795
1796 if (!this.Core.EncounteredError)
1797 {
1798 this.Core.AddSymbol(new EventMappingSymbol(sourceLineNumbers)
1799 {
1800 DialogRef = dialog,
1801 ControlRef = control,
1802 Event = eventMapping,
1803 Attribute = controlAttribute,
1804 });
1805 }
1806 }
1807 }
1808}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using WixToolset.Data;
6 using WixToolset.Extensibility.Data;
7
8 internal class ComponentKeyPath : IComponentKeyPath
9 {
10 /// <summary>
11 /// Identifier of the resource to be a key path.
12 /// </summary>
13 public string Id { get; set; }
14
15 /// <summary>
16 /// Indicates whether the key path was explicitly set for this resource.
17 /// </summary>
18 public bool Explicit { get; set; }
19
20 /// <summary>
21 /// Type of resource to be the key path.
22 /// </summary>
23 public PossibleKeyPathType Type { get; set; }
24 }
25}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System;
6 using System.Collections.Generic;
7 using WixToolset.Data;
8 using WixToolset.Extensibility;
9 using WixToolset.Extensibility.Data;
10 using WixToolset.Extensibility.Services;
11
12 internal class DecompileContext : IDecompileContext
13 {
14 internal DecompileContext(IServiceProvider serviceProvider)
15 {
16 this.ServiceProvider = serviceProvider;
17 }
18
19 public IServiceProvider ServiceProvider { get; }
20
21 public string DecompilePath { get; set; }
22
23 public OutputType DecompileType { get; set; }
24
25 public IReadOnlyCollection<IDecompilerExtension> Extensions { get; set; }
26
27 public string ExtractFolder { get; set; }
28
29 public string CabinetExtractFolder { get; set; }
30
31 public string BaseSourcePath { get; set; }
32
33 public string IntermediateFolder { get; set; }
34
35 public bool IsAdminImage { get; set; }
36
37 public string OutputPath { get; set; }
38
39 public bool SuppressCustomTables { get; set; }
40
41 public bool SuppressDroppingEmptyTables { get; set; }
42
43 public bool SuppressExtractCabinets { get; set; }
44
45 public bool SuppressUI { get; set; }
46
47 public bool TreatProductAsModule { get; set; }
48 }
49}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System.Collections.Generic;
6 using System.Xml.Linq;
7 using WixToolset.Data;
8 using WixToolset.Extensibility.Data;
9
10 internal class DecompileResult : IDecompileResult
11 {
12 public XDocument Document { get; set; }
13
14 public IReadOnlyCollection<string> ExtractedFilePaths { get; set; }
15
16 public Platform? Platform { get; set; }
17 }
18}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System;
6 using WixToolset.Extensibility;
7 using WixToolset.Extensibility.Data;
8 using WixToolset.Extensibility.Services;
9
10 /// <summary>
11 /// Decompiler of the WiX toolset.
12 /// </summary>
13 internal class Decompiler : IDecompiler
14 {
15 internal Decompiler(IServiceProvider serviceProvider)
16 {
17 this.ServiceProvider = serviceProvider;
18 }
19
20 public IServiceProvider ServiceProvider { get; }
21
22 public IDecompileResult Decompile(IDecompileContext context)
23 {
24 // Pre-decompile.
25 //
26 foreach (var extension in context.Extensions)
27 {
28 extension.PreDecompile(context);
29 }
30
31 // Decompile.
32 //
33 var result = this.BackendDecompile(context);
34
35 if (result != null)
36 {
37 // Post-decompile.
38 //
39 foreach (var extension in context.Extensions)
40 {
41 extension.PostDecompile(result);
42 }
43 }
44
45 return result;
46 }
47
48 private IDecompileResult BackendDecompile(IDecompileContext context)
49 {
50 var extensionManager = context.ServiceProvider.GetService<IExtensionManager>();
51
52 var backendFactories = extensionManager.GetServices<IBackendFactory>();
53
54 foreach (var factory in backendFactories)
55 {
56 if (factory.TryCreateBackend(context.DecompileType.ToString(), context.OutputPath, out var backend))
57 {
58 var result = backend.Decompile(context);
59 return result;
60 }
61 }
62
63 // TODO: messaging that a backend could not be found to decompile the decompile type?
64
65 return null;
66 }
67 }
68}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.ExtensibilityServices
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using WixToolset.Core.Bind;
9 using WixToolset.Data;
10 using WixToolset.Data.Symbols;
11 using WixToolset.Data.WindowsInstaller.Rows;
12 using WixToolset.Extensibility.Data;
13 using WixToolset.Extensibility.Services;
14
15 internal class BackendHelper : IBackendHelper
16 {
17 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" };
18
19 public BackendHelper(IServiceProvider serviceProvider)
20 {
21 this.Messaging = serviceProvider.GetService<IMessaging>();
22 }
23
24 private IMessaging Messaging { get; }
25
26 public IFileFacade CreateFileFacade(FileSymbol file, AssemblySymbol assembly)
27 {
28 return new FileFacade(file, assembly);
29 }
30
31 public IFileFacade CreateFileFacade(FileRow fileRow)
32 {
33 return new FileFacade(fileRow);
34 }
35
36 public IFileFacade CreateFileFacadeFromMergeModule(FileSymbol fileSymbol)
37 {
38 return new FileFacade(true, fileSymbol);
39 }
40
41 public IFileTransfer CreateFileTransfer(string source, string destination, bool move, SourceLineNumber sourceLineNumbers = null)
42 {
43 var sourceFullPath = this.GetValidatedFullPath(sourceLineNumbers, source);
44
45 var destinationFullPath = this.GetValidatedFullPath(sourceLineNumbers, destination);
46
47 return (String.IsNullOrEmpty(sourceFullPath) || String.IsNullOrEmpty(destinationFullPath)) ? null : new FileTransfer
48 {
49 Source = sourceFullPath,
50 Destination = destinationFullPath,
51 Move = move,
52 SourceLineNumbers = sourceLineNumbers,
53 Redundant = String.Equals(sourceFullPath, destinationFullPath, StringComparison.OrdinalIgnoreCase)
54 };
55 }
56
57 public string CreateGuid()
58 {
59 return Common.GenerateGuid();
60 }
61
62 public string CreateGuid(Guid namespaceGuid, string value)
63 {
64 return Uuid.NewUuid(namespaceGuid, value).ToString("B").ToUpperInvariant();
65 }
66
67 public IResolvedDirectory CreateResolvedDirectory(string directoryParent, string name)
68 {
69 return new ResolvedDirectory
70 {
71 DirectoryParent = directoryParent,
72 Name = name
73 };
74 }
75
76 public IReadOnlyList<ITrackedFile> ExtractEmbeddedFiles(IEnumerable<IExpectedExtractFile> embeddedFiles)
77 {
78 var command = new ExtractEmbeddedFilesCommand(this, embeddedFiles);
79 command.Execute();
80
81 return command.TrackedFiles;
82 }
83
84 public string GenerateIdentifier(string prefix, params string[] args)
85 {
86 return Common.GenerateIdentifier(prefix, args);
87 }
88
89 public string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath)
90 {
91 return Common.GetCanonicalRelativePath(sourceLineNumbers, elementName, attributeName, relativePath, this.Messaging);
92 }
93
94 public int GetValidCodePage(string value, bool allowNoChange = false, bool onlyAnsi = false, SourceLineNumber sourceLineNumbers = null)
95 {
96 return Common.GetValidCodePage(value, allowNoChange, onlyAnsi, sourceLineNumbers);
97 }
98
99 public string GetMsiFileName(string value, bool source, bool longName)
100 {
101 return Common.GetName(value, source, longName);
102 }
103
104 public void ResolveDelayedFields(IEnumerable<IDelayedField> delayedFields, Dictionary<string, string> variableCache)
105 {
106 var command = new ResolveDelayedFieldsCommand(this.Messaging, delayedFields, variableCache);
107 command.Execute();
108 }
109
110 public string[] SplitMsiFileName(string value)
111 {
112 return Common.GetNames(value);
113 }
114
115 public ITrackedFile TrackFile(string path, TrackedFileType type, SourceLineNumber sourceLineNumbers = null)
116 {
117 return new TrackedFile(path, type, sourceLineNumbers);
118 }
119
120 public bool IsValidBinderVariable(string variable)
121 {
122 return Common.IsValidBinderVariable(variable);
123 }
124
125 public bool IsValidFourPartVersion(string version)
126 {
127 return Common.IsValidFourPartVersion(version);
128 }
129
130 public bool IsValidIdentifier(string id)
131 {
132 return Common.IsIdentifier(id);
133 }
134
135 public bool IsValidLongFilename(string filename, bool allowWildcards, bool allowRelative)
136 {
137 return Common.IsValidLongFilename(filename, allowWildcards, allowRelative);
138 }
139
140 public bool IsValidShortFilename(string filename, bool allowWildcards)
141 {
142 return Common.IsValidShortFilename(filename, allowWildcards);
143 }
144
145 private string GetValidatedFullPath(SourceLineNumber sourceLineNumbers, string path)
146 {
147 try
148 {
149 var result = Path.GetFullPath(path);
150
151 var filename = Path.GetFileName(result);
152
153 foreach (var reservedName in ReservedFileNames)
154 {
155 if (reservedName.Equals(filename, StringComparison.OrdinalIgnoreCase))
156 {
157 this.Messaging.Write(ErrorMessages.InvalidFileName(sourceLineNumbers, path));
158 return null;
159 }
160 }
161
162 return result;
163 }
164 catch (ArgumentException)
165 {
166 this.Messaging.Write(ErrorMessages.InvalidFileName(sourceLineNumbers, path));
167 }
168 catch (PathTooLongException)
169 {
170 this.Messaging.Write(ErrorMessages.PathTooLong(sourceLineNumbers, path));
171 }
172
173 return null;
174 }
175 }
176}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.ExtensibilityServices
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 using System.Reflection;
10 using WixToolset.Data;
11 using WixToolset.Extensibility;
12 using WixToolset.Extensibility.Services;
13
14 internal class ExtensionManager : IExtensionManager
15 {
16 private const string UserWixFolderName = ".wix4";
17 private const string MachineWixFolderName = "WixToolset4";
18 private const string ExtensionsFolderName = "extensions";
19
20 private readonly List<IExtensionFactory> extensionFactories = new List<IExtensionFactory>();
21 private readonly Dictionary<Type, List<object>> loadedExtensionsByType = new Dictionary<Type, List<object>>();
22
23 public ExtensionManager(IWixToolsetCoreServiceProvider serviceProvider)
24 {
25 this.ServiceProvider = serviceProvider;
26 }
27
28 private IWixToolsetCoreServiceProvider ServiceProvider { get; }
29
30 public void Add(Assembly extensionAssembly)
31 {
32 var types = extensionAssembly.GetTypes().Where(t => !t.IsAbstract && !t.IsInterface && typeof(IExtensionFactory).IsAssignableFrom(t));
33 var factories = types.Select(this.CreateExtensionFactory).ToList();
34
35 if (!factories.Any())
36 {
37 var path = Path.GetFullPath(new Uri(extensionAssembly.CodeBase).LocalPath);
38 throw new WixException(ErrorMessages.InvalidExtension(path, "The extension does not implement IExtensionFactory. All extensions must have at least one implementation of IExtensionFactory."));
39 }
40
41 this.extensionFactories.AddRange(factories);
42 }
43
44 public void Load(string extensionPath)
45 {
46 var checkPath = extensionPath;
47 var checkedPaths = new List<string> { checkPath };
48 try
49 {
50 if (!TryLoadFromPath(checkPath, out var assembly) && !Path.IsPathRooted(extensionPath))
51 {
52 if (TryParseExtensionReference(extensionPath, out var extensionId, out var extensionVersion))
53 {
54 foreach (var cachePath in this.CacheLocations())
55 {
56 var extensionFolder = Path.Combine(cachePath, extensionId);
57
58 var versionFolder = extensionVersion;
59 if (String.IsNullOrEmpty(versionFolder) && !TryFindLatestVersionInFolder(extensionFolder, out versionFolder))
60 {
61 checkedPaths.Add(extensionFolder);
62 continue;
63 }
64
65 checkPath = Path.Combine(extensionFolder, versionFolder, "tools", extensionId + ".dll");
66 checkedPaths.Add(checkPath);
67
68 if (TryLoadFromPath(checkPath, out assembly))
69 {
70 break;
71 }
72 }
73 }
74 }
75
76 if (assembly == null)
77 {
78 throw new WixException(ErrorMessages.CouldNotFindExtensionInPaths(extensionPath, checkedPaths));
79 }
80
81 this.Add(assembly);
82 }
83 catch (ReflectionTypeLoadException rtle)
84 {
85 throw new WixException(ErrorMessages.InvalidExtension(checkPath, String.Join(Environment.NewLine, rtle.LoaderExceptions.Select(le => le.ToString()))));
86 }
87 catch (WixException)
88 {
89 throw;
90 }
91 catch (Exception e)
92 {
93 throw new WixException(ErrorMessages.InvalidExtension(checkPath, e.Message), e);
94 }
95 }
96
97 public IReadOnlyCollection<T> GetServices<T>() where T : class
98 {
99 if (!this.loadedExtensionsByType.TryGetValue(typeof(T), out var extensions))
100 {
101 extensions = new List<object>();
102
103 foreach (var factory in this.extensionFactories)
104 {
105 if (factory.TryCreateExtension(typeof(T), out var obj) && obj is T extension)
106 {
107 extensions.Add(extension);
108 }
109 }
110
111 this.loadedExtensionsByType.Add(typeof(T), extensions);
112 }
113
114 return extensions.Cast<T>().ToList();
115 }
116
117 private IExtensionFactory CreateExtensionFactory(Type type)
118 {
119 var constructor = type.GetConstructor(new[] { typeof(IWixToolsetCoreServiceProvider) });
120 if (constructor != null)
121 {
122 return (IExtensionFactory)constructor.Invoke(new[] { this.ServiceProvider });
123 }
124
125 return (IExtensionFactory)Activator.CreateInstance(type);
126 }
127
128 private IEnumerable<string> CacheLocations()
129 {
130 var path = Path.Combine(Environment.CurrentDirectory, UserWixFolderName, ExtensionsFolderName);
131 if (Directory.Exists(path))
132 {
133 yield return path;
134 }
135
136 path = Environment.GetEnvironmentVariable("WIX_EXTENSIONS") ?? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
137 path = Path.Combine(path, UserWixFolderName, ExtensionsFolderName);
138 if (Directory.Exists(path))
139 {
140 yield return path;
141 }
142
143 if (Environment.Is64BitOperatingSystem)
144 {
145 path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonProgramFiles), MachineWixFolderName, ExtensionsFolderName);
146 if (Directory.Exists(path))
147 {
148 yield return path;
149 }
150 }
151
152 path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonProgramFilesX86), MachineWixFolderName, ExtensionsFolderName);
153 if (Directory.Exists(path))
154 {
155 yield return path;
156 }
157
158 path = Path.Combine(Path.GetDirectoryName(new Uri(Assembly.GetCallingAssembly().CodeBase).LocalPath), ExtensionsFolderName);
159 if (Directory.Exists(path))
160 {
161 yield return path;
162 }
163 }
164
165 private static bool TryParseExtensionReference(string extensionReference, out string extensionId, out string extensionVersion)
166 {
167 extensionId = extensionReference ?? String.Empty;
168 extensionVersion = String.Empty;
169
170 var index = extensionId.LastIndexOf('/');
171 if (index > 0)
172 {
173 extensionVersion = extensionReference.Substring(index + 1);
174 extensionId = extensionReference.Substring(0, index);
175
176 if (!NuGet.Versioning.NuGetVersion.TryParse(extensionVersion, out _))
177 {
178 return false;
179 }
180
181 if (String.IsNullOrEmpty(extensionId))
182 {
183 return false;
184 }
185 }
186
187 return true;
188 }
189
190 private static bool TryFindLatestVersionInFolder(string basePath, out string foundVersionFolder)
191 {
192 foundVersionFolder = null;
193
194 try
195 {
196 NuGet.Versioning.NuGetVersion version = null;
197 foreach (var versionPath in Directory.GetDirectories(basePath))
198 {
199 var versionFolder = Path.GetFileName(versionPath);
200 if (NuGet.Versioning.NuGetVersion.TryParse(versionFolder, out var checkVersion) &&
201 (version == null || version < checkVersion))
202 {
203 foundVersionFolder = versionFolder;
204 version = checkVersion;
205 }
206 }
207 }
208 catch (IOException)
209 {
210 }
211
212 return !String.IsNullOrEmpty(foundVersionFolder);
213 }
214
215 private static bool TryLoadFromPath(string extensionPath, out Assembly assembly)
216 {
217 try
218 {
219 if (File.Exists(extensionPath))
220 {
221 assembly = Assembly.LoadFrom(extensionPath);
222 return true;
223 }
224 }
225 catch (IOException e) when (e is FileLoadException || e is FileNotFoundException)
226 {
227 }
228
229 assembly = null;
230 return false;
231 }
232 }
233}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.ExtensibilityServices
4{
5 using System;
6 using System.Collections.Generic;
7 using WixToolset.Data;
8 using WixToolset.Data.Symbols;
9 using WixToolset.Data.WindowsInstaller;
10 using WixToolset.Data.WindowsInstaller.Rows;
11 using WixToolset.Extensibility.Data;
12
13 internal class FileFacade : IFileFacade
14 {
15 public FileFacade(FileSymbol file, AssemblySymbol assembly)
16 {
17 this.FileSymbol = file;
18 this.AssemblySymbol = assembly;
19
20 this.Identifier = file.Id;
21 this.ComponentRef = file.ComponentRef;
22 }
23
24 public FileFacade(bool fromModule, FileSymbol file)
25 {
26 this.FromModule = fromModule;
27 this.FileSymbol = file;
28
29 this.Identifier = file.Id;
30 this.ComponentRef = file.ComponentRef;
31 }
32
33 public FileFacade(FileRow row)
34 {
35 this.FromTransform = true;
36 this.FileRow = row;
37
38 this.Identifier = new Identifier(AccessModifier.Section, row.File);
39 this.ComponentRef = row.Component;
40 }
41
42 public bool FromModule { get; }
43
44 public bool FromTransform { get; }
45
46 private FileRow FileRow { get; }
47
48 private FileSymbol FileSymbol { get; }
49
50 private AssemblySymbol AssemblySymbol { get; }
51
52 public string Id => this.Identifier.Id;
53
54 public Identifier Identifier { get; }
55
56 public string ComponentRef { get; }
57
58 public int DiskId
59 {
60 get => this.FileRow == null ? this.FileSymbol.DiskId ?? 1 : this.FileRow.DiskId;
61 set
62 {
63 if (this.FileRow == null)
64 {
65 this.FileSymbol.DiskId = value;
66 }
67 else
68 {
69 this.FileRow.DiskId = value;
70 }
71 }
72 }
73
74 public string FileName => this.FileRow == null ? this.FileSymbol.Name : this.FileRow.FileName;
75
76 public int FileSize
77 {
78 get => this.FileRow == null ? this.FileSymbol.FileSize : this.FileRow.FileSize;
79 set
80 {
81 if (this.FileRow == null)
82 {
83 this.FileSymbol.FileSize = value;
84 }
85 else
86 {
87 this.FileRow.FileSize = value;
88 }
89 }
90 }
91
92 public string Language
93 {
94 get => this.FileRow == null ? this.FileSymbol.Language : this.FileRow.Language;
95 set
96 {
97 if (this.FileRow == null)
98 {
99 this.FileSymbol.Language = value;
100 }
101 else
102 {
103 this.FileRow.Language = value;
104 }
105 }
106 }
107
108 public int? PatchGroup => this.FileRow == null ? this.FileSymbol.PatchGroup : null;
109
110 public int Sequence
111 {
112 get => this.FileRow == null ? this.FileSymbol.Sequence : this.FileRow.Sequence;
113 set
114 {
115 if (this.FileRow == null)
116 {
117 this.FileSymbol.Sequence = value;
118 }
119 else
120 {
121 this.FileRow.Sequence = value;
122 }
123 }
124 }
125
126 public SourceLineNumber SourceLineNumber => this.FileRow == null ? this.FileSymbol.SourceLineNumbers : this.FileRow.SourceLineNumbers;
127
128 public string SourcePath => this.FileRow == null ? this.FileSymbol.Source?.Path : this.FileRow.Source;
129
130 public bool Compressed => this.FileRow == null ? (this.FileSymbol.Attributes & FileSymbolAttributes.Compressed) == FileSymbolAttributes.Compressed : (this.FileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesCompressed) == WindowsInstallerConstants.MsidbFileAttributesCompressed;
131
132 public bool Uncompressed => this.FileRow == null ? (this.FileSymbol.Attributes & FileSymbolAttributes.Uncompressed) == FileSymbolAttributes.Uncompressed : (this.FileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesNoncompressed) == WindowsInstallerConstants.MsidbFileAttributesNoncompressed;
133
134 public string Version
135 {
136 get => this.FileRow == null ? this.FileSymbol.Version : this.FileRow.Version;
137 set
138 {
139 if (this.FileRow == null)
140 {
141 this.FileSymbol.Version = value;
142 }
143 else
144 {
145 this.FileRow.Version = value;
146 }
147 }
148 }
149
150 public AssemblyType? AssemblyType => this.FileRow == null ? this.AssemblySymbol?.Type : null;
151
152 public string AssemblyApplicationFileRef => this.FileRow == null ? this.AssemblySymbol?.ApplicationFileRef : throw new NotImplementedException();
153
154 public string AssemblyManifestFileRef => this.FileRow == null ? this.AssemblySymbol?.ManifestFileRef : throw new NotImplementedException();
155
156 /// <summary>
157 /// Gets the set of MsiAssemblyName rows created for this file.
158 /// </summary>
159 /// <value>RowCollection of MsiAssemblyName table.</value>
160 public List<MsiAssemblyNameSymbol> AssemblyNames { get; set; }
161
162 /// <summary>
163 /// Gets or sets the MsiFileHash row for this file.
164 /// </summary>
165 public MsiFileHashSymbol Hash { get; set; }
166
167 /// <summary>
168 /// Allows direct access to the underlying FileRow as requried for patching.
169 /// </summary>
170 public FileRow GetFileRow() => this.FileRow ?? throw new NotImplementedException();
171 }
172}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.ExtensibilityServices
4{
5 using WixToolset.Data;
6 using WixToolset.Extensibility.Data;
7
8 internal class FileTransfer : IFileTransfer
9 {
10 public string Source { get; set; }
11
12 public string Destination { get; set; }
13
14 public bool Move { get; set; }
15
16 public SourceLineNumber SourceLineNumbers { get; set; }
17
18 public bool Redundant { get; set; }
19 }
20}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.ExtensibilityServices
4{
5 using System.Collections.Generic;
6 using WixToolset.Data;
7 using WixToolset.Extensibility;
8 using WixToolset.Extensibility.Services;
9
10 internal class Messaging : IMessaging
11 {
12 private IMessageListener listener;
13 private readonly HashSet<int> suppressedWarnings = new HashSet<int>();
14 private readonly HashSet<int> warningsAsErrors = new HashSet<int>();
15
16 public bool EncounteredError { get; private set; }
17
18 public int LastErrorNumber { get; private set; }
19
20 public bool ShowVerboseMessages { get; set; }
21
22 public bool SuppressAllWarnings { get; set; }
23
24 public bool WarningsAsError { get; set; }
25
26 public void ElevateWarningMessage(int warningNumber) => this.warningsAsErrors.Add(warningNumber);
27
28 public void SetListener(IMessageListener listener) => this.listener = listener;
29
30 public void SuppressWarningMessage(int warningNumber) => this.suppressedWarnings.Add(warningNumber);
31
32 public void Write(Message message)
33 {
34 var level = this.CalculateMessageLevel(message);
35
36 if (level == MessageLevel.Nothing)
37 {
38 return;
39 }
40
41 if (level == MessageLevel.Error)
42 {
43 this.EncounteredError = true;
44 this.LastErrorNumber = message.Id;
45 }
46
47 if (this.listener != null)
48 {
49 this.listener.Write(message);
50 }
51 else if (level == MessageLevel.Error)
52 {
53 throw new WixException(message);
54 }
55 }
56
57 public void Write(string message, bool verbose = false)
58 {
59 if (!verbose || this.ShowVerboseMessages)
60 {
61 this.listener?.Write(message);
62 }
63 }
64
65 /// <summary>
66 /// Determines the level of this message, when taking into account warning-as-error,
67 /// warning level, verbosity level and message suppressed by the caller.
68 /// </summary>
69 /// <param name="message">Event arguments for the message.</param>
70 /// <returns>MessageLevel representing the level of this message.</returns>
71 private MessageLevel CalculateMessageLevel(Message message)
72 {
73 var level = message.Level;
74
75 if (level == MessageLevel.Verbose)
76 {
77 if (!this.ShowVerboseMessages)
78 {
79 level = MessageLevel.Nothing;
80 }
81 }
82 else if (level == MessageLevel.Warning)
83 {
84 if (this.SuppressAllWarnings || this.suppressedWarnings.Contains(message.Id))
85 {
86 level = MessageLevel.Nothing;
87 }
88 else if (this.WarningsAsError || this.warningsAsErrors.Contains(message.Id))
89 {
90 level = MessageLevel.Error;
91 }
92 }
93
94 level = this.listener?.CalculateMessageLevel(this, message, level) ?? level;
95
96 return level;
97 }
98 }
99}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.ExtensibilityServices
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.Globalization;
9 using System.Linq;
10 using System.Xml;
11 using System.Xml.Linq;
12 using WixToolset.Data;
13 using WixToolset.Data.Symbols;
14 using WixToolset.Data.WindowsInstaller;
15 using WixToolset.Extensibility;
16 using WixToolset.Extensibility.Data;
17 using WixToolset.Extensibility.Services;
18
19 internal class ParseHelper : IParseHelper
20 {
21 public ParseHelper(IServiceProvider serviceProvider)
22 {
23 this.ServiceProvider = serviceProvider;
24
25 this.Messaging = serviceProvider.GetService<IMessaging>();
26 }
27
28 private IServiceProvider ServiceProvider { get; }
29
30 private IMessaging Messaging { get; }
31
32 private ISymbolDefinitionCreator Creator { get; set; }
33
34 public bool ContainsProperty(string possibleProperty)
35 {
36 return Common.ContainsProperty(possibleProperty);
37 }
38
39 public void CreateComplexReference(IntermediateSection section, SourceLineNumber sourceLineNumbers, ComplexReferenceParentType parentType, string parentId, string parentLanguage, ComplexReferenceChildType childType, string childId, bool isPrimary)
40 {
41
42 section.AddSymbol(new WixComplexReferenceSymbol(sourceLineNumbers)
43 {
44 Parent = parentId,
45 ParentType = parentType,
46 ParentLanguage = parentLanguage,
47 Child = childId,
48 ChildType = childType,
49 IsPrimary = isPrimary
50 });
51
52 this.CreateWixGroupSymbol(section, sourceLineNumbers, parentType, parentId, childType, childId);
53 }
54
55 public Identifier CreateDirectorySymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, Identifier id, string parentId, string name, string shortName = null, string sourceName = null, string shortSourceName = null)
56 {
57 if (null == id)
58 {
59 id = this.CreateIdentifier("d", parentId, name, shortName, sourceName, shortSourceName);
60 }
61
62 var symbol = section.AddSymbol(new DirectorySymbol(sourceLineNumbers, id)
63 {
64 ParentDirectoryRef = parentId,
65 Name = name,
66 ShortName = shortName,
67 SourceName = sourceName,
68 SourceShortName = shortSourceName
69 });
70
71 return symbol.Id;
72 }
73
74 public string CreateDirectoryReferenceFromInlineSyntax(IntermediateSection section, SourceLineNumber sourceLineNumbers, XAttribute attribute, string parentId, string inlineSyntax, IDictionary<string, string> sectionCachedInlinedDirectoryIds)
75 {
76 if (String.IsNullOrEmpty(parentId))
77 {
78 throw new ArgumentNullException(nameof(parentId));
79 }
80
81 if (String.IsNullOrEmpty(inlineSyntax))
82 {
83 inlineSyntax = this.GetAttributeLongFilename(sourceLineNumbers, attribute, false, true);
84 }
85
86 if (String.IsNullOrEmpty(inlineSyntax))
87 {
88 return parentId;
89 }
90
91 inlineSyntax = inlineSyntax.Trim('\\', '/');
92
93 var cacheKey = String.Concat(parentId, ":", inlineSyntax);
94
95 if (!sectionCachedInlinedDirectoryIds.TryGetValue(cacheKey, out var id))
96 {
97 var identifier = this.CreateDirectorySymbol(section, sourceLineNumbers, id: null, parentId, inlineSyntax);
98
99 id = identifier.Id;
100 }
101 else
102 {
103 this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.Directory, id);
104 }
105
106 return id;
107 }
108
109 public string CreateGuid(Guid namespaceGuid, string value)
110 {
111 return Uuid.NewUuid(namespaceGuid, value).ToString("B").ToUpperInvariant();
112 }
113
114 public Identifier CreateIdentifier(string prefix, params string[] args)
115 {
116 var id = Common.GenerateIdentifier(prefix, args);
117 return new Identifier(AccessModifier.Section, id);
118 }
119
120 public Identifier CreateIdentifierFromFilename(string filename)
121 {
122 var id = Common.GetIdentifierFromName(filename);
123 return new Identifier(AccessModifier.Section, id);
124 }
125
126 public string CreateIdentifierValueFromPlatform(string name, Platform currentPlatform, BurnPlatforms supportedPlatforms)
127 {
128 string suffix = null;
129
130 switch (currentPlatform)
131 {
132 case Platform.X86:
133 if ((supportedPlatforms & BurnPlatforms.X86) == BurnPlatforms.X86)
134 {
135 suffix = "_X86";
136 }
137 break;
138 case Platform.X64:
139 if ((supportedPlatforms & BurnPlatforms.X64) == BurnPlatforms.X64)
140 {
141 suffix = "_X64";
142 }
143 break;
144 case Platform.ARM64:
145 if ((supportedPlatforms & BurnPlatforms.ARM64) == BurnPlatforms.ARM64)
146 {
147 suffix = "_A64";
148 }
149 break;
150 }
151
152 return suffix == null ? null : name + suffix;
153 }
154
155 public Identifier CreateRegistrySymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, RegistryRootType root, string key, string name, string value, string componentId, bool escapeLeadingHash)
156 {
157 if (RegistryRootType.Unknown == root)
158 {
159 throw new ArgumentOutOfRangeException(nameof(root));
160 }
161
162 if (null == key)
163 {
164 throw new ArgumentNullException(nameof(key));
165 }
166
167 if (null == componentId)
168 {
169 throw new ArgumentNullException(nameof(componentId));
170 }
171
172 // Escape the leading '#' character for string registry values.
173 if (escapeLeadingHash && null != value && value.StartsWith("#", StringComparison.Ordinal))
174 {
175 value = String.Concat("#", value);
176 }
177
178 var id = this.CreateIdentifier("reg", componentId, ((int)root).ToString(CultureInfo.InvariantCulture.NumberFormat), key.ToLowerInvariant(), (null != name ? name.ToLowerInvariant() : name));
179
180 var symbol = section.AddSymbol(new RegistrySymbol(sourceLineNumbers, id)
181 {
182 Root = root,
183 Key = key,
184 Name = name,
185 Value = value,
186 ComponentRef = componentId,
187 });
188
189 return symbol.Id;
190 }
191
192 public void CreateSimpleReference(IntermediateSection section, SourceLineNumber sourceLineNumbers, string symbolName, string primaryKey)
193 {
194 section.AddSymbol(new WixSimpleReferenceSymbol(sourceLineNumbers)
195 {
196 Table = symbolName,
197 PrimaryKeys = primaryKey
198 });
199 }
200
201 public void CreateSimpleReference(IntermediateSection section, SourceLineNumber sourceLineNumbers, string symbolName, params string[] primaryKeys)
202 {
203 section.AddSymbol(new WixSimpleReferenceSymbol(sourceLineNumbers)
204 {
205 Table = symbolName,
206 PrimaryKeys = String.Join("/", primaryKeys)
207 });
208 }
209
210 public void CreateSimpleReference(IntermediateSection section, SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, string primaryKey)
211 {
212 this.CreateSimpleReference(section, sourceLineNumbers, symbolDefinition.Name, primaryKey);
213 }
214
215 public void CreateSimpleReference(IntermediateSection section, SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, params string[] primaryKeys)
216 {
217 this.CreateSimpleReference(section, sourceLineNumbers, symbolDefinition.Name, primaryKeys);
218 }
219
220 public void CreateWixGroupSymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType childType, string childId)
221 {
222 if (null == parentId || ComplexReferenceParentType.Unknown == parentType)
223 {
224 return;
225 }
226
227 if (null == childId)
228 {
229 throw new ArgumentNullException(nameof(childId));
230 }
231
232 section.AddSymbol(new WixGroupSymbol(sourceLineNumbers)
233 {
234 ParentId = parentId,
235 ParentType = parentType,
236 ChildId = childId,
237 ChildType = childType,
238 });
239 }
240
241 public void CreateWixSearchSymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, string elementName, Identifier id, string variable, string condition, string after, string bundleExtensionId)
242 {
243 // TODO: verify variable is not a standard bundle variable
244 if (variable == null)
245 {
246 this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, elementName, "Variable"));
247 }
248
249 section.AddSymbol(new WixSearchSymbol(sourceLineNumbers, id)
250 {
251 Variable = variable,
252 Condition = condition,
253 BundleExtensionRef = bundleExtensionId,
254 });
255
256 if (after != null)
257 {
258 this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.WixSearch, after);
259 // TODO: We're currently defaulting to "always run after", which we will need to change...
260 this.CreateWixSearchRelationSymbol(section, sourceLineNumbers, id, after, 2);
261 }
262
263 if (!String.IsNullOrEmpty(bundleExtensionId))
264 {
265 this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.WixBundleExtension, bundleExtensionId);
266 }
267 }
268
269 public void CreateWixSearchRelationSymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, Identifier id, string parentId, int attributes)
270 {
271 section.AddSymbol(new WixSearchRelationSymbol(sourceLineNumbers, id)
272 {
273 ParentSearchRef = parentId,
274 Attributes = attributes,
275 });
276 }
277
278 public IntermediateSymbol CreateSymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, string symbolName, Identifier identifier = null)
279 {
280 if (this.Creator == null)
281 {
282 this.CreateSymbolDefinitionCreator();
283 }
284
285 if (!this.Creator.TryGetSymbolDefinitionByName(symbolName, out var symbolDefinition))
286 {
287 throw new ArgumentException(nameof(symbolName));
288 }
289
290 return this.CreateSymbol(section, sourceLineNumbers, symbolDefinition, identifier);
291 }
292
293 public IntermediateSymbol CreateSymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, IntermediateSymbolDefinition symbolDefinition, Identifier identifier = null)
294 {
295 return section.AddSymbol(symbolDefinition.CreateSymbol(sourceLineNumbers, identifier));
296 }
297
298 public void EnsureTable(IntermediateSection section, SourceLineNumber sourceLineNumbers, TableDefinition tableDefinition)
299 {
300 section.AddSymbol(new WixEnsureTableSymbol(sourceLineNumbers)
301 {
302 Table = tableDefinition.Name,
303 });
304
305 // TODO: Check if the given table definition is a custom table. For now we have to assume that it isn't.
306 //this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.WixCustomTable, tableDefinition.Name);
307 }
308
309 public void EnsureTable(IntermediateSection section, SourceLineNumber sourceLineNumbers, string tableName)
310 {
311 section.AddSymbol(new WixEnsureTableSymbol(sourceLineNumbers)
312 {
313 Table = tableName,
314 });
315
316 if (this.Creator == null)
317 {
318 this.CreateSymbolDefinitionCreator();
319 }
320
321 // TODO: The tableName may not be the same as the symbolName. For now, we have to assume that it is.
322 // We don't add custom table definitions to the tableDefinitions collection,
323 // so if it's not in there, it better be a custom table. If the Id is just wrong,
324 // instead of a custom table, we get an unresolved reference at link time.
325 if (!this.Creator.TryGetSymbolDefinitionByName(tableName, out var _))
326 {
327 this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.WixCustomTable, tableName);
328 }
329 }
330
331 public string GetAttributeGuidValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool generatable = false, bool canBeEmpty = false)
332 {
333 if (null == attribute)
334 {
335 throw new ArgumentNullException(nameof(attribute));
336 }
337
338 var emptyRule = canBeEmpty ? EmptyRule.CanBeEmpty : EmptyRule.CanBeWhitespaceOnly;
339 var value = this.GetAttributeValue(sourceLineNumbers, attribute, emptyRule);
340
341 if (String.IsNullOrEmpty(value))
342 {
343 if (canBeEmpty)
344 {
345 return String.Empty;
346 }
347 }
348 else
349 {
350 if (generatable && value == "*")
351 {
352 return value;
353 }
354
355 if (Guid.TryParse(value, out var guid))
356 {
357 return guid.ToString("B").ToUpperInvariant();
358 }
359
360 if (value.StartsWith("!(loc", StringComparison.Ordinal) || value.StartsWith("$(loc", StringComparison.Ordinal) || value.StartsWith("!(wix", StringComparison.Ordinal))
361 {
362 return value;
363 }
364
365 if (value.StartsWith("PUT-GUID-", StringComparison.OrdinalIgnoreCase) ||
366 value.StartsWith("{PUT-GUID-", StringComparison.OrdinalIgnoreCase))
367 {
368 this.Messaging.Write(ErrorMessages.ExampleGuid(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
369 }
370 else
371 {
372 this.Messaging.Write(ErrorMessages.IllegalGuidValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
373 }
374 }
375
376 return CompilerConstants.IllegalGuid;
377 }
378
379 public Identifier GetAttributeIdentifier(SourceLineNumber sourceLineNumbers, XAttribute attribute)
380 {
381 var access = AccessModifier.Global;
382 var value = Common.GetAttributeValue(this.Messaging, sourceLineNumbers, attribute, EmptyRule.CanBeEmpty);
383
384 var separator = value.IndexOf(' ');
385 if (separator > 0)
386 {
387 var prefix = value.Substring(0, separator);
388 switch (prefix)
389 {
390 case "global":
391 case "public":
392 case "package":
393 access = AccessModifier.Global;
394 break;
395
396 case "internal":
397 case "library":
398 access = AccessModifier.Library;
399 break;
400
401 case "file":
402 case "protected":
403 access = AccessModifier.File;
404 break;
405
406 case "private":
407 case "fragment":
408 case "section":
409 access = AccessModifier.Section;
410 break;
411
412 default:
413 return null;
414 }
415
416 value = value.Substring(separator + 1).Trim();
417 }
418
419 if (!Common.IsIdentifier(value))
420 {
421 this.Messaging.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
422 return null;
423 }
424 else if (72 < value.Length)
425 {
426 this.Messaging.Write(WarningMessages.IdentifierTooLong(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
427 }
428
429 return new Identifier(access, value);
430 }
431
432 public string GetAttributeIdentifierValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
433 {
434 return Common.GetAttributeIdentifierValue(this.Messaging, sourceLineNumbers, attribute);
435 }
436
437 public int GetAttributeIntegerValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, int minimum, int maximum)
438 {
439 return Common.GetAttributeIntegerValue(this.Messaging, sourceLineNumbers, attribute, minimum, maximum);
440 }
441
442 public string GetAttributeLongFilename(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowWildcards, bool allowRelative)
443 {
444 if (null == attribute)
445 {
446 throw new ArgumentNullException("attribute");
447 }
448
449 var value = this.GetAttributeValue(sourceLineNumbers, attribute);
450
451 if (!String.IsNullOrEmpty(value))
452 {
453 if (!this.IsValidLongFilename(value, allowWildcards, allowRelative) && !this.IsValidLocIdentifier(value))
454 {
455 if (allowRelative)
456 {
457 this.Messaging.Write(ErrorMessages.IllegalRelativeLongFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
458 }
459 else
460 {
461 this.Messaging.Write(ErrorMessages.IllegalLongFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
462 }
463 }
464 else if (allowRelative)
465 {
466 value = this.GetCanonicalRelativePath(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value);
467 }
468 else if (CompilerCore.IsAmbiguousFilename(value))
469 {
470 this.Messaging.Write(WarningMessages.AmbiguousFileOrDirectoryName(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
471 }
472 }
473
474 return value;
475 }
476
477 public long GetAttributeLongValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, long minimum, long maximum)
478 {
479 Debug.Assert(minimum > CompilerConstants.LongNotSet && minimum > CompilerConstants.IllegalLong, "The legal values for this attribute collide with at least one sentinel used during parsing.");
480
481 var value = this.GetAttributeValue(sourceLineNumbers, attribute);
482
483 if (0 < value.Length)
484 {
485 try
486 {
487 var longValue = Convert.ToInt64(value, CultureInfo.InvariantCulture.NumberFormat);
488
489 if (CompilerConstants.LongNotSet == longValue || CompilerConstants.IllegalLong == longValue)
490 {
491 this.Messaging.Write(ErrorMessages.IntegralValueSentinelCollision(sourceLineNumbers, longValue));
492 }
493 else if (minimum > longValue || maximum < longValue)
494 {
495 this.Messaging.Write(ErrorMessages.IntegralValueOutOfRange(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, longValue, minimum, maximum));
496 longValue = CompilerConstants.IllegalLong;
497 }
498
499 return longValue;
500 }
501 catch (FormatException)
502 {
503 this.Messaging.Write(ErrorMessages.IllegalLongValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
504 }
505 catch (OverflowException)
506 {
507 this.Messaging.Write(ErrorMessages.IllegalLongValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
508 }
509 }
510
511 return CompilerConstants.IllegalLong;
512 }
513
514 public string GetAttributeValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, EmptyRule emptyRule = EmptyRule.CanBeWhitespaceOnly)
515 {
516 return Common.GetAttributeValue(this.Messaging, sourceLineNumbers, attribute, emptyRule);
517 }
518
519 public RegistryRootType? GetAttributeRegistryRootValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowHkmu)
520 {
521 var value = this.GetAttributeValue(sourceLineNumbers, attribute);
522 if (String.IsNullOrEmpty(value))
523 {
524 return null;
525 }
526
527 switch (value)
528 {
529 case "HKCR":
530 return RegistryRootType.ClassesRoot;
531
532 case "HKCU":
533 return RegistryRootType.CurrentUser;
534
535 case "HKLM":
536 return RegistryRootType.LocalMachine;
537
538 case "HKU":
539 return RegistryRootType.Users;
540
541 case "HKMU":
542 if (allowHkmu)
543 {
544 return RegistryRootType.MachineUser;
545 }
546 break;
547 }
548
549 if (allowHkmu)
550 {
551 this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, "HKMU", "HKCR", "HKCU", "HKLM", "HKU"));
552 }
553 else
554 {
555 this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, "HKCR", "HKCU", "HKLM", "HKU"));
556 }
557
558 return RegistryRootType.Unknown;
559 }
560
561 public string GetAttributeVersionValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
562 {
563 var value = this.GetAttributeValue(sourceLineNumbers, attribute);
564
565 if (!String.IsNullOrEmpty(value))
566 {
567 if (Version.TryParse(value, out var version))
568 {
569 return version.ToString();
570 }
571
572 // Allow versions to contain binder variables.
573 if (Common.ContainsValidBinderVariable(value))
574 {
575 return value;
576 }
577
578 this.Messaging.Write(ErrorMessages.IllegalVersionValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
579 }
580
581 return null;
582 }
583
584 public YesNoDefaultType GetAttributeYesNoDefaultValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
585 {
586 var value = this.GetAttributeValue(sourceLineNumbers, attribute);
587
588 switch (value)
589 {
590 case "yes":
591 case "true":
592 return YesNoDefaultType.Yes;
593
594 case "no":
595 case "false":
596 return YesNoDefaultType.No;
597
598 case "default":
599 return YesNoDefaultType.Default;
600
601 default:
602 this.Messaging.Write(ErrorMessages.IllegalYesNoDefaultValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
603 return YesNoDefaultType.IllegalValue;
604 }
605 }
606
607 public YesNoType GetAttributeYesNoValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
608 {
609 var value = this.GetAttributeValue(sourceLineNumbers, attribute);
610
611 switch (value)
612 {
613 case "yes":
614 case "true":
615 return YesNoType.Yes;
616
617 case "no":
618 case "false":
619 return YesNoType.No;
620
621 default:
622 this.Messaging.Write(ErrorMessages.IllegalYesNoValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
623 return YesNoType.IllegalValue;
624 }
625 }
626
627 public string GetCanonicalRelativePath(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string relativePath)
628 {
629 return Common.GetCanonicalRelativePath(sourceLineNumbers, elementName, attributeName, relativePath, this.Messaging);
630 }
631
632 public SourceLineNumber GetSourceLineNumbers(XElement element)
633 {
634 return Preprocessor.GetSourceLineNumbers(element);
635 }
636
637 public string GetConditionInnerText(XElement element)
638 {
639 var value = Common.GetInnerText(element)?.Trim().Replace('\t', ' ').Replace('\r', ' ').Replace('\n', ' ');
640
641 // Return null for a non-existant condition.
642 return String.IsNullOrEmpty(value) ? null : value;
643 }
644
645 public string GetTrimmedInnerText(XElement element)
646 {
647 var value = Common.GetInnerText(element);
648 return value?.Trim();
649 }
650
651 public void InnerTextDisallowed(XElement element)
652 {
653 if (element.Nodes().Any(n => XmlNodeType.Text == n.NodeType || XmlNodeType.CDATA == n.NodeType))
654 {
655 var innerText = Common.GetInnerText(element);
656 if (!String.IsNullOrWhiteSpace(innerText))
657 {
658 var sourceLineNumbers = this.GetSourceLineNumbers(element);
659 this.Messaging.Write(ErrorMessages.IllegalInnerText(sourceLineNumbers, element.Name.LocalName, innerText));
660 }
661 }
662 }
663
664 public bool IsValidIdentifier(string value)
665 {
666 return Common.IsIdentifier(value);
667 }
668
669 public bool IsValidLocIdentifier(string identifier)
670 {
671 return Common.TryParseWixVariable(identifier, 0, out var parsed) && parsed.Index == 0 && parsed.Length == identifier.Length && parsed.Namespace == "loc";
672 }
673
674 public bool IsValidLongFilename(string filename, bool allowWildcards, bool allowRelative)
675 {
676 return Common.IsValidLongFilename(filename, allowWildcards, allowRelative);
677 }
678
679 public bool IsValidShortFilename(string filename, bool allowWildcards)
680 {
681 return Common.IsValidShortFilename(filename, allowWildcards);
682 }
683
684 public void ParseExtensionAttribute(IEnumerable<ICompilerExtension> extensions, Intermediate intermediate, IntermediateSection section, XElement element, XAttribute attribute, IDictionary<string, string> context = null)
685 {
686 // Ignore attributes defined by the W3C because we'll assume they are always right.
687 if ((String.IsNullOrEmpty(attribute.Name.NamespaceName) && attribute.Name.LocalName.Equals("xmlns", StringComparison.Ordinal)) ||
688 attribute.Name.NamespaceName.StartsWith(CompilerCore.W3SchemaPrefix.NamespaceName, StringComparison.Ordinal))
689 {
690 return;
691 }
692
693 if (ParseHelper.TryFindExtension(extensions, attribute.Name.NamespaceName, out var extension))
694 {
695 extension.ParseAttribute(intermediate, section, element, attribute, context);
696 }
697 else
698 {
699 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(element);
700 this.Messaging.Write(ErrorMessages.UnhandledExtensionAttribute(sourceLineNumbers, element.Name.LocalName, attribute.Name.LocalName, attribute.Name.NamespaceName));
701 }
702 }
703
704 public void ParseExtensionElement(IEnumerable<ICompilerExtension> extensions, Intermediate intermediate, IntermediateSection section, XElement parentElement, XElement element, IDictionary<string, string> context = null)
705 {
706 if (ParseHelper.TryFindExtension(extensions, element.Name.Namespace, out var extension))
707 {
708 extension.ParseElement(intermediate, section, parentElement, element, context);
709 }
710 else
711 {
712 var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(element);
713 this.Messaging.Write(ErrorMessages.UnhandledExtensionElement(childSourceLineNumbers, parentElement.Name.LocalName, element.Name.LocalName, element.Name.NamespaceName));
714 }
715 }
716
717 public IComponentKeyPath ParsePossibleKeyPathExtensionElement(IEnumerable<ICompilerExtension> extensions, Intermediate intermediate, IntermediateSection section, XElement parentElement, XElement element, IDictionary<string, string> context)
718 {
719 IComponentKeyPath keyPath = null;
720
721 if (ParseHelper.TryFindExtension(extensions, element.Name.Namespace, out var extension))
722 {
723 keyPath = extension.ParsePossibleKeyPathElement(intermediate, section, parentElement, element, context);
724 }
725 else
726 {
727 var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(element);
728 this.Messaging.Write(ErrorMessages.UnhandledExtensionElement(childSourceLineNumbers, parentElement.Name.LocalName, element.Name.LocalName, element.Name.NamespaceName));
729 }
730
731 return keyPath;
732 }
733
734 public void ParseForExtensionElements(IEnumerable<ICompilerExtension> extensions, Intermediate intermediate, IntermediateSection section, XElement element)
735 {
736 var checkInnerText = false;
737
738 foreach (var child in element.Nodes())
739 {
740 if (child is XElement childElement)
741 {
742 if (element.Name.Namespace == childElement.Name.Namespace)
743 {
744 this.UnexpectedElement(element, childElement);
745 }
746 else
747 {
748 this.ParseExtensionElement(extensions, intermediate, section, element, childElement);
749 }
750 }
751 else
752 {
753 checkInnerText = true;
754 }
755 }
756
757 if (checkInnerText)
758 {
759 this.InnerTextDisallowed(element);
760 }
761 }
762
763 public WixActionSymbol ScheduleActionSymbol(IntermediateSection section, SourceLineNumber sourceLineNumbers, AccessModifier access, SequenceTable sequence, string actionName, string condition, string beforeAction, string afterAction, bool overridable = false)
764 {
765 var actionId = new Identifier(access, sequence, actionName);
766
767 var actionSymbol = section.AddSymbol(new WixActionSymbol(sourceLineNumbers, actionId)
768 {
769 SequenceTable = sequence,
770 Action = actionName,
771 Condition = condition,
772 Before = beforeAction,
773 After = afterAction,
774 Overridable = overridable,
775 });
776
777 if (null != beforeAction)
778 {
779 if (WindowsInstallerStandard.IsStandardAction(beforeAction))
780 {
781 this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.WixAction, sequence.ToString(), beforeAction);
782 }
783 else
784 {
785 this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.CustomAction, beforeAction);
786 }
787 }
788
789 if (null != afterAction)
790 {
791 if (WindowsInstallerStandard.IsStandardAction(afterAction))
792 {
793 this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.WixAction, sequence.ToString(), afterAction);
794 }
795 else
796 {
797 this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.CustomAction, afterAction);
798 }
799 }
800
801 return actionSymbol;
802 }
803
804 public void CreateCustomActionReference(SourceLineNumber sourceLineNumbers, IntermediateSection section, string customAction, Platform currentPlatform, CustomActionPlatforms supportedPlatforms)
805 {
806 if (!this.Messaging.EncounteredError)
807 {
808 var suffix = "_X86";
809
810 switch (currentPlatform)
811 {
812 case Platform.X64:
813 if ((supportedPlatforms & CustomActionPlatforms.X64) == CustomActionPlatforms.X64)
814 {
815 suffix = "_X64";
816 }
817 break;
818 case Platform.ARM64:
819 if ((supportedPlatforms & CustomActionPlatforms.ARM64) == CustomActionPlatforms.ARM64)
820 {
821 suffix = "_A64";
822 }
823 break;
824 }
825
826 this.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.CustomAction, customAction + suffix);
827 }
828 }
829
830 public void UnexpectedAttribute(XElement element, XAttribute attribute)
831 {
832 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(element);
833 Common.UnexpectedAttribute(this.Messaging, sourceLineNumbers, attribute);
834 }
835
836 public void UnexpectedElement(XElement parentElement, XElement childElement)
837 {
838 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(childElement);
839 this.Messaging.Write(ErrorMessages.UnexpectedElement(sourceLineNumbers, parentElement.Name.LocalName, childElement.Name.LocalName));
840 }
841
842 private void CreateSymbolDefinitionCreator()
843 {
844 this.Creator = this.ServiceProvider.GetService<ISymbolDefinitionCreator>();
845 }
846
847 private static bool TryFindExtension(IEnumerable<ICompilerExtension> extensions, XNamespace ns, out ICompilerExtension extension)
848 {
849 extension = null;
850
851 foreach (var ext in extensions)
852 {
853 if (ext.Namespace == ns)
854 {
855 extension = ext;
856 break;
857 }
858 }
859
860 return extension != null;
861 }
862 }
863}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.ExtensibilityServices
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using WixToolset.Data;
9 using WixToolset.Data.WindowsInstaller;
10 using WixToolset.Extensibility.Data;
11 using WixToolset.Extensibility.Services;
12
13 internal class PathResolver : IPathResolver
14 {
15 public string GetCanonicalDirectoryPath(Dictionary<string, IResolvedDirectory> directories, Dictionary<string, string> componentIdGenSeeds, string directory, Platform platform)
16 {
17 if (!directories.TryGetValue(directory, out var resolvedDirectory))
18 {
19 throw new WixException(ErrorMessages.ExpectedDirectory(directory));
20 }
21
22 if (null == resolvedDirectory.Path)
23 {
24 if (null != componentIdGenSeeds && componentIdGenSeeds.ContainsKey(directory))
25 {
26 resolvedDirectory.Path = componentIdGenSeeds[directory];
27 }
28 else if (WindowsInstallerStandard.IsStandardDirectory(directory))
29 {
30 resolvedDirectory.Path = WindowsInstallerStandard.GetPlatformSpecificDirectoryId(directory, platform);
31 }
32 else
33 {
34 var name = resolvedDirectory.Name?.ToLowerInvariant();
35
36 if (String.IsNullOrEmpty(resolvedDirectory.DirectoryParent))
37 {
38 resolvedDirectory.Path = name;
39 }
40 else
41 {
42 var parentPath = this.GetCanonicalDirectoryPath(directories, componentIdGenSeeds, resolvedDirectory.DirectoryParent, platform);
43
44 if (null != resolvedDirectory.Name)
45 {
46 resolvedDirectory.Path = Path.Combine(parentPath, name);
47 }
48 else
49 {
50 resolvedDirectory.Path = parentPath;
51 }
52 }
53 }
54 }
55
56 return resolvedDirectory.Path;
57 }
58
59 public string GetDirectoryPath(Dictionary<string, IResolvedDirectory> directories, string directory)
60 {
61 if (!directories.TryGetValue(directory, out var resolvedDirectory))
62 {
63 throw new WixException(ErrorMessages.ExpectedDirectory(directory));
64 }
65
66 if (null == resolvedDirectory.Path)
67 {
68 var name = resolvedDirectory.Name;
69
70 if (String.IsNullOrEmpty(resolvedDirectory.DirectoryParent))
71 {
72 resolvedDirectory.Path = name;
73 }
74 else
75 {
76 var parentPath = this.GetDirectoryPath(directories, resolvedDirectory.DirectoryParent);
77
78 if (null != resolvedDirectory.Name)
79 {
80 resolvedDirectory.Path = Path.Combine(parentPath, name);
81 }
82 else
83 {
84 resolvedDirectory.Path = parentPath;
85 }
86 }
87 }
88
89 return resolvedDirectory.Path;
90 }
91
92 public string GetFileSourcePath(Dictionary<string, IResolvedDirectory> directories, string directoryId, string fileName, bool compressed, bool useLongName)
93 {
94 var fileSourcePath = Common.GetName(fileName, true, useLongName);
95
96 if (compressed)
97 {
98 // Use just the file name of the file since all uncompressed files must appear
99 // in the root of the image in a compressed package.
100 }
101 else
102 {
103 // Get the relative path of where we want the file to be layed out as specified
104 // in the Directory table.
105 var directoryPath = this.GetDirectoryPath(directories, directoryId);
106 fileSourcePath = Path.Combine(directoryPath, fileSourcePath);
107 }
108
109 // Strip off "SourceDir" if it's still on there.
110 if (fileSourcePath.StartsWith("SourceDir\\", StringComparison.Ordinal))
111 {
112 fileSourcePath = fileSourcePath.Substring(10);
113 }
114
115 return fileSourcePath;
116 }
117 }
118}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.ExtensibilityServices
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Text;
9 using System.Xml.Linq;
10 using WixToolset.Data;
11 using WixToolset.Extensibility;
12 using WixToolset.Extensibility.Data;
13 using WixToolset.Extensibility.Services;
14
15 internal class PreprocessHelper : IPreprocessHelper
16 {
17 private static readonly char[] VariableSplitter = new char[] { '.' };
18 private static readonly char[] ArgumentSplitter = new char[] { ',' };
19
20 public PreprocessHelper(IServiceProvider serviceProvider)
21 {
22 this.ServiceProvider = serviceProvider;
23
24 this.Messaging = this.ServiceProvider.GetService<IMessaging>();
25 }
26
27 private IServiceProvider ServiceProvider { get; }
28
29 private IMessaging Messaging { get; }
30
31 private Dictionary<string, IPreprocessorExtension> ExtensionsByPrefix { get; set; }
32
33 public void AddVariable(IPreprocessContext context, string name, string value)
34 {
35 this.AddVariable(context, name, value, true);
36 }
37
38 public void AddVariable(IPreprocessContext context, string name, string value, bool showWarning)
39 {
40 var currentValue = this.GetVariableValue(context, "var", name);
41
42 if (null == currentValue)
43 {
44 context.Variables.Add(name, value);
45 }
46 else
47 {
48 if (showWarning && value != currentValue)
49 {
50 this.Messaging.Write(WarningMessages.VariableDeclarationCollision(context.CurrentSourceLineNumber, name, value, currentValue));
51 }
52
53 context.Variables[name] = value;
54 }
55 }
56
57 public string EvaluateFunction(IPreprocessContext context, string function)
58 {
59 var prefixParts = function.Split(VariableSplitter, 2);
60
61 // Check to make sure there are 2 parts and neither is an empty string.
62 if (2 != prefixParts.Length || 0 >= prefixParts[0].Length || 0 >= prefixParts[1].Length)
63 {
64 throw new WixException(ErrorMessages.InvalidPreprocessorFunction(context.CurrentSourceLineNumber, function));
65 }
66
67 var prefix = prefixParts[0];
68 var functionParts = prefixParts[1].Split(new char[] { '(' }, 2);
69
70 // Check to make sure there are 2 parts, neither is an empty string, and the second part ends with a closing paren.
71 if (2 != functionParts.Length || 0 >= functionParts[0].Length || 0 >= functionParts[1].Length || !functionParts[1].EndsWith(")", StringComparison.Ordinal))
72 {
73 throw new WixException(ErrorMessages.InvalidPreprocessorFunction(context.CurrentSourceLineNumber, function));
74 }
75
76 var functionName = functionParts[0];
77
78 // Remove the trailing closing paren.
79 var allArgs = functionParts[1].Substring(0, functionParts[1].Length - 1);
80
81 // Parse the arguments and preprocess them.
82 var args = allArgs.Split(ArgumentSplitter);
83 for (var i = 0; i < args.Length; i++)
84 {
85 args[i] = this.PreprocessString(context, args[i].Trim());
86 }
87
88 var result = this.EvaluateFunction(context, prefix, functionName, args);
89
90 // If the function didn't evaluate, try to evaluate the original value as a variable to support
91 // the use of open and closed parens inside variable names. Example: $(env.ProgramFiles(x86)) should resolve.
92 if (result == null)
93 {
94 result = this.GetVariableValue(context, function, true);
95 }
96
97 return result;
98 }
99
100 public string EvaluateFunction(IPreprocessContext context, string prefix, string function, string[] args)
101 {
102 if (String.IsNullOrEmpty(prefix))
103 {
104 throw new ArgumentNullException("prefix");
105 }
106
107 if (String.IsNullOrEmpty(function))
108 {
109 throw new ArgumentNullException("function");
110 }
111
112 switch (prefix)
113 {
114 case "fun":
115 switch (function)
116 {
117 case "AutoVersion":
118 // Make sure the base version is specified
119 if (args.Length == 0 || String.IsNullOrEmpty(args[0]))
120 {
121 throw new WixException(ErrorMessages.InvalidPreprocessorFunctionAutoVersion(context.CurrentSourceLineNumber));
122 }
123
124 // Build = days since 1/1/2000; Revision = seconds since midnight / 2
125 var now = DateTime.UtcNow;
126 var build = now - new DateTime(2000, 1, 1);
127 var revision = now - new DateTime(now.Year, now.Month, now.Day);
128
129 return String.Join(".", args[0], (int)build.TotalDays, (int)(revision.TotalSeconds / 2));
130
131 default:
132 return null;
133 }
134
135 default:
136 var extensionsByPrefix = this.GetExtensionsByPrefix();
137 if (extensionsByPrefix.TryGetValue(prefix, out var extension))
138 {
139 try
140 {
141 return extension.EvaluateFunction(prefix, function, args);
142 }
143 catch (Exception e)
144 {
145 throw new WixException(ErrorMessages.PreprocessorExtensionEvaluateFunctionFailed(context.CurrentSourceLineNumber, prefix, function, String.Join(",", args), e.Message));
146 }
147 }
148 else
149 {
150 return null;
151 }
152 }
153 }
154
155 public string GetVariableValue(IPreprocessContext context, string variable, bool allowMissingPrefix)
156 {
157 // Strip the "$(" off the front and the ")" off the back.
158 if (variable.StartsWith("$(", StringComparison.Ordinal))
159 {
160 variable = variable.Substring(2, variable.Length - 3);
161 }
162
163 var parts = variable.Split(VariableSplitter, 2);
164
165 if (1 == parts.Length) // missing prefix
166 {
167 if (allowMissingPrefix)
168 {
169 return this.GetVariableValue(context, "var", parts[0]);
170 }
171 else
172 {
173 throw new WixException(ErrorMessages.InvalidPreprocessorVariable(context.CurrentSourceLineNumber, variable));
174 }
175 }
176 else
177 {
178 // check for empty variable name
179 if (0 < parts[1].Length)
180 {
181 string result = this.GetVariableValue(context, parts[0], parts[1]);
182
183 // 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
184 if (null == result && allowMissingPrefix && variable.Contains("."))
185 {
186 result = this.GetVariableValue(context, "var", variable);
187 }
188
189 return result;
190 }
191 else
192 {
193 throw new WixException(ErrorMessages.InvalidPreprocessorVariable(context.CurrentSourceLineNumber, variable));
194 }
195 }
196 }
197
198 public string GetVariableValue(IPreprocessContext context, string prefix, string name)
199 {
200 if (String.IsNullOrEmpty(prefix))
201 {
202 throw new ArgumentNullException("prefix");
203 }
204
205 if (String.IsNullOrEmpty(name))
206 {
207 throw new ArgumentNullException("name");
208 }
209
210 switch (prefix)
211 {
212 case "env":
213 return Environment.GetEnvironmentVariable(name);
214
215 case "sys":
216 switch (name)
217 {
218 case "CURRENTDIR":
219 return String.Concat(Directory.GetCurrentDirectory(), Path.DirectorySeparatorChar);
220
221 case "SOURCEFILEDIR":
222 return String.Concat(Path.GetDirectoryName(context.CurrentSourceLineNumber.FileName), Path.DirectorySeparatorChar);
223
224 case "SOURCEFILEPATH":
225 return context.CurrentSourceLineNumber.FileName;
226
227 case "PLATFORM":
228 this.Messaging.Write(WarningMessages.DeprecatedPreProcVariable(context.CurrentSourceLineNumber, "$(sys.PLATFORM)", "$(sys.BUILDARCH)"));
229
230 goto case "BUILDARCH";
231
232 case "BUILDARCH":
233 switch (context.Platform)
234 {
235 case Platform.X86:
236 return "x86";
237
238 case Platform.X64:
239 return "x64";
240
241 case Platform.ARM64:
242 return "arm64";
243
244 default:
245 throw new ArgumentException("Unknown platform enumeration '{0}' encountered.", context.Platform.ToString());
246 }
247
248 case "WIXMAJORVERSION":
249 return ThisAssembly.AssemblyFileVersion.Split('.')[0];
250
251 case "WIXVERSION":
252 return ThisAssembly.AssemblyFileVersion;
253
254 default:
255 return null;
256 }
257
258 case "var":
259 return context.Variables.TryGetValue(name, out var result) ? result : null;
260
261 default:
262 var extensionsByPrefix = this.GetExtensionsByPrefix();
263 if (extensionsByPrefix.TryGetValue(prefix, out var extension))
264 {
265 try
266 {
267 return extension.GetVariableValue(prefix, name);
268 }
269 catch (Exception e)
270 {
271 throw new WixException(ErrorMessages.PreprocessorExtensionGetVariableValueFailed(context.CurrentSourceLineNumber, prefix, name, e.Message));
272 }
273 }
274 else
275 {
276 return null;
277 }
278 }
279 }
280
281 public void PreprocessPragma(IPreprocessContext context, string pragmaName, string args, XContainer parent)
282 {
283 var prefixParts = pragmaName.Split(VariableSplitter, 2);
284
285 // Check to make sure there are 2 parts and neither is an empty string.
286 if (2 != prefixParts.Length)
287 {
288 throw new WixException(ErrorMessages.InvalidPreprocessorPragma(context.CurrentSourceLineNumber, pragmaName));
289 }
290
291 var prefix = prefixParts[0];
292 var pragma = prefixParts[1];
293
294 if (String.IsNullOrEmpty(prefix) || String.IsNullOrEmpty(pragma))
295 {
296 throw new WixException(ErrorMessages.InvalidPreprocessorPragma(context.CurrentSourceLineNumber, pragmaName));
297 }
298
299 switch (prefix)
300 {
301 case "wix":
302 switch (pragma)
303 {
304 // Add any core defined pragmas here
305 default:
306 this.Messaging.Write(WarningMessages.PreprocessorUnknownPragma(context.CurrentSourceLineNumber, pragmaName));
307 break;
308 }
309 break;
310
311 default:
312 var extensionsByPrefix = this.GetExtensionsByPrefix();
313 if (extensionsByPrefix.TryGetValue(prefix, out var extension))
314 {
315 if (!extension.ProcessPragma(prefix, pragma, args, parent))
316 {
317 this.Messaging.Write(WarningMessages.PreprocessorUnknownPragma(context.CurrentSourceLineNumber, pragmaName));
318 }
319 }
320 break;
321 }
322 }
323
324 public string PreprocessString(IPreprocessContext context, string value)
325 {
326 var sb = new StringBuilder();
327 var currentPosition = 0;
328 var end = 0;
329
330 while (-1 != (currentPosition = value.IndexOf('$', end)))
331 {
332 if (end < currentPosition)
333 {
334 sb.Append(value, end, currentPosition - end);
335 }
336
337 end = currentPosition + 1;
338
339 var remainder = value.Substring(end);
340 if (remainder.StartsWith("$", StringComparison.Ordinal))
341 {
342 sb.Append("$");
343 end++;
344 }
345 else if (remainder.StartsWith("(loc.", StringComparison.Ordinal))
346 {
347 currentPosition = remainder.IndexOf(')');
348 if (-1 == currentPosition)
349 {
350 this.Messaging.Write(ErrorMessages.InvalidPreprocessorVariable(context.CurrentSourceLineNumber, remainder));
351 break;
352 }
353
354 sb.Append("$"); // just put the resource reference back as was
355 sb.Append(remainder, 0, currentPosition + 1);
356
357 end += currentPosition + 1;
358 }
359 else if (remainder.StartsWith("(", StringComparison.Ordinal))
360 {
361 var openParenCount = 1;
362 var closingParenCount = 0;
363 var isFunction = false;
364 var foundClosingParen = false;
365
366 // find the closing paren
367 int closingParenPosition;
368 for (closingParenPosition = 1; closingParenPosition < remainder.Length; closingParenPosition++)
369 {
370 switch (remainder[closingParenPosition])
371 {
372 case '(':
373 openParenCount++;
374 isFunction = true;
375 break;
376
377 case ')':
378 closingParenCount++;
379 break;
380 }
381
382 if (openParenCount == closingParenCount)
383 {
384 foundClosingParen = true;
385 break;
386 }
387 }
388
389 // Environment variables may contain parens so if it looks
390 // like a function, check to see if the environment variable
391 // prefix was explicitly provided.
392 if (isFunction && remainder.StartsWith("(env.", StringComparison.Ordinal))
393 {
394 isFunction = false;
395 }
396
397 // move the currentPosition to the closing paren
398 currentPosition += closingParenPosition;
399
400 if (!foundClosingParen)
401 {
402 if (isFunction)
403 {
404 this.Messaging.Write(ErrorMessages.InvalidPreprocessorFunction(context.CurrentSourceLineNumber, remainder));
405 break;
406 }
407 else
408 {
409 this.Messaging.Write(ErrorMessages.InvalidPreprocessorVariable(context.CurrentSourceLineNumber, remainder));
410 break;
411 }
412 }
413
414 var subString = remainder.Substring(1, closingParenPosition - 1);
415 string result = null;
416 if (isFunction)
417 {
418 result = this.EvaluateFunction(context, subString);
419 }
420 else
421 {
422 result = this.GetVariableValue(context, subString, true);
423 }
424
425 if (null == result)
426 {
427 if (isFunction)
428 {
429 this.Messaging.Write(ErrorMessages.UndefinedPreprocessorFunction(context.CurrentSourceLineNumber, subString));
430 break;
431 }
432 else
433 {
434 this.Messaging.Write(ErrorMessages.UndefinedPreprocessorVariable(context.CurrentSourceLineNumber, subString));
435 break;
436 }
437 }
438 else
439 {
440 if (!isFunction)
441 {
442 //this.OnResolvedVariable(new ResolvedVariableEventArgs(context.CurrentSourceLineNumber, subString, result));
443 }
444 }
445
446 sb.Append(result);
447 end += closingParenPosition + 1;
448 }
449 else // just a floating "$" so put it in the final string (i.e. leave it alone) and keep processing
450 {
451 sb.Append('$');
452 }
453 }
454
455 if (end < value.Length)
456 {
457 sb.Append(value.Substring(end));
458 }
459
460 return sb.ToString();
461 }
462
463 public void RemoveVariable(IPreprocessContext context, string name)
464 {
465 if (!context.Variables.Remove(name))
466 {
467 this.Messaging.Write(ErrorMessages.CannotReundefineVariable(context.CurrentSourceLineNumber, name));
468 }
469 }
470
471 private Dictionary<string, IPreprocessorExtension> GetExtensionsByPrefix()
472 {
473 if (this.ExtensionsByPrefix == null)
474 {
475 this.ExtensionsByPrefix = new Dictionary<string, IPreprocessorExtension>();
476
477 var extensionManager = this.ServiceProvider.GetService<IExtensionManager>();
478
479 var extensions = extensionManager.GetServices<IPreprocessorExtension>();
480
481 foreach (var extension in extensions)
482 {
483 if (null != extension.Prefixes)
484 {
485 foreach (string prefix in extension.Prefixes)
486 {
487 if (!this.ExtensionsByPrefix.ContainsKey(prefix))
488 {
489 this.ExtensionsByPrefix.Add(prefix, extension);
490 }
491 }
492 }
493 }
494 }
495
496 return this.ExtensionsByPrefix;
497 }
498 }
499}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.ExtensibilityServices
4{
5 using WixToolset.Extensibility.Data;
6
7 internal class ResolvedDirectory : IResolvedDirectory
8 {
9 public string DirectoryParent { get; set; }
10
11 public string Name { get; set; }
12
13 public string Path { get; set; }
14 }
15}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.ExtensibilityServices
4{
5 using System;
6 using System.Collections.Generic;
7 using WixToolset.Data;
8 using WixToolset.Extensibility;
9 using WixToolset.Extensibility.Services;
10
11 internal class SymbolDefinitionCreator : ISymbolDefinitionCreator
12 {
13 public SymbolDefinitionCreator(IServiceProvider serviceProvider)
14 {
15 this.ServiceProvider = serviceProvider;
16 }
17
18 private IServiceProvider ServiceProvider { get; }
19
20 private IEnumerable<IExtensionData> ExtensionData { get; set; }
21
22 private Dictionary<string, IntermediateSymbolDefinition> CustomDefinitionByName { get; } = new Dictionary<string, IntermediateSymbolDefinition>();
23
24 public void AddCustomSymbolDefinition(IntermediateSymbolDefinition definition)
25 {
26 if (!this.CustomDefinitionByName.TryGetValue(definition.Name, out var existing) || definition.Revision > existing.Revision)
27 {
28 this.CustomDefinitionByName[definition.Name] = definition;
29 }
30 }
31
32 public bool TryGetSymbolDefinitionByName(string name, out IntermediateSymbolDefinition symbolDefinition)
33 {
34 // First, look in the built-ins.
35 symbolDefinition = SymbolDefinitions.ByName(name);
36
37 if (symbolDefinition == null)
38 {
39 if (this.ExtensionData == null)
40 {
41 this.LoadExtensionData();
42 }
43
44 // Second, look in the extensions.
45 foreach (var data in this.ExtensionData)
46 {
47 if (data.TryGetSymbolDefinitionByName(name, out symbolDefinition))
48 {
49 break;
50 }
51 }
52
53 // Finally, look in the custom symbol definitions provided during an intermediate load.
54 if (symbolDefinition == null)
55 {
56 this.CustomDefinitionByName.TryGetValue(name, out symbolDefinition);
57 }
58 }
59
60 return symbolDefinition != null;
61 }
62
63 private void LoadExtensionData()
64 {
65 var extensionManager = this.ServiceProvider.GetService<IExtensionManager>();
66
67 this.ExtensionData = extensionManager.GetServices<IExtensionData>();
68 }
69 }
70}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.ExtensibilityServices
4{
5 using WixToolset.Data;
6 using WixToolset.Extensibility.Data;
7
8 internal class TrackedFile : ITrackedFile
9 {
10 public TrackedFile(string path, TrackedFileType type, SourceLineNumber sourceLineNumbers)
11 {
12 this.Path = path;
13 this.Type = type;
14 this.SourceLineNumbers = sourceLineNumbers;
15 this.Clean = (type == TrackedFileType.Intermediate || type == TrackedFileType.Final);
16 }
17
18 public bool Clean { get; set; }
19
20 public string Path { get; set; }
21
22 public SourceLineNumber SourceLineNumbers { get; set; }
23
24 public TrackedFileType Type { get; set; }
25 }
26}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.ExtensibilityServices
4{
5 using System;
6 using System.Net;
7 using System.Security.Cryptography;
8 using System.Text;
9
10 /// <summary>
11 /// Implementation of RFC 4122 - A Universally Unique Identifier (UUID) URN Namespace.
12 /// </summary>
13 internal static class Uuid
14 {
15 /// <summary>
16 /// Creates a version 3 name-based UUID.
17 /// </summary>
18 /// <param name="namespaceGuid">The namespace UUID.</param>
19 /// <param name="value">The value.</param>
20 /// <returns>The UUID for the given namespace and value.</returns>
21 public static Guid NewUuid(Guid namespaceGuid, string value)
22 {
23 byte[] namespaceBytes = namespaceGuid.ToByteArray();
24 short uuidVersion = (short)0x5000;
25
26 // get the fields of the guid which are in host byte ordering
27 int timeLow = BitConverter.ToInt32(namespaceBytes, 0);
28 short timeMid = BitConverter.ToInt16(namespaceBytes, 4);
29 short timeHiAndVersion = BitConverter.ToInt16(namespaceBytes, 6);
30
31 // convert to network byte ordering
32 timeLow = IPAddress.HostToNetworkOrder(timeLow);
33 timeMid = IPAddress.HostToNetworkOrder(timeMid);
34 timeHiAndVersion = IPAddress.HostToNetworkOrder(timeHiAndVersion);
35
36 // get the bytes from the value
37 byte[] valueBytes = Encoding.Unicode.GetBytes(value);
38
39 // fill-in the hash input buffer
40 byte[] buffer = new byte[namespaceBytes.Length + valueBytes.Length];
41 Buffer.BlockCopy(BitConverter.GetBytes(timeLow), 0, buffer, 0, 4);
42 Buffer.BlockCopy(BitConverter.GetBytes(timeMid), 0, buffer, 4, 2);
43 Buffer.BlockCopy(BitConverter.GetBytes(timeHiAndVersion), 0, buffer, 6, 2);
44 Buffer.BlockCopy(namespaceBytes, 8, buffer, 8, 8);
45 Buffer.BlockCopy(valueBytes, 0, buffer, 16, valueBytes.Length);
46
47 // perform the appropriate hash of the namespace and value
48 byte[] hash;
49 using (SHA1 sha1 = SHA1.Create())
50 {
51 hash = sha1.ComputeHash(buffer);
52 }
53
54 // get the fields of the hash which are in network byte ordering
55 timeLow = BitConverter.ToInt32(hash, 0);
56 timeMid = BitConverter.ToInt16(hash, 4);
57 timeHiAndVersion = BitConverter.ToInt16(hash, 6);
58
59 // convert to network byte ordering
60 timeLow = IPAddress.NetworkToHostOrder(timeLow);
61 timeMid = IPAddress.NetworkToHostOrder(timeMid);
62 timeHiAndVersion = IPAddress.NetworkToHostOrder(timeHiAndVersion);
63
64 // set the version and variant bits
65 timeHiAndVersion &= 0x0FFF;
66 timeHiAndVersion += uuidVersion;
67 hash[8] &= 0x3F;
68 hash[8] |= 0x80;
69
70 // put back the converted values into a 128-bit value
71 byte[] guidBits = new byte[16];
72 Buffer.BlockCopy(hash, 0, guidBits, 0, 16);
73
74 Buffer.BlockCopy(BitConverter.GetBytes(timeLow), 0, guidBits, 0, 4);
75 Buffer.BlockCopy(BitConverter.GetBytes(timeMid), 0, guidBits, 4, 2);
76 Buffer.BlockCopy(BitConverter.GetBytes(timeHiAndVersion), 0, guidBits, 6, 2);
77
78 return new Guid(guidBits);
79 }
80 }
81}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3using System.Resources;
4
5[assembly: NeutralResourcesLanguage("en-US")]
6
7namespace WixToolset.Core.ExtensibilityServices
8{
9 using System;
10 using System.Diagnostics;
11 using System.IO;
12 using System.Reflection;
13 using WixToolset.Extensibility.Services;
14
15 /// <summary>
16 /// Branding strings.
17 /// </summary>
18 internal class WixBranding : IWixBranding
19 {
20 /// <summary>
21 /// News URL for the distribution.
22 /// </summary>
23 public static string NewsUrl = "http://wixtoolset.org/news/";
24
25 /// <summary>
26 /// Short product name for the distribution.
27 /// </summary>
28 public static string ShortProduct = "WiX Toolset";
29
30 /// <summary>
31 /// Support URL for the distribution.
32 /// </summary>
33 public static string SupportUrl = "http://wixtoolset.org/";
34
35 /// <summary>
36 /// Telemetry URL format for the distribution.
37 /// </summary>
38 public static string TelemetryUrlFormat = "http://wixtoolset.org/telemetry/v{0}/?r={1}";
39
40 /// <summary>
41 /// VS Extensions Landing page Url for the distribution.
42 /// </summary>
43 public static string VSExtensionsLandingUrl = "http://wixtoolset.org/releases/";
44
45 public string GetCreatingApplication()
46 {
47 return this.ReplacePlaceholders("[AssemblyProduct] ([FileVersion])");
48 }
49
50 public string ReplacePlaceholders(string original, Assembly assembly = null)
51 {
52 if (assembly == null)
53 {
54 assembly = typeof(WixBranding).Assembly;
55 }
56
57 var commonVersionPath = Path.Combine(Path.GetDirectoryName(typeof(WixBranding).Assembly.Location), "wixver.dll");
58 if (File.Exists(commonVersionPath))
59 {
60 var commonFileVersion = FileVersionInfo.GetVersionInfo(commonVersionPath);
61
62 original = original.Replace("[FileCopyright]", commonFileVersion.LegalCopyright);
63 original = original.Replace("[FileVersion]", commonFileVersion.FileVersion);
64 }
65
66 var fileVersion = FileVersionInfo.GetVersionInfo(assembly.Location);
67
68 original = original.Replace("[FileComments]", fileVersion.Comments);
69 original = original.Replace("[FileCopyright]", fileVersion.LegalCopyright);
70 original = original.Replace("[FileProductName]", fileVersion.ProductName);
71 original = original.Replace("[FileVersion]", fileVersion.FileVersion);
72
73 if (original.Contains("[FileVersionMajorMinor]"))
74 {
75 var version = new Version(fileVersion.FileVersion);
76 original = original.Replace("[FileVersionMajorMinor]", String.Concat(version.Major, ".", version.Minor));
77 }
78
79 if (TryGetAttribute(assembly, out AssemblyCompanyAttribute company))
80 {
81 original = original.Replace("[AssemblyCompany]", company.Company);
82 }
83
84 if (TryGetAttribute(assembly, out AssemblyCopyrightAttribute copyright))
85 {
86 original = original.Replace("[AssemblyCopyright]", copyright.Copyright);
87 }
88
89 if (TryGetAttribute(assembly, out AssemblyDescriptionAttribute description))
90 {
91 original = original.Replace("[AssemblyDescription]", description.Description);
92 }
93
94 if (TryGetAttribute(assembly, out AssemblyProductAttribute product))
95 {
96 original = original.Replace("[AssemblyProduct]", product.Product);
97 }
98
99 if (TryGetAttribute(assembly, out AssemblyTitleAttribute title))
100 {
101 original = original.Replace("[AssemblyTitle]", title.Title);
102 }
103
104 original = original.Replace("[NewsUrl]", NewsUrl);
105 original = original.Replace("[ShortProduct]", ShortProduct);
106 original = original.Replace("[SupportUrl]", SupportUrl);
107
108 return original;
109 }
110
111 private static bool TryGetAttribute<T>(Assembly assembly, out T attribute) where T : Attribute
112 {
113 attribute = null;
114
115 var customAttributes = assembly.GetCustomAttributes(typeof(T), false);
116 if (null != customAttributes && 0 < customAttributes.Length)
117 {
118 attribute = customAttributes[0] as T;
119 }
120
121 return null != attribute;
122 }
123 }
124}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using WixToolset.Extensibility.Data;
6
7#pragma warning disable 1591 // TODO: add documentation
8 public interface IBinder
9 {
10 IBindResult Bind(IBindContext context);
11 }
12}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using WixToolset.Data;
6 using WixToolset.Extensibility.Data;
7
8#pragma warning disable 1591 // TODO: add documentation
9 public interface ICompiler
10 {
11 Intermediate Compile(ICompileContext context);
12 }
13}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using WixToolset.Extensibility.Data;
6
7#pragma warning disable 1591 // TODO: add documentation
8 public interface IDecompiler
9 {
10 IDecompileResult Decompile(IDecompileContext context);
11 }
12}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using WixToolset.Extensibility.Data;
6
7#pragma warning disable 1591 // TODO: add documentation
8 public interface ILayoutCreator
9 {
10 void Layout(ILayoutContext context);
11 }
12}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using WixToolset.Data;
6 using WixToolset.Extensibility.Data;
7
8#pragma warning disable 1591 // TODO: add documentation
9 public interface ILibrarian
10 {
11 Intermediate Combine(ILibraryContext context);
12 }
13}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using WixToolset.Data;
6 using WixToolset.Extensibility.Data;
7
8#pragma warning disable 1591 // TODO: add documentation
9 public interface ILinker
10 {
11 Intermediate Link(ILinkContext context);
12 }
13}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System.Xml.Linq;
6 using WixToolset.Data;
7
8 /// <summary>
9 /// Parses localization source files.
10 /// </summary>
11 public interface ILocalizationParser
12 {
13 /// <summary>
14 /// Loads a localization file from a path on disk.
15 /// </summary>
16 /// <param name="path">Path to localization file saved on disk.</param>
17 /// <returns>Returns the loaded localization file.</returns>
18 Localization ParseLocalization(string path);
19
20 /// <summary>
21 /// Loads a localization file from memory.
22 /// </summary>
23 /// <param name="document">Document to parse as localization file.</param>
24 /// <returns>Returns the loaded localization file.</returns>
25 Localization ParseLocalization(XDocument document);
26 }
27}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System.Xml;
6 using WixToolset.Extensibility.Data;
7
8#pragma warning disable 1591 // TODO: add documentation, move into Extensibility
9 public interface IPreprocessor
10 {
11 IPreprocessResult Preprocess(IPreprocessContext context);
12
13 IPreprocessResult Preprocess(IPreprocessContext context, XmlReader reader);
14 }
15}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using WixToolset.Extensibility.Data;
6
7 /// <summary>
8 /// Resolves localization and bind variables.
9 /// </summary>
10 public interface IResolver
11 {
12 /// <summary>
13 /// Resolve localization and bind variables.
14 /// </summary>
15 /// <param name="context">Resolve context.</param>
16 /// <returns>Resolve result.</returns>
17 IResolveResult Resolve(IResolveContext context);
18 }
19}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using WixToolset.Data;
6
7#pragma warning disable 1591 // TODO: add documentation, move into Extensibility
8 public interface IUnbinder
9 {
10 Intermediate Unbind(string file, OutputType outputType, string exportBasePath);
11 }
12}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using WixToolset.Data;
6 using WixToolset.Extensibility.Data;
7
8 internal class IncludedFile : IIncludedFile
9 {
10 public string Path { get; set; }
11
12 public SourceLineNumber SourceLineNumbers { get; set; }
13 }
14}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System;
6 using WixToolset.Extensibility.Data;
7 using WixToolset.Extensibility.Services;
8
9 internal class InscribeContext : IInscribeContext
10 {
11 public InscribeContext(IServiceProvider serviceProvider)
12 {
13 this.ServiceProvider = serviceProvider;
14 }
15
16 public IServiceProvider ServiceProvider { get; }
17
18 public string IntermediateFolder { get; set; }
19
20 public string InputFilePath { get; set; }
21
22 public string SignedEngineFile { get; set; }
23
24 public string OutputFile { get; set; }
25 }
26}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Threading;
8 using WixToolset.Extensibility;
9 using WixToolset.Extensibility.Data;
10
11 internal class LayoutContext : ILayoutContext
12 {
13 internal LayoutContext(IServiceProvider serviceProvider)
14 {
15 this.ServiceProvider = serviceProvider;
16 }
17
18 public IServiceProvider ServiceProvider { get; }
19
20 public IReadOnlyCollection<ILayoutExtension> Extensions { get; set; }
21
22 public IReadOnlyCollection<IFileSystemExtension> FileSystemExtensions { get; set; }
23
24 public IReadOnlyCollection<IFileTransfer> FileTransfers { get; set; }
25
26 public IReadOnlyCollection<ITrackedFile> TrackedFiles { get; set; }
27
28 public string IntermediateFolder { get; set; }
29
30 public string ContentsFile { get; set; }
31
32 public string OutputsFile { get; set; }
33
34 public string BuiltOutputsFile { get; set; }
35
36 public bool ResetAcls { get; set; }
37
38 public CancellationToken CancellationToken { get; set; }
39 }
40}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 using WixToolset.Core.Bind;
10 using WixToolset.Data;
11 using WixToolset.Extensibility.Data;
12 using WixToolset.Extensibility.Services;
13
14 /// <summary>
15 /// Layout for the WiX toolset.
16 /// </summary>
17 internal class LayoutCreator : ILayoutCreator
18 {
19 internal LayoutCreator(IServiceProvider serviceProvider)
20 {
21 this.Messaging = serviceProvider.GetService<IMessaging>();
22 }
23
24 private IMessaging Messaging { get; }
25
26 public void Layout(ILayoutContext context)
27 {
28 // Pre-layout.
29 //
30 foreach (var extension in context.Extensions)
31 {
32 extension.PreLayout(context);
33 }
34
35 try
36 {
37 // Final step in binding that transfers (moves/copies) all files generated into the appropriate
38 // location in the source image.
39 if (context.FileTransfers?.Any() == true)
40 {
41 this.Messaging.Write(VerboseMessages.LayingOutMedia());
42
43 var command = new TransferFilesCommand(this.Messaging, context.Extensions, context.FileTransfers, context.ResetAcls);
44 command.Execute();
45 }
46
47 if (context.TrackedFiles != null)
48 {
49 this.CleanTempFiles(context.IntermediateFolder, context.TrackedFiles);
50 }
51 }
52 finally
53 {
54 if (context.TrackedFiles != null)
55 {
56 if (!String.IsNullOrEmpty(context.ContentsFile))
57 {
58 this.CreateContentsFile(context.ContentsFile, context.TrackedFiles);
59 }
60
61 if (!String.IsNullOrEmpty(context.OutputsFile))
62 {
63 this.CreateOutputsFile(context.OutputsFile, context.TrackedFiles);
64 }
65
66 if (!String.IsNullOrEmpty(context.BuiltOutputsFile))
67 {
68 this.CreateBuiltOutputsFile(context.BuiltOutputsFile, context.TrackedFiles);
69 }
70 }
71 }
72
73 // Post-layout.
74 foreach (var extension in context.Extensions)
75 {
76 extension.PostLayout();
77 }
78 }
79
80 /// <summary>
81 /// Writes the paths to the content files to a text file.
82 /// </summary>
83 /// <param name="path">Path to write file.</param>
84 /// <param name="trackedFiles">Collection of paths to content files that will be written to file.</param>
85 private void CreateContentsFile(string path, IEnumerable<ITrackedFile> trackedFiles)
86 {
87 var uniqueInputFilePaths = new SortedSet<string>(trackedFiles.Where(t => t.Type == TrackedFileType.Input).Select(t => t.Path), StringComparer.OrdinalIgnoreCase);
88
89 if (!uniqueInputFilePaths.Any())
90 {
91 return;
92 }
93
94 var directory = Path.GetDirectoryName(path);
95 Directory.CreateDirectory(directory);
96
97 using (var contents = new StreamWriter(path, false))
98 {
99 foreach (var inputPath in uniqueInputFilePaths)
100 {
101 contents.WriteLine(inputPath);
102 }
103 }
104 }
105
106 /// <summary>
107 /// Writes the paths to the output files to a text file.
108 /// </summary>
109 /// <param name="path">Path to write file.</param>
110 /// <param name="trackedFiles">Collection of files that were transferred to the output directory.</param>
111 private void CreateOutputsFile(string path, IEnumerable<ITrackedFile> trackedFiles)
112 {
113 var uniqueOutputPaths = new SortedSet<string>(trackedFiles.Where(t => t.Clean).Select(t => t.Path), StringComparer.OrdinalIgnoreCase);
114
115 if (!uniqueOutputPaths.Any())
116 {
117 return;
118 }
119
120 var directory = Path.GetDirectoryName(path);
121 Directory.CreateDirectory(directory);
122
123 using (var outputs = new StreamWriter(path, false))
124 {
125 //// Don't list files where the source is the same as the destination since
126 //// that might be the only place the file exists. The outputs file is often
127 //// used to delete stuff and losing the original source would be bad.
128 //var uniqueOutputPaths = new SortedSet<string>(fileTransfers.Where(ft => !ft.Redundant).Select(ft => ft.Destination), StringComparer.OrdinalIgnoreCase);
129
130 foreach (var outputPath in uniqueOutputPaths)
131 {
132 outputs.WriteLine(outputPath);
133 }
134 }
135 }
136
137 /// <summary>
138 /// Writes the paths to the built output files to a text file.
139 /// </summary>
140 /// <param name="path">Path to write file.</param>
141 /// <param name="trackedFiles">Collection of files that were transferred to the output directory.</param>
142 private void CreateBuiltOutputsFile(string path, IEnumerable<ITrackedFile> trackedFiles)
143 {
144 var uniqueBuiltPaths = new SortedSet<string>(trackedFiles.Where(t => t.Type == TrackedFileType.Final).Select(t => t.Path), StringComparer.OrdinalIgnoreCase);
145
146 if (!uniqueBuiltPaths.Any())
147 {
148 return;
149 }
150
151 var directory = Path.GetDirectoryName(path);
152 Directory.CreateDirectory(directory);
153
154 using (var outputs = new StreamWriter(path, false))
155 {
156 foreach (var builtPath in uniqueBuiltPaths)
157 {
158 outputs.WriteLine(builtPath);
159 }
160 }
161 }
162
163 private void CleanTempFiles(string intermediateFolder, IEnumerable<ITrackedFile> trackedFiles)
164 {
165 var uniqueTempPaths = new SortedSet<string>(trackedFiles.Where(t => t.Type == TrackedFileType.Temporary).Select(t => t.Path), StringComparer.OrdinalIgnoreCase);
166
167 if (!uniqueTempPaths.Any())
168 {
169 return;
170 }
171
172 var uniqueFolders = new SortedSet<string>(StringComparer.OrdinalIgnoreCase)
173 {
174 intermediateFolder
175 };
176
177 // Clean up temp files.
178 foreach (var tempPath in uniqueTempPaths)
179 {
180 try
181 {
182 this.SplitUniqueFolders(intermediateFolder, tempPath, uniqueFolders);
183
184 File.Delete(tempPath);
185 }
186 catch // delete is best effort.
187 {
188 }
189 }
190
191 // Clean up empty temp folders.
192 foreach (var folder in uniqueFolders.Reverse())
193 {
194 try
195 {
196 Directory.Delete(folder);
197 }
198 catch // delete is best effort.
199 {
200 }
201 }
202 }
203
204 private void SplitUniqueFolders(string intermediateFolder, string tempPath, SortedSet<string> uniqueFolders)
205 {
206 if (tempPath.StartsWith(intermediateFolder, StringComparison.OrdinalIgnoreCase))
207 {
208 var folder = Path.GetDirectoryName(tempPath).Substring(intermediateFolder.Length);
209
210 var parts = folder.Split(new[] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries);
211
212 folder = intermediateFolder;
213
214 foreach (var part in parts)
215 {
216 folder = Path.Combine(folder, part);
217
218 uniqueFolders.Add(folder);
219 }
220 }
221 }
222 }
223}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using WixToolset.Core.Bind;
9 using WixToolset.Core.Link;
10 using WixToolset.Data;
11 using WixToolset.Extensibility.Data;
12 using WixToolset.Extensibility.Services;
13
14 /// <summary>
15 /// Core librarian tool.
16 /// </summary>
17 internal class Librarian : ILibrarian
18 {
19 internal Librarian(IServiceProvider serviceProvider)
20 {
21 this.ServiceProvider = serviceProvider;
22
23 this.Messaging = this.ServiceProvider.GetService<IMessaging>();
24 }
25
26 private IServiceProvider ServiceProvider { get; }
27
28 private IMessaging Messaging { get; }
29
30 /// <summary>
31 /// Create a library by combining several intermediates (objects).
32 /// </summary>
33 /// <returns>Returns the new library.</returns>
34 public Intermediate Combine(ILibraryContext context)
35 {
36 if (String.IsNullOrEmpty(context.LibraryId))
37 {
38 context.LibraryId = Convert.ToBase64String(Guid.NewGuid().ToByteArray()).TrimEnd('=').Replace('+', '.').Replace('/', '_');
39 }
40
41 foreach (var extension in context.Extensions)
42 {
43 extension.PreCombine(context);
44 }
45
46 Intermediate library = null;
47 try
48 {
49 var sections = context.Intermediates.SelectMany(i => i.Sections).ToList();
50
51 var collate = new CollateLocalizationsCommand(this.Messaging, context.Localizations);
52 var localizationsByCulture = collate.Execute();
53
54 if (this.Messaging.EncounteredError)
55 {
56 return null;
57 }
58
59 this.ResolveFilePathsToEmbed(context, sections);
60
61 foreach (var section in sections)
62 {
63 section.AssignToLibrary(context.LibraryId);
64 }
65
66 library = new Intermediate(context.LibraryId, IntermediateLevels.Compiled, sections, localizationsByCulture);
67
68 library.UpdateLevel(IntermediateLevels.Combined);
69
70 this.Validate(library);
71 }
72 finally
73 {
74 foreach (var extension in context.Extensions)
75 {
76 extension.PostCombine(library);
77 }
78 }
79
80 return this.Messaging.EncounteredError ? null : library;
81 }
82
83 private void ResolveFilePathsToEmbed(ILibraryContext context, IEnumerable<IntermediateSection> sections)
84 {
85 // Resolve paths to files that are to be embedded in the library.
86 if (context.BindFiles)
87 {
88 var variableResolver = this.ServiceProvider.GetService<IVariableResolver>();
89
90 var fileResolver = new FileResolver(context.BindPaths, context.Extensions);
91
92 foreach (var symbol in sections.SelectMany(s => s.Symbols))
93 {
94 foreach (var field in symbol.Fields.Where(f => f?.Type == IntermediateFieldType.Path))
95 {
96 var pathField = field.AsPath();
97
98 if (pathField != null && !String.IsNullOrEmpty(pathField.Path))
99 {
100 var resolution = variableResolver.ResolveVariables(symbol.SourceLineNumbers, pathField.Path);
101
102 var file = fileResolver.Resolve(symbol.SourceLineNumbers, symbol.Definition, resolution.Value);
103
104 if (!String.IsNullOrEmpty(file))
105 {
106 // File was successfully resolved so track the embedded index as the embedded file index.
107 field.Set(new IntermediateFieldPathValue { Embed = true, Path = file });
108 }
109 else
110 {
111 this.Messaging.Write(ErrorMessages.FileNotFound(symbol.SourceLineNumbers, pathField.Path, symbol.Definition.Name));
112 }
113 }
114 }
115 }
116 }
117 }
118
119 private void Validate(Intermediate library)
120 {
121 var find = new FindEntrySectionAndLoadSymbolsCommand(this.Messaging, library.Sections, OutputType.Library);
122 find.Execute();
123
124 // TODO: Consider bringing this sort of verification back.
125 // foreach (Section section in library.Sections)
126 // {
127 // ResolveReferencesCommand resolve = new ResolveReferencesCommand(find.EntrySection, find.Symbols);
128 // resolve.Execute();
129 //
130 // ReportDuplicateResolvedSymbolErrorsCommand reportDupes = new ReportDuplicateResolvedSymbolErrorsCommand(find.SymbolsWithDuplicates, resolve.ResolvedSections);
131 // reportDupes.Execute();
132 // }
133 }
134 }
135}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Threading;
8 using WixToolset.Data;
9 using WixToolset.Extensibility;
10 using WixToolset.Extensibility.Data;
11 using WixToolset.Extensibility.Services;
12
13 internal class LibraryContext : ILibraryContext
14 {
15 internal LibraryContext(IServiceProvider serviceProvider)
16 {
17 this.ServiceProvider = serviceProvider;
18 }
19
20 public IServiceProvider ServiceProvider { get; }
21
22 public IMessaging Messaging { get; set; }
23
24 public bool BindFiles { get; set; }
25
26 public IReadOnlyCollection<IBindPath> BindPaths { get; set; }
27
28 public IReadOnlyCollection<ILibrarianExtension> Extensions { get; set; }
29
30 public string LibraryId { get; set; }
31
32 public IReadOnlyCollection<Localization> Localizations { get; set; }
33
34 public IReadOnlyCollection<Intermediate> Intermediates { get; set; }
35
36 public CancellationToken CancellationToken { get; set; }
37 }
38}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Link
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using WixToolset.Data;
9 using WixToolset.Extensibility.Services;
10
11 internal class CollateLocalizationsCommand
12 {
13 public CollateLocalizationsCommand(IMessaging messaging, IEnumerable<Localization> localizations)
14 {
15 this.Messaging = messaging;
16 this.Localizations = localizations;
17 }
18
19 private IMessaging Messaging { get; }
20
21 private IEnumerable<Localization> Localizations { get; }
22
23 public Dictionary<string, Localization> Execute()
24 {
25 var localizationsByCulture = new Dictionary<string, Localization>(StringComparer.OrdinalIgnoreCase);
26
27 foreach (var localization in this.Localizations)
28 {
29 if (localizationsByCulture.TryGetValue(localization.Culture, out var existingCulture))
30 {
31 var merged = this.Merge(existingCulture, localization);
32 localizationsByCulture[localization.Culture] = merged;
33 }
34 else
35 {
36 localizationsByCulture.Add(localization.Culture, localization);
37 }
38 }
39
40 return localizationsByCulture;
41 }
42
43 private Localization Merge(Localization existingLocalization, Localization localization)
44 {
45 var variables = existingLocalization.Variables.ToDictionary(v => v.Id);
46 var controls = existingLocalization.LocalizedControls.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
47
48 foreach (var newVariable in localization.Variables)
49 {
50 if (!variables.TryGetValue(newVariable.Id, out var existingVariable) || (existingVariable.Overridable && !newVariable.Overridable))
51 {
52 variables[newVariable.Id] = newVariable;
53 }
54 else if (!newVariable.Overridable)
55 {
56 this.Messaging.Write(ErrorMessages.DuplicateLocalizationIdentifier(newVariable.SourceLineNumbers, newVariable.Id));
57 }
58 }
59
60 foreach (var localizedControl in localization.LocalizedControls)
61 {
62 if (!controls.ContainsKey(localizedControl.Key))
63 {
64 controls.Add(localizedControl.Key, localizedControl.Value);
65 }
66 }
67
68 return new Localization(existingLocalization.Codepage ?? localization.Codepage, existingLocalization.SummaryInformationCodepage ?? localization.SummaryInformationCodepage, existingLocalization.Culture, variables, controls);
69 }
70 }
71}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Link
4{
5 using System.Collections.Generic;
6 using WixToolset.Data;
7
8 /// <summary>
9 /// Object that connects things (components/modules) to features.
10 /// </summary>
11 internal class ConnectToFeature
12 {
13 /// <summary>
14 /// Creates a new connect to feature.
15 /// </summary>
16 /// <param name="section">Section this connect belongs to.</param>
17 /// <param name="childId">Id of the child.</param>
18 /// <param name="primaryFeature">Sets the primary feature for the connection.</param>
19 /// <param name="explicitPrimaryFeature">Sets if this is explicit primary.</param>
20 public ConnectToFeature(IntermediateSection section, string childId, string primaryFeature, bool explicitPrimaryFeature)
21 {
22 this.Section = section;
23 this.ChildId = childId;
24
25 this.PrimaryFeature = primaryFeature;
26 this.IsExplicitPrimaryFeature = explicitPrimaryFeature;
27 }
28
29 /// <summary>
30 /// Gets the section.
31 /// </summary>
32 /// <value>Section.</value>
33 public IntermediateSection Section { get; }
34
35 /// <summary>
36 /// Gets the child identifier.
37 /// </summary>
38 /// <value>The child identifier.</value>
39 public string ChildId { get; }
40
41 /// <summary>
42 /// Gets or sets if the flag for if the primary feature was set explicitly.
43 /// </summary>
44 /// <value>The flag for if the primary feature was set explicitly.</value>
45 public bool IsExplicitPrimaryFeature { get; set; }
46
47 /// <summary>
48 /// Gets or sets the primary feature.
49 /// </summary>
50 /// <value>The primary feature.</value>
51 public string PrimaryFeature { get; set; }
52
53 /// <summary>
54 /// Gets the features connected to.
55 /// </summary>
56 /// <value>Features connected to.</value>
57 public List<string> ConnectFeatures { get; } = new List<string>();
58 }
59}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Link
4{
5 using System;
6 using System.Collections;
7
8 /// <summary>
9 /// Hash collection of connect to feature objects.
10 /// </summary>
11 internal class ConnectToFeatureCollection : ICollection
12 {
13 private Hashtable collection;
14
15 /// <summary>
16 /// Instantiate a new ConnectToFeatureCollection class.
17 /// </summary>
18 public ConnectToFeatureCollection()
19 {
20 this.collection = new Hashtable();
21 }
22
23 /// <summary>
24 /// Gets the number of items in the collection.
25 /// </summary>
26 /// <value>Number of items in collection.</value>
27 public int Count
28 {
29 get { return this.collection.Count; }
30 }
31
32 /// <summary>
33 /// Gets if the collection has been synchronized.
34 /// </summary>
35 /// <value>True if the collection has been synchronized.</value>
36 public bool IsSynchronized
37 {
38 get { return this.collection.IsSynchronized; }
39 }
40
41 /// <summary>
42 /// Gets the object used to synchronize the collection.
43 /// </summary>
44 /// <value>Oject used the synchronize the collection.</value>
45 public object SyncRoot
46 {
47 get { return this.collection.SyncRoot; }
48 }
49
50 /// <summary>
51 /// Gets a feature connection by child id.
52 /// </summary>
53 /// <param name="childId">Identifier of child to locate.</param>
54 public ConnectToFeature this[string childId]
55 {
56 get { return (ConnectToFeature)this.collection[childId]; }
57 }
58
59 /// <summary>
60 /// Adds a feature connection to the collection.
61 /// </summary>
62 /// <param name="connection">Feature connection to add.</param>
63 public void Add(ConnectToFeature connection)
64 {
65 if (null == connection)
66 {
67 throw new ArgumentNullException("connection");
68 }
69
70 this.collection.Add(connection.ChildId, connection);
71 }
72
73 /// <summary>
74 /// Copies the collection into an array.
75 /// </summary>
76 /// <param name="array">Array to copy the collection into.</param>
77 /// <param name="index">Index to start copying from.</param>
78 public void CopyTo(System.Array array, int index)
79 {
80 this.collection.CopyTo(array, index);
81 }
82
83 /// <summary>
84 /// Gets enumerator for the collection.
85 /// </summary>
86 /// <returns>Enumerator for the collection.</returns>
87 public IEnumerator GetEnumerator()
88 {
89 return this.collection.Values.GetEnumerator();
90 }
91 }
92}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Link
4{
5 /// <summary>
6 /// Object that connects things to modules.
7 /// </summary>
8 internal class ConnectToModule
9 {
10 private string childId;
11 private string module;
12 private string moduleLanguage;
13
14 /// <summary>
15 /// Creates a new connect to module.
16 /// </summary>
17 /// <param name="childId">Id of the child.</param>
18 /// <param name="module">Id of the module.</param>
19 /// <param name="moduleLanguage">Language of the module.</param>
20 public ConnectToModule(string childId, string module, string moduleLanguage)
21 {
22 this.childId = childId;
23 this.module = module;
24 this.moduleLanguage = moduleLanguage;
25 }
26
27 /// <summary>
28 /// Gets the id of the child.
29 /// </summary>
30 /// <value>Child identifier.</value>
31 public string ChildId
32 {
33 get { return this.childId; }
34 }
35
36 /// <summary>
37 /// Gets the id of the module.
38 /// </summary>
39 /// <value>The id of the module.</value>
40 public string Module
41 {
42 get { return this.module; }
43 }
44
45 /// <summary>
46 /// Gets the language of the module.
47 /// </summary>
48 /// <value>The language of the module.</value>
49 public string ModuleLanguage
50 {
51 get { return this.moduleLanguage; }
52 }
53 }
54}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Link
4{
5 using System;
6 using System.Collections;
7
8 /// <summary>
9 /// Hash collection of connect to module objects.
10 /// </summary>
11 internal class ConnectToModuleCollection : ICollection
12 {
13 private Hashtable collection;
14
15 /// <summary>
16 /// Instantiate a new ConnectToModuleCollection class.
17 /// </summary>
18 public ConnectToModuleCollection()
19 {
20 this.collection = new Hashtable();
21 }
22
23 /// <summary>
24 /// Gets the number of elements actually contained in the ConnectToModuleCollection.
25 /// </summary>
26 /// <value>The number of elements actually contained in the ConnectToModuleCollection.</value>
27 public int Count
28 {
29 get { return this.collection.Count; }
30 }
31
32 /// <summary>
33 /// Gets a value indicating whether access to the ConnectToModuleCollection is synchronized (thread-safe).
34 /// </summary>
35 /// <value>true if access to the ConnectToModuleCollection is synchronized (thread-safe); otherwise, false. The default is false.</value>
36 public bool IsSynchronized
37 {
38 get { return this.collection.IsSynchronized; }
39 }
40
41 /// <summary>
42 /// Gets an object that can be used to synchronize access to the ConnectToModuleCollection.
43 /// </summary>
44 /// <value>An object that can be used to synchronize access to the ConnectToModuleCollection.</value>
45 public object SyncRoot
46 {
47 get { return this.collection.SyncRoot; }
48 }
49
50 /// <summary>
51 /// Gets a module connection by child id.
52 /// </summary>
53 /// <param name="childId">Identifier of child to locate.</param>
54 public ConnectToModule this[string childId]
55 {
56 get { return (ConnectToModule)this.collection[childId]; }
57 }
58
59 /// <summary>
60 /// Adds a module connection to the collection.
61 /// </summary>
62 /// <param name="connection">Module connection to add.</param>
63 public void Add(ConnectToModule connection)
64 {
65 if (null == connection)
66 {
67 throw new ArgumentNullException("connection");
68 }
69
70 this.collection.Add(connection.ChildId, connection);
71 }
72
73 /// <summary>
74 /// Copies the entire ConnectToModuleCollection to a compatible one-dimensional Array, starting at the specified index of the target array.
75 /// </summary>
76 /// <param name="array">The one-dimensional Array that is the destination of the elements copied from this ConnectToModuleCollection. The Array must have zero-based indexing.</param>
77 /// <param name="index">The zero-based index in array at which copying begins.</param>
78 public void CopyTo(System.Array array, int index)
79 {
80 this.collection.Keys.CopyTo(array, index);
81 }
82
83 /// <summary>
84 /// Returns an enumerator for the entire ConnectToModuleCollection.
85 /// </summary>
86 /// <returns>An IEnumerator for the entire ConnectToModuleCollection.</returns>
87 public IEnumerator GetEnumerator()
88 {
89 return this.collection.Keys.GetEnumerator();
90 }
91 }
92}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Link
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using WixToolset.Data;
9 using WixToolset.Extensibility.Services;
10
11 internal class FindEntrySectionAndLoadSymbolsCommand
12 {
13 public FindEntrySectionAndLoadSymbolsCommand(IMessaging messaging, IEnumerable<IntermediateSection> sections, OutputType expectedOutpuType)
14 {
15 this.Messaging = messaging;
16 this.Sections = sections;
17 this.ExpectedOutputType = expectedOutpuType;
18 }
19
20 private IMessaging Messaging { get; }
21
22 private IEnumerable<IntermediateSection> Sections { get; }
23
24 private OutputType ExpectedOutputType { get; }
25
26 /// <summary>
27 /// Gets the located entry section after the command is executed.
28 /// </summary>
29 public IntermediateSection EntrySection { get; private set; }
30
31 /// <summary>
32 /// Gets the collection of loaded symbols.
33 /// </summary>
34 public IDictionary<string, SymbolWithSection> SymbolsByName { get; private set; }
35
36 /// <summary>
37 /// Gets the collection of possibly conflicting symbols.
38 /// </summary>
39 public IEnumerable<SymbolWithSection> PossibleConflicts { get; private set; }
40
41 /// <summary>
42 /// Gets the collection of redundant symbols that should not be included
43 /// in the final output.
44 /// </summary>
45 public ISet<IntermediateSymbol> RedundantSymbols { get; private set; }
46
47 public void Execute()
48 {
49 var symbolsByName = new Dictionary<string, SymbolWithSection>();
50 var possibleConflicts = new HashSet<SymbolWithSection>();
51 var redundantSymbols = new HashSet<IntermediateSymbol>();
52
53 if (!Enum.TryParse(this.ExpectedOutputType.ToString(), out SectionType expectedEntrySectionType))
54 {
55 expectedEntrySectionType = SectionType.Unknown;
56 }
57
58 foreach (var section in this.Sections)
59 {
60 // Try to find the one and only entry section.
61 if (SectionType.Product == section.Type || SectionType.Module == section.Type || SectionType.PatchCreation == section.Type || SectionType.Patch == section.Type || SectionType.Bundle == section.Type)
62 {
63 // TODO: remove this?
64 //if (SectionType.Unknown != expectedEntrySectionType && section.Type != expectedEntrySectionType)
65 //{
66 // string outputExtension = Output.GetExtension(this.ExpectedOutputType);
67 // this.Messaging.Write(WixWarnings.UnexpectedEntrySection(section.SourceLineNumbers, section.Type.ToString(), expectedEntrySectionType.ToString(), outputExtension));
68 //}
69
70 if (null == this.EntrySection)
71 {
72 this.EntrySection = section;
73 }
74 else
75 {
76 this.Messaging.Write(ErrorMessages.MultipleEntrySections(this.EntrySection.Symbols.FirstOrDefault()?.SourceLineNumbers, this.EntrySection.Id, section.Id));
77 this.Messaging.Write(ErrorMessages.MultipleEntrySections2(section.Symbols.FirstOrDefault()?.SourceLineNumbers));
78 }
79 }
80
81 // Load all the symbols from the section's tables that create symbols.
82 foreach (var symbol in section.Symbols.Where(t => t.Id != null))
83 {
84 var symbolWithSection = new SymbolWithSection(section, symbol);
85
86 if (!symbolsByName.TryGetValue(symbolWithSection.Name, out var existingSymbol))
87 {
88 symbolsByName.Add(symbolWithSection.Name, symbolWithSection);
89 }
90 else // uh-oh, duplicate symbols.
91 {
92 // If the duplicate symbols are both private directories, there is a chance that they
93 // point to identical symbols. Identical directory symbols are redundant and will not cause
94 // conflicts.
95 if (AccessModifier.Section == existingSymbol.Access && AccessModifier.Section == symbolWithSection.Access &&
96 SymbolDefinitionType.Directory == existingSymbol.Symbol.Definition.Type && existingSymbol.Symbol.IsIdentical(symbolWithSection.Symbol))
97 {
98 // Ensure identical symbol's symbol is marked redundant to ensure (should the symbol be
99 // referenced into the final output) it will not add duplicate primary keys during
100 // the .IDT importing.
101 existingSymbol.AddRedundant(symbolWithSection);
102 redundantSymbols.Add(symbolWithSection.Symbol);
103 }
104 else
105 {
106 symbolWithSection.AddPossibleConflict(existingSymbol);
107 existingSymbol.AddPossibleConflict(symbolWithSection);
108 possibleConflicts.Add(symbolWithSection);
109 }
110 }
111 }
112 }
113
114 this.SymbolsByName = symbolsByName;
115 this.PossibleConflicts = possibleConflicts;
116 this.RedundantSymbols = redundantSymbols;
117 }
118 }
119}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Link
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using WixToolset.Data;
9 using WixToolset.Data.Burn;
10 using WixToolset.Data.Symbols;
11 using WixToolset.Extensibility.Services;
12
13 internal class FlattenAndProcessBundleTablesCommand
14 {
15 public FlattenAndProcessBundleTablesCommand(IntermediateSection entrySection, IMessaging messaging)
16 {
17 this.EntrySection = entrySection;
18 this.Messaging = messaging;
19 }
20
21 private IntermediateSection EntrySection { get; }
22
23 private IMessaging Messaging { get; }
24
25 public void Execute()
26 {
27 this.FlattenBundleTables();
28
29 if (this.Messaging.EncounteredError)
30 {
31 return;
32 }
33
34 this.ProcessBundleComplexReferences();
35 }
36
37 /// <summary>
38 /// Flattens the tables used in a Bundle.
39 /// </summary>
40 private void FlattenBundleTables()
41 {
42 // We need to flatten the nested PayloadGroups and PackageGroups under
43 // UX, Chain, and any Containers. When we're done, the WixGroups table
44 // will hold Payloads under UX, ChainPackages (references?) under Chain,
45 // and ContainerPackages/Payloads under any authored Containers.
46 var groups = new WixGroupingOrdering(this.EntrySection, this.Messaging);
47
48 // Create UX payloads and Package payloads and Container packages
49 groups.UseTypes(new[] { ComplexReferenceParentType.Container, ComplexReferenceParentType.Layout, ComplexReferenceParentType.PackageGroup, ComplexReferenceParentType.PayloadGroup, ComplexReferenceParentType.Package },
50 new[] { ComplexReferenceChildType.ContainerPackage, ComplexReferenceChildType.PackageGroup, ComplexReferenceChildType.Package, ComplexReferenceChildType.PackagePayload, ComplexReferenceChildType.PayloadGroup, ComplexReferenceChildType.Payload });
51 groups.FlattenAndRewriteGroups(ComplexReferenceParentType.Package, false);
52 groups.FlattenAndRewriteGroups(ComplexReferenceParentType.Container, false);
53 groups.FlattenAndRewriteGroups(ComplexReferenceParentType.Layout, false);
54
55 // Create Chain packages...
56 groups.UseTypes(new[] { ComplexReferenceParentType.PackageGroup }, new[] { ComplexReferenceChildType.Package, ComplexReferenceChildType.PackageGroup });
57 groups.FlattenAndRewriteRows(ComplexReferenceParentType.PackageGroup, BurnConstants.BundleChainPackageGroupId, false);
58
59 groups.RemoveUsedGroupRows();
60 }
61
62 private void ProcessBundleComplexReferences()
63 {
64 var containersById = this.EntrySection.Symbols.OfType<WixBundleContainerSymbol>().ToDictionary(c => c.Id.Id);
65 var groups = this.EntrySection.Symbols.OfType<WixGroupSymbol>().ToList();
66 var payloadsById = this.EntrySection.Symbols.OfType<WixBundlePayloadSymbol>().ToDictionary(c => c.Id.Id);
67
68 var containerByPackage = new Dictionary<string, WixBundleContainerSymbol>();
69 var referencedPackages = new HashSet<string>();
70 var payloadsInBA = new HashSet<string>();
71 var payloadsInPackageOrLayout = new HashSet<string>();
72
73 foreach (var groupSymbol in groups)
74 {
75 switch (groupSymbol.ChildType)
76 {
77 case ComplexReferenceChildType.ContainerPackage:
78 switch (groupSymbol.ParentType)
79 {
80 case ComplexReferenceParentType.Container:
81 if (containerByPackage.TryGetValue(groupSymbol.ChildId, out var collisionContainer))
82 {
83 this.Messaging.Write(LinkerErrors.PackageInMultipleContainers(groupSymbol.SourceLineNumbers, groupSymbol.ChildId, groupSymbol.ParentId, collisionContainer.Id.Id));
84 }
85 else
86 {
87 containerByPackage.Add(groupSymbol.ChildId, containersById[groupSymbol.ParentId]);
88 }
89 break;
90 }
91 break;
92 case ComplexReferenceChildType.Package:
93 switch (groupSymbol.ParentType)
94 {
95 case ComplexReferenceParentType.PackageGroup:
96 if (groupSymbol.ParentId == BurnConstants.BundleChainPackageGroupId)
97 {
98 referencedPackages.Add(groupSymbol.ChildId);
99 }
100 break;
101 }
102 break;
103 case ComplexReferenceChildType.Payload:
104 switch (groupSymbol.ParentType)
105 {
106 case ComplexReferenceParentType.Container:
107 if (groupSymbol.ParentId == BurnConstants.BurnUXContainerName)
108 {
109 payloadsInBA.Add(groupSymbol.ChildId);
110 }
111 break;
112 case ComplexReferenceParentType.Layout:
113 payloadsById[groupSymbol.ChildId].LayoutOnly = true;
114 payloadsInPackageOrLayout.Add(groupSymbol.ChildId);
115 break;
116 case ComplexReferenceParentType.Package:
117 payloadsInPackageOrLayout.Add(groupSymbol.ChildId);
118 break;
119 }
120 break;
121 }
122 }
123
124 foreach (var package in this.EntrySection.Symbols.OfType<WixBundlePackageSymbol>())
125 {
126 if (!referencedPackages.Contains(package.Id.Id))
127 {
128 this.Messaging.Write(LinkerErrors.UnscheduledChainPackage(package.SourceLineNumbers, package.Id.Id));
129 }
130 }
131
132 foreach (var rollbackBoundary in this.EntrySection.Symbols.OfType<WixBundleRollbackBoundarySymbol>())
133 {
134 if (!referencedPackages.Contains(rollbackBoundary.Id.Id))
135 {
136 this.Messaging.Write(LinkerErrors.UnscheduledRollbackBoundary(rollbackBoundary.SourceLineNumbers, rollbackBoundary.Id.Id));
137 }
138 }
139
140 foreach (var payload in payloadsById.Values)
141 {
142 var payloadId = payload.Id.Id;
143 if (payloadsInBA.Contains(payloadId))
144 {
145 if (payloadsInPackageOrLayout.Contains(payloadId))
146 {
147 this.Messaging.Write(LinkerErrors.PayloadSharedWithBA(payload.SourceLineNumbers, payloadId));
148 }
149 }
150 else if (!payloadsInPackageOrLayout.Contains(payloadId))
151 {
152 this.Messaging.Write(LinkerErrors.OrphanedPayload(payload.SourceLineNumbers, payloadId));
153 }
154 }
155
156 if (this.Messaging.EncounteredError)
157 {
158 return;
159 }
160
161 // Assign authored payloads to authored containers.
162 // Compressed Payloads not assigned to a container here will get assigned to the default attached container during binding.
163 foreach (var groupSymbol in groups)
164 {
165 if (groupSymbol.ChildType == ComplexReferenceChildType.Payload && groupSymbol.ParentType == ComplexReferenceParentType.Container)
166 {
167 var payloadSymbol = payloadsById[groupSymbol.ChildId];
168 var containerId = groupSymbol.ParentId;
169
170 if (String.IsNullOrEmpty(payloadSymbol.ContainerRef))
171 {
172 if (payloadSymbol.Compressed == false)
173 {
174 this.Messaging.Write(LinkerWarnings.UncompressedPayloadInContainer(payloadSymbol.SourceLineNumbers, groupSymbol.ChildId, containerId));
175 }
176
177 payloadSymbol.Compressed = true;
178 payloadSymbol.ContainerRef = containerId;
179 }
180 else
181 {
182 this.Messaging.Write(LinkerWarnings.PayloadInMultipleContainers(groupSymbol.SourceLineNumbers, groupSymbol.ChildId, containerId, payloadSymbol.ContainerRef));
183 }
184
185 if (payloadSymbol.LayoutOnly)
186 {
187 this.Messaging.Write(LinkerWarnings.LayoutPayloadInContainer(groupSymbol.SourceLineNumbers, groupSymbol.ChildId, containerId));
188 }
189 }
190 }
191
192 }
193 }
194}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Link
4{
5 using WixToolset.Data;
6
7 internal static class IntermediateSymbolExtensions
8 {
9 public static bool IsIdentical(this IntermediateSymbol first, IntermediateSymbol second)
10 {
11 var identical = (first.Definition.Type == second.Definition.Type &&
12 (first.Definition.Type != SymbolDefinitionType.MustBeFromAnExtension || first.Definition.Name == second.Definition.Name) &&
13 first.Definition.FieldDefinitions.Length == second.Definition.FieldDefinitions.Length);
14
15 for (var i = 0; identical && i < first.Definition.FieldDefinitions.Length; ++i)
16 {
17 var firstField = first[i];
18 var secondField = second[i];
19
20 identical = (firstField.AsString() == secondField.AsString());
21 }
22
23 return identical;
24 }
25 }
26}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Link
4{
5 using System.Collections.Generic;
6 using System.Linq;
7 using WixToolset.Data;
8 using WixToolset.Extensibility.Services;
9
10 internal class ReportConflictingSymbolsCommand
11 {
12 public ReportConflictingSymbolsCommand(IMessaging messaging, IEnumerable<SymbolWithSection> possibleConflicts, IEnumerable<IntermediateSection> resolvedSections)
13 {
14 this.Messaging = messaging;
15 this.PossibleConflicts = possibleConflicts;
16 this.ResolvedSections = resolvedSections;
17 }
18
19 private IMessaging Messaging { get; }
20
21 private IEnumerable<SymbolWithSection> PossibleConflicts { get; }
22
23 private IEnumerable<IntermediateSection> ResolvedSections { get; }
24
25 public void Execute()
26 {
27 // Do a quick check if there are any possibly conflicting symbols that don't come from tables that allow
28 // overriding. Hopefully the symbols with possible conflicts list is usually very short list (empty should
29 // be the most common). If we find any matches, we'll do a more costly check to see if the possible conflicting
30 // symbols are in sections we actually referenced. From the resulting set, show an error for each duplicate
31 // (aka: conflicting) symbol.
32 var illegalDuplicates = this.PossibleConflicts.Where(s => s.Symbol.Definition.Type != SymbolDefinitionType.WixAction && s.Symbol.Definition.Type != SymbolDefinitionType.WixVariable).ToList();
33 if (0 < illegalDuplicates.Count)
34 {
35 var referencedSections = new HashSet<IntermediateSection>(this.ResolvedSections);
36
37 foreach (var referencedDuplicate in illegalDuplicates.Where(s => referencedSections.Contains(s.Section)))
38 {
39 var actuallyReferencedDuplicates = referencedDuplicate.PossiblyConflicts.Where(s => referencedSections.Contains(s.Section)).ToList();
40
41 if (actuallyReferencedDuplicates.Any())
42 {
43 this.Messaging.Write(ErrorMessages.DuplicateSymbol(referencedDuplicate.Symbol.SourceLineNumbers, referencedDuplicate.Name));
44
45 foreach (var duplicate in actuallyReferencedDuplicates)
46 {
47 this.Messaging.Write(ErrorMessages.DuplicateSymbol2(duplicate.Symbol.SourceLineNumbers));
48 }
49 }
50 }
51 }
52 }
53 }
54}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Link
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using WixToolset.Data;
9 using WixToolset.Data.Symbols;
10 using WixToolset.Extensibility.Services;
11
12 /// <summary>
13 /// Resolves all the simple references in a section.
14 /// </summary>
15 internal class ResolveReferencesCommand
16 {
17 private readonly IntermediateSection entrySection;
18 private readonly IDictionary<string, SymbolWithSection> symbolsWithSections;
19 private HashSet<SymbolWithSection> referencedSymbols;
20 private HashSet<IntermediateSection> resolvedSections;
21
22 public ResolveReferencesCommand(IMessaging messaging, IntermediateSection entrySection, IDictionary<string, SymbolWithSection> symbolsWithSections)
23 {
24 this.Messaging = messaging;
25 this.entrySection = entrySection;
26 this.symbolsWithSections = symbolsWithSections;
27 this.BuildingMergeModule = (SectionType.Module == entrySection.Type);
28 }
29
30 public IEnumerable<SymbolWithSection> ReferencedSymbolWithSections => this.referencedSymbols;
31
32 public IEnumerable<IntermediateSection> ResolvedSections => this.resolvedSections;
33
34 private bool BuildingMergeModule { get; }
35
36 private IMessaging Messaging { get; }
37
38 /// <summary>
39 /// Resolves all the simple references in a section.
40 /// </summary>
41 public void Execute()
42 {
43 this.resolvedSections = new HashSet<IntermediateSection>();
44 this.referencedSymbols = new HashSet<SymbolWithSection>();
45
46 this.RecursivelyResolveReferences(this.entrySection);
47 }
48
49 /// <summary>
50 /// Recursive helper function to resolve all references of passed in section.
51 /// </summary>
52 /// <param name="section">Section with references to resolve.</param>
53 /// <remarks>Note: recursive function.</remarks>
54 private void RecursivelyResolveReferences(IntermediateSection section)
55 {
56 // If we already resolved this section, move on to the next.
57 if (!this.resolvedSections.Add(section))
58 {
59 return;
60 }
61
62 // Process all of the references contained in this section using the collection of
63 // symbols provided. Then recursively call this method to process the
64 // located symbol's section. All in all this is a very simple depth-first
65 // search of the references per-section.
66 foreach (var wixSimpleReferenceRow in section.Symbols.OfType<WixSimpleReferenceSymbol>())
67 {
68 // If we're building a Merge Module, ignore all references to the Media table
69 // because Merge Modules don't have Media tables.
70 if (this.BuildingMergeModule && wixSimpleReferenceRow.Table == "Media")
71 {
72 continue;
73 }
74
75 // See if the symbol (and any of its duplicates) are appropriately accessible.
76 if (this.symbolsWithSections.TryGetValue(wixSimpleReferenceRow.SymbolicName, out var symbolWithSection))
77 {
78 var accessible = this.DetermineAccessibleSymbols(section, symbolWithSection);
79 if (accessible.Count == 1)
80 {
81 var accessibleSymbol = accessible[0];
82 if (this.referencedSymbols.Add(accessibleSymbol) && null != accessibleSymbol.Section)
83 {
84 this.RecursivelyResolveReferences(accessibleSymbol.Section);
85 }
86 }
87 else if (accessible.Count == 0)
88 {
89 this.Messaging.Write(ErrorMessages.UnresolvedReference(wixSimpleReferenceRow.SourceLineNumbers, wixSimpleReferenceRow.SymbolicName, symbolWithSection.Access));
90 }
91 else // display errors for the duplicate symbols.
92 {
93 var accessibleSymbol = accessible[0];
94 var referencingSourceLineNumber = wixSimpleReferenceRow.SourceLineNumbers?.ToString();
95
96 if (String.IsNullOrEmpty(referencingSourceLineNumber))
97 {
98 this.Messaging.Write(ErrorMessages.DuplicateSymbol(accessibleSymbol.Symbol.SourceLineNumbers, accessibleSymbol.Name));
99 }
100 else
101 {
102 this.Messaging.Write(ErrorMessages.DuplicateSymbol(accessibleSymbol.Symbol.SourceLineNumbers, accessibleSymbol.Name, referencingSourceLineNumber));
103 }
104
105 foreach (var accessibleDuplicate in accessible.Skip(1))
106 {
107 this.Messaging.Write(ErrorMessages.DuplicateSymbol2(accessibleDuplicate.Symbol.SourceLineNumbers));
108 }
109 }
110 }
111 else
112 {
113 this.Messaging.Write(ErrorMessages.UnresolvedReference(wixSimpleReferenceRow.SourceLineNumbers, wixSimpleReferenceRow.SymbolicName));
114 }
115 }
116 }
117
118 /// <summary>
119 /// Determine if the symbol and any of its duplicates are accessbile by referencing section.
120 /// </summary>
121 /// <param name="referencingSection">Section referencing the symbol.</param>
122 /// <param name="symbolWithSection">Symbol being referenced.</param>
123 /// <returns>List of symbols accessible by referencing section.</returns>
124 private List<SymbolWithSection> DetermineAccessibleSymbols(IntermediateSection referencingSection, SymbolWithSection symbolWithSection)
125 {
126 var accessibleSymbols = new List<SymbolWithSection>();
127
128 if (this.AccessibleSymbol(referencingSection, symbolWithSection))
129 {
130 accessibleSymbols.Add(symbolWithSection);
131 }
132
133 foreach (var dupe in symbolWithSection.PossiblyConflicts)
134 {
135 // don't count overridable WixActionSymbols
136 var symbolAction = symbolWithSection.Symbol as WixActionSymbol;
137 var dupeAction = dupe.Symbol as WixActionSymbol;
138 if (symbolAction?.Overridable != dupeAction?.Overridable)
139 {
140 continue;
141 }
142
143 if (this.AccessibleSymbol(referencingSection, dupe))
144 {
145 accessibleSymbols.Add(dupe);
146 }
147 }
148
149 foreach (var dupe in symbolWithSection.Redundants)
150 {
151 if (this.AccessibleSymbol(referencingSection, dupe))
152 {
153 accessibleSymbols.Add(dupe);
154 }
155 }
156
157 return accessibleSymbols;
158 }
159
160 /// <summary>
161 /// Determine if a single symbol is accessible by the referencing section.
162 /// </summary>
163 /// <param name="referencingSection">Section referencing the symbol.</param>
164 /// <param name="symbolWithSection">Symbol being referenced.</param>
165 /// <returns>True if symbol is accessible.</returns>
166 private bool AccessibleSymbol(IntermediateSection referencingSection, SymbolWithSection symbolWithSection)
167 {
168 switch (symbolWithSection.Access)
169 {
170 case AccessModifier.Global:
171 return true;
172 case AccessModifier.Library:
173 return symbolWithSection.Section.CompilationId == referencingSection.CompilationId || (null != symbolWithSection.Section.LibraryId && symbolWithSection.Section.LibraryId == referencingSection.LibraryId);
174 case AccessModifier.File:
175 return symbolWithSection.Section.CompilationId == referencingSection.CompilationId;
176 case AccessModifier.Section:
177 return referencingSection == symbolWithSection.Section;
178 default:
179 throw new ArgumentOutOfRangeException(nameof(symbolWithSection.Access));
180 }
181 }
182 }
183}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Link
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using WixToolset.Data;
9
10 /// <summary>
11 /// Symbol with section representing a single unique symbol.
12 /// </summary>
13 internal class SymbolWithSection
14 {
15 private HashSet<SymbolWithSection> possibleConflicts;
16 private HashSet<SymbolWithSection> redundants;
17
18 /// <summary>
19 /// Creates a symbol for a symbol.
20 /// </summary>
21 /// <param name="section"></param>
22 /// <param name="symbol">Symbol for the symbol</param>
23 public SymbolWithSection(IntermediateSection section, IntermediateSymbol symbol)
24 {
25 this.Symbol = symbol;
26 this.Section = section;
27 this.Name = String.Concat(this.Symbol.Definition.Name, ":", this.Symbol.Id.Id);
28 }
29
30 /// <summary>
31 /// Gets the accessibility of the symbol which is a direct reflection of the accessibility of the row's accessibility.
32 /// </summary>
33 /// <value>Accessbility of the symbol.</value>
34 public AccessModifier Access => this.Symbol.Id.Access;
35
36 /// <summary>
37 /// Gets the name of the symbol.
38 /// </summary>
39 /// <value>Name of the symbol.</value>
40 public string Name { get; }
41
42 /// <summary>
43 /// Gets the symbol for this symbol.
44 /// </summary>
45 /// <value>Symbol for this symbol.</value>
46 public IntermediateSymbol Symbol { get; }
47
48 /// <summary>
49 /// Gets the section for the symbol.
50 /// </summary>
51 /// <value>Section for the symbol.</value>
52 public IntermediateSection Section { get; }
53
54 /// <summary>
55 /// Gets any duplicates of this symbol with sections that are possible conflicts.
56 /// </summary>
57 public IEnumerable<SymbolWithSection> PossiblyConflicts => this.possibleConflicts ?? Enumerable.Empty<SymbolWithSection>();
58
59 /// <summary>
60 /// Gets any duplicates of this symbol with sections that are redundant.
61 /// </summary>
62 public IEnumerable<SymbolWithSection> Redundants => this.redundants ?? Enumerable.Empty<SymbolWithSection>();
63
64 /// <summary>
65 /// Adds a duplicate symbol with sections that is a possible conflict.
66 /// </summary>
67 /// <param name="symbolWithSection">Symbol with section that is a possible conflict of this symbol.</param>
68 public void AddPossibleConflict(SymbolWithSection symbolWithSection)
69 {
70 if (null == this.possibleConflicts)
71 {
72 this.possibleConflicts = new HashSet<SymbolWithSection>();
73 }
74
75 this.possibleConflicts.Add(symbolWithSection);
76 }
77
78 /// <summary>
79 /// Adds a duplicate symbol that is redundant.
80 /// </summary>
81 /// <param name="symbolWithSection">Symbol with section that is redundant of this symbol.</param>
82 public void AddRedundant(SymbolWithSection symbolWithSection)
83 {
84 if (null == this.redundants)
85 {
86 this.redundants = new HashSet<SymbolWithSection>();
87 }
88
89 this.redundants.Add(symbolWithSection);
90 }
91 }
92}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Link
4{
5 using System;
6 using WixToolset.Data.Symbols;
7
8 internal static class WixComplexReferenceSymbolExtensions
9 {
10 /// <summary>
11 /// Creates a shallow copy of the ComplexReference.
12 /// </summary>
13 /// <returns>A shallow copy of the ComplexReference.</returns>
14 public static WixComplexReferenceSymbol Clone(this WixComplexReferenceSymbol source)
15 {
16 var clone = new WixComplexReferenceSymbol(source.SourceLineNumbers, source.Id);
17 clone.ParentType = source.ParentType;
18 clone.Parent = source.Parent;
19 clone.ParentLanguage = source.ParentLanguage;
20 clone.ChildType = source.ChildType;
21 clone.Child = source.Child;
22 clone.IsPrimary = source.IsPrimary;
23
24 return clone;
25 }
26
27 /// <summary>
28 /// Compares two complex references without considering the primary bit.
29 /// </summary>
30 /// <param name="symbol">this</param>
31 /// <param name="other">Complex reference to compare to.</param>
32 /// <returns>Zero if the objects are equivalent, negative number if the provided object is less, positive if greater.</returns>
33 public static int CompareToWithoutConsideringPrimary(this WixComplexReferenceSymbol symbol, WixComplexReferenceSymbol other)
34 {
35 var comparison = symbol.ChildType - other.ChildType;
36 if (0 == comparison)
37 {
38 comparison = String.Compare(symbol.Child, other.Child, StringComparison.Ordinal);
39 if (0 == comparison)
40 {
41 comparison = symbol.ParentType - other.ParentType;
42 if (0 == comparison)
43 {
44 string thisParentLanguage = null == symbol.ParentLanguage ? String.Empty : symbol.ParentLanguage;
45 string otherParentLanguage = null == other.ParentLanguage ? String.Empty : other.ParentLanguage;
46 comparison = String.Compare(thisParentLanguage, otherParentLanguage, StringComparison.Ordinal);
47 if (0 == comparison)
48 {
49 comparison = String.Compare(symbol.Parent, other.Parent, StringComparison.Ordinal);
50 }
51 }
52 }
53 }
54
55 return comparison;
56 }
57
58 /// <summary>
59 /// Changes all of the parent references to point to the passed in parent reference.
60 /// </summary>
61 /// <param name="symbol">this</param>
62 /// <param name="parent">New parent complex reference.</param>
63 public static void Reparent(this WixComplexReferenceSymbol symbol, WixComplexReferenceSymbol parent)
64 {
65 symbol.Parent = parent.Parent;
66 symbol.ParentLanguage = parent.ParentLanguage;
67 symbol.ParentType = parent.ParentType;
68
69 if (!symbol.IsPrimary)
70 {
71 symbol.IsPrimary = parent.IsPrimary;
72 }
73 }
74 }
75}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Link
4{
5 using System;
6 using System.Collections.ObjectModel;
7 using System.Collections.Generic;
8 using System.Diagnostics;
9 using System.Globalization;
10 using System.Linq;
11 using System.Text;
12 using WixToolset.Data;
13 using WixToolset.Data.Symbols;
14 using WixToolset.Extensibility.Services;
15 using WixToolset.Data.Burn;
16
17 /// <summary>
18 /// Grouping and Ordering class of the WiX toolset.
19 /// </summary>
20 internal class WixGroupingOrdering
21 {
22 private readonly IMessaging Messaging;
23 private List<string> groupTypes;
24 private List<string> itemTypes;
25 private ItemCollection items;
26 private readonly List<IntermediateSymbol> symbolsUsed;
27 private bool loaded;
28
29 /// <summary>
30 /// Creates a WixGroupingOrdering object.
31 /// </summary>
32 /// <param name="entrySections">Output from which to read the group and order information.</param>
33 /// <param name="messageHandler">Handler for any error messages.</param>
34 public WixGroupingOrdering(IntermediateSection entrySections, IMessaging messageHandler)
35 {
36 this.EntrySection = entrySections;
37 this.Messaging = messageHandler;
38
39 this.symbolsUsed = new List<IntermediateSymbol>();
40 this.loaded = false;
41 }
42
43 private IntermediateSection EntrySection { get; }
44
45 /// <summary>
46 /// Switches a WixGroupingOrdering object to operate on a new set of groups/items.
47 /// </summary>
48 /// <param name="groupTypes">Group types to include.</param>
49 /// <param name="itemTypes">Item types to include.</param>
50 public void UseTypes(IEnumerable<ComplexReferenceParentType> groupTypes, IEnumerable<ComplexReferenceChildType> itemTypes)
51 {
52 this.groupTypes = new List<string>(groupTypes.Select(g => g.ToString()));
53 this.itemTypes = new List<string>(itemTypes.Select(i => i.ToString()));
54
55 this.items = new ItemCollection();
56 this.loaded = false;
57 }
58
59 /// <summary>
60 /// Finds all nested items under a parent group and creates new WixGroup data for them.
61 /// </summary>
62 /// <param name="parentType">The group type for the parent group to flatten.</param>
63 /// <param name="parentId">The identifier of the parent group to flatten.</param>
64 /// <param name="removeUsedRows">Whether to remove used group rows before returning.</param>
65 public void FlattenAndRewriteRows(ComplexReferenceParentType parentType, string parentId, bool removeUsedRows)
66 {
67 var parentTypeString = parentType.ToString();
68 Debug.Assert(this.groupTypes.Contains(parentTypeString));
69
70 this.CreateOrderedList(parentTypeString, parentId, out var orderedItems);
71 if (this.Messaging.EncounteredError)
72 {
73 return;
74 }
75
76 this.CreateNewGroupRows(parentTypeString, parentId, orderedItems);
77
78 if (removeUsedRows)
79 {
80 this.RemoveUsedGroupRows();
81 }
82 }
83
84 /// <summary>
85 /// Finds all items under a parent group type and creates new WixGroup data for them.
86 /// </summary>
87 /// <param name="parentType">The type of the parent group to flatten.</param>
88 /// <param name="removeUsedRows">Whether to remove used group rows before returning.</param>
89 public void FlattenAndRewriteGroups(ComplexReferenceParentType parentType, bool removeUsedRows)
90 {
91 var parentTypeString = parentType.ToString();
92 Debug.Assert(this.groupTypes.Contains(parentTypeString));
93
94 this.LoadFlattenOrderGroups();
95 if (this.Messaging.EncounteredError)
96 {
97 return;
98 }
99
100 foreach (Item item in this.items)
101 {
102 if (parentTypeString == item.Type)
103 {
104 this.CreateOrderedList(item.Type, item.Id, out var orderedItems);
105 this.CreateNewGroupRows(item.Type, item.Id, orderedItems);
106 }
107 }
108
109 if (removeUsedRows)
110 {
111 this.RemoveUsedGroupRows();
112 }
113 }
114
115
116 /// <summary>
117 /// Creates a flattened and ordered list of items for the given parent group.
118 /// </summary>
119 /// <param name="parentType">The group type for the parent group to flatten.</param>
120 /// <param name="parentId">The identifier of the parent group to flatten.</param>
121 /// <param name="orderedItems">The returned list of ordered items.</param>
122 private void CreateOrderedList(string parentType, string parentId, out List<Item> orderedItems)
123 {
124 orderedItems = null;
125
126 this.LoadFlattenOrderGroups();
127 if (this.Messaging.EncounteredError)
128 {
129 return;
130 }
131
132 if (!this.items.TryGetValue(parentType, parentId, out var parentItem))
133 {
134 this.Messaging.Write(ErrorMessages.IdentifierNotFound(parentType, parentId));
135 return;
136 }
137
138 orderedItems = new List<Item>(parentItem.ChildItems);
139 orderedItems.Sort(new Item.AfterItemComparer());
140 }
141
142 /// <summary>
143 /// Removes rows from WixGroup that have been used by this object.
144 /// </summary>
145 public void RemoveUsedGroupRows()
146 {
147 foreach (var symbol in this.symbolsUsed)
148 {
149 this.EntrySection.RemoveSymbol(symbol);
150 }
151 }
152
153 /// <summary>
154 /// Creates new WixGroup rows for a list of items.
155 /// </summary>
156 /// <param name="parentType">The group type for the parent group in the new rows.</param>
157 /// <param name="parentId">The identifier of the parent group in the new rows.</param>
158 /// <param name="orderedItems">The list of new items.</param>
159 private void CreateNewGroupRows(string parentType, string parentId, List<Item> orderedItems)
160 {
161 // TODO: MSIs don't guarantee that rows stay in the same order, and technically, neither
162 // does WiX (although they do, currently). We probably want to "upgrade" this to a new
163 // table that includes a sequence number, and then change the code that uses ordered
164 // groups to read from that table instead.
165 foreach (var item in orderedItems)
166 {
167 this.EntrySection.AddSymbol(new WixGroupSymbol(item.Row.SourceLineNumbers)
168 {
169 ParentId = parentId,
170 ParentType = (ComplexReferenceParentType)Enum.Parse(typeof(ComplexReferenceParentType), parentType),
171 ChildId = item.Id,
172 ChildType = (ComplexReferenceChildType)Enum.Parse(typeof(ComplexReferenceChildType), item.Type),
173 });
174 }
175 }
176
177 // Group/Ordering Flattening Logic
178 //
179 // What follows is potentially convoluted logic. Two somewhat orthogonal concepts are in
180 // play: grouping (parent/child relationships) and ordering (before/after relationships).
181 // Dealing with just one or the other is straghtforward. Groups can be flattened
182 // recursively. Ordering can be propagated in either direction. When the ordering also
183 // participates in the grouping constructions, however, things get trickier. For the
184 // purposes of this discussion, we're dealing with "items" and "groups", and an instance
185 // of either of them can be marked as coming "after" some other instance.
186 //
187 // For simple item-to-item ordering, the "after" values simply propagate: if A is after B,
188 // and B is after C, then we can say that A is after *both* B and C. If a group is involved,
189 // it acts as a proxy for all of its included items and any sub-groups.
190
191 /// <summary>
192 /// Internal workhorse for ensuring that group and ordering information has
193 /// been loaded and applied.
194 /// </summary>
195 private void LoadFlattenOrderGroups()
196 {
197 if (!this.loaded)
198 {
199 this.LoadGroups();
200 this.LoadOrdering();
201
202 // It would be really nice to have a "find circular after dependencies"
203 // function, but it gets much more complicated because of the way that
204 // the dependencies are propagated across group boundaries. For now, we
205 // just live with the dependency loop detection as we flatten the
206 // dependencies. Group references, however, we can check directly.
207 this.FindCircularGroupReferences();
208
209 if (!this.Messaging.EncounteredError)
210 {
211 this.FlattenGroups();
212 this.FlattenOrdering();
213 }
214
215 this.loaded = true;
216 }
217 }
218
219 /// <summary>
220 /// Loads data from the WixGroup table.
221 /// </summary>
222 private void LoadGroups()
223 {
224 //Table wixGroupTable = this.output.Tables["WixGroup"];
225 //if (null == wixGroupTable || 0 == wixGroupTable.Rows.Count)
226 //{
227 // // TODO: Change message name to make it *not* Bundle specific?
228 // this.Write(WixErrors.MissingBundleInformation("WixGroup"));
229 //}
230
231 // Collect all of the groups
232 foreach (var symbol in this.EntrySection.Symbols.OfType<WixGroupSymbol>())
233 {
234 var rowParentName = symbol.ParentId;
235 var rowParentType = symbol.ParentType.ToString();
236 var rowChildName = symbol.ChildId;
237 var rowChildType = symbol.ChildType.ToString();
238
239 // If this row specifies a parent or child type that's not in our
240 // lists, we assume it's not a row that we're concerned about.
241 if (!this.groupTypes.Contains(rowParentType) ||
242 !this.itemTypes.Contains(rowChildType))
243 {
244 continue;
245 }
246
247 this.symbolsUsed.Add(symbol);
248
249 if (!this.items.TryGetValue(rowParentType, rowParentName, out var parentItem))
250 {
251 parentItem = new Item(symbol, rowParentType, rowParentName);
252 this.items.Add(parentItem);
253 }
254
255 if (!this.items.TryGetValue(rowChildType, rowChildName, out var childItem))
256 {
257 childItem = new Item(symbol, rowChildType, rowChildName);
258 this.items.Add(childItem);
259 }
260
261 parentItem.ChildItems.Add(childItem);
262 }
263 }
264
265 /// <summary>
266 /// Flattens group/item information.
267 /// </summary>
268 private void FlattenGroups()
269 {
270 foreach (Item item in this.items)
271 {
272 item.FlattenChildItems();
273 }
274 }
275
276 /// <summary>
277 /// Finds and reports circular references in the group/item data.
278 /// </summary>
279 private void FindCircularGroupReferences()
280 {
281 ItemCollection itemsInKnownLoops = new ItemCollection();
282 foreach (Item item in this.items)
283 {
284 if (itemsInKnownLoops.Contains(item))
285 {
286 continue;
287 }
288
289 ItemCollection itemsSeen = new ItemCollection();
290 string circularReference;
291 if (this.FindCircularGroupReference(item, item, itemsSeen, out circularReference))
292 {
293 itemsInKnownLoops.Add(itemsSeen);
294 this.Messaging.Write(ErrorMessages.ReferenceLoopDetected(item.Row.SourceLineNumbers, circularReference));
295 }
296 }
297 }
298
299 /// <summary>
300 /// Recursive worker to find and report circular references in group/item data.
301 /// </summary>
302 /// <param name="checkItem">The sentinal item being checked.</param>
303 /// <param name="currentItem">The current item in the recursion.</param>
304 /// <param name="itemsSeen">A list of all items already visited (for performance).</param>
305 /// <param name="circularReference">A list of items in the current circular reference, if one was found; null otherwise.</param>
306 /// <returns>True if a circular reference was found; false otherwise.</returns>
307 private bool FindCircularGroupReference(Item checkItem, Item currentItem, ItemCollection itemsSeen, out string circularReference)
308 {
309 circularReference = null;
310 foreach (Item subitem in currentItem.ChildItems)
311 {
312 if (checkItem == subitem)
313 {
314 // TODO: Even better would be to include the source lines for each reference!
315 circularReference = String.Format(CultureInfo.InvariantCulture, "{0}:{1} -> {2}:{3}",
316 currentItem.Type, currentItem.Id, subitem.Type, subitem.Id);
317 return true;
318 }
319
320 if (!itemsSeen.Contains(subitem))
321 {
322 itemsSeen.Add(subitem);
323 if (this.FindCircularGroupReference(checkItem, subitem, itemsSeen, out circularReference))
324 {
325 // TODO: Even better would be to include the source lines for each reference!
326 circularReference = String.Format(CultureInfo.InvariantCulture, "{0}:{1} -> {2}",
327 currentItem.Type, currentItem.Id, circularReference);
328 return true;
329 }
330 }
331 }
332
333 return false;
334 }
335
336 /// <summary>
337 /// Loads ordering dependency data from the WixOrdering table.
338 /// </summary>
339 private void LoadOrdering()
340 {
341 //Table wixOrderingTable = output.Tables["WixOrdering"];
342 //if (null == wixOrderingTable || 0 == wixOrderingTable.Rows.Count)
343 //{
344 // // TODO: Do we need a message here?
345 // return;
346 //}
347
348 foreach (var row in this.EntrySection.Symbols.OfType<WixOrderingSymbol>())
349 {
350 var rowItemType = row.ItemType.ToString();
351 var rowItemName = row.ItemIdRef;
352 var rowDependsOnType = row.DependsOnType.ToString();
353 var rowDependsOnName = row.DependsOnIdRef;
354
355 // If this row specifies some other (unknown) type in either
356 // position, we assume it's not a row that we're concerned about.
357 // For ordering, we allow group and item in either position.
358 if (!(this.groupTypes.Contains(rowItemType) || this.itemTypes.Contains(rowItemType)) ||
359 !(this.groupTypes.Contains(rowDependsOnType) || this.itemTypes.Contains(rowDependsOnType)))
360 {
361 continue;
362 }
363
364 if (!this.items.TryGetValue(rowItemType, rowItemName, out var item))
365 {
366 this.Messaging.Write(ErrorMessages.IdentifierNotFound(rowItemType, rowItemName));
367 }
368
369 if (!this.items.TryGetValue(rowDependsOnType, rowDependsOnName, out var dependsOn))
370 {
371 this.Messaging.Write(ErrorMessages.IdentifierNotFound(rowDependsOnType, rowDependsOnName));
372 }
373
374 if (null == item || null == dependsOn)
375 {
376 continue;
377 }
378
379 item.AddAfter(dependsOn, this.Messaging);
380 }
381 }
382
383 /// <summary>
384 /// Flattens the ordering dependencies in the groups/items.
385 /// </summary>
386 private void FlattenOrdering()
387 {
388 // Because items don't know about their parent groups (and can, in fact, be
389 // in more than one group at a time), we need to pre-propagate the 'afters'
390 // from each parent item to its children before we attempt to flatten the
391 // ordering.
392 foreach (Item item in this.items)
393 {
394 item.PropagateAfterToChildItems(this.Messaging);
395 }
396
397 foreach (Item item in this.items)
398 {
399 item.FlattenAfters(this.Messaging);
400 }
401 }
402
403 /// <summary>
404 /// A variant of KeyedCollection that doesn't throw when an item is re-added.
405 /// </summary>
406 /// <typeparam name="TKey">Key type for the collection.</typeparam>
407 /// <typeparam name="TItem">Item type for the colelction.</typeparam>
408 internal abstract class EnhancedKeyCollection<TKey, TItem> : KeyedCollection<TKey, TItem>
409 {
410 new public void Add(TItem item)
411 {
412 if (!this.Contains(item))
413 {
414 base.Add(item);
415 }
416 }
417
418 public void Add(Collection<TItem> list)
419 {
420 foreach (TItem item in list)
421 {
422 this.Add(item);
423 }
424 }
425
426 public void Remove(Collection<TItem> list)
427 {
428 foreach (TItem item in list)
429 {
430 this.Remove(item);
431 }
432 }
433
434 public bool TryGetValue(TKey key, out TItem item)
435 {
436 // KeyedCollection doesn't implement the TryGetValue() method, but it's
437 // a useful concept. We can't just always pass this to the enclosed
438 // Dictionary, however, because it doesn't always exist! If it does, we
439 // can delegate to it as one would expect. If it doesn't, we have to
440 // implement everything ourselves in terms of Contains().
441
442 if (null != this.Dictionary)
443 {
444 return this.Dictionary.TryGetValue(key, out item);
445 }
446
447 if (this.Contains(key))
448 {
449 item = this[key];
450 return true;
451 }
452
453 item = default(TItem);
454 return false;
455 }
456
457#if DEBUG
458 // This just makes debugging easier...
459 public override string ToString()
460 {
461 StringBuilder sb = new StringBuilder();
462 foreach (TItem item in this)
463 {
464 sb.AppendFormat("{0}, ", item);
465 }
466 sb.Length -= 2;
467 return sb.ToString();
468 }
469#endif // DEBUG
470 }
471
472 /// <summary>
473 /// A specialized EnhancedKeyCollection, typed to Items.
474 /// </summary>
475 internal class ItemCollection : EnhancedKeyCollection<string, Item>
476 {
477 protected override string GetKeyForItem(Item item)
478 {
479 return item.Key;
480 }
481
482 public bool TryGetValue(string type, string id, out Item item)
483 {
484 return this.TryGetValue(CreateKeyFromTypeId(type, id), out item);
485 }
486
487 public static string CreateKeyFromTypeId(string type, string id)
488 {
489 return String.Format(CultureInfo.InvariantCulture, "{0}_{1}", type, id);
490 }
491 }
492
493 /// <summary>
494 /// An item (or group) in the grouping/ordering engine.
495 /// </summary>
496 /// <remarks>Encapsulates nested group membership and also before/after
497 /// ordering dependencies.</remarks>
498 internal class Item
499 {
500 private readonly ItemCollection afterItems;
501 private readonly ItemCollection beforeItems; // for checking for circular references
502 private bool flattenedAfterItems;
503
504 public Item(IntermediateSymbol row, string type, string id)
505 {
506 this.Row = row;
507 this.Type = type;
508 this.Id = id;
509
510 this.Key = ItemCollection.CreateKeyFromTypeId(type, id);
511
512 this.afterItems = new ItemCollection();
513 this.beforeItems = new ItemCollection();
514 this.flattenedAfterItems = false;
515 }
516
517 public IntermediateSymbol Row { get; private set; }
518 public string Type { get; private set; }
519 public string Id { get; private set; }
520 public string Key { get; private set; }
521
522#if DEBUG
523 // Makes debugging easier...
524 public override string ToString()
525 {
526 return this.Key;
527 }
528#endif // DEBUG
529
530 public ItemCollection ChildItems { get; } = new ItemCollection();
531
532 /// <summary>
533 /// Removes any nested groups under this item and replaces
534 /// them with their child items.
535 /// </summary>
536 public void FlattenChildItems()
537 {
538 ItemCollection flattenedChildItems = new ItemCollection();
539
540 foreach (Item childItem in this.ChildItems)
541 {
542 if (0 == childItem.ChildItems.Count)
543 {
544 flattenedChildItems.Add(childItem);
545 }
546 else
547 {
548 childItem.FlattenChildItems();
549 flattenedChildItems.Add(childItem.ChildItems);
550 }
551 }
552
553 this.ChildItems.Clear();
554 this.ChildItems.Add(flattenedChildItems);
555 }
556
557 /// <summary>
558 /// Adds a list of items to the 'after' ordering collection.
559 /// </summary>
560 /// <param name="items">List of items to add.</param>
561 /// <param name="messageHandler">Message handler in case a circular ordering reference is found.</param>
562 public void AddAfter(ItemCollection items, IMessaging messageHandler)
563 {
564 foreach (Item item in items)
565 {
566 this.AddAfter(item, messageHandler);
567 }
568 }
569
570 /// <summary>
571 /// Adds an item to the 'after' ordering collection.
572 /// </summary>
573 /// <param name="after">Item to add.</param>
574 /// <param name="messageHandler">Message handler in case a circular ordering reference is found.</param>
575 public void AddAfter(Item after, IMessaging messageHandler)
576 {
577 if (this.beforeItems.Contains(after))
578 {
579 // We could try to chain this up (the way that group circular dependencies
580 // are reported), but since we're in the process of flattening, we may already
581 // have lost some distinction between authored and propagated ordering.
582 string circularReference = String.Format(CultureInfo.InvariantCulture, "{0}:{1} -> {2}:{3} -> {0}:{1}",
583 this.Type, this.Id, after.Type, after.Id);
584 messageHandler.Write(ErrorMessages.OrderingReferenceLoopDetected(after.Row.SourceLineNumbers, circularReference));
585 return;
586 }
587
588 this.afterItems.Add(after);
589 after.beforeItems.Add(this);
590 }
591
592 /// <summary>
593 /// Propagates 'after' dependencies from an item to its child items.
594 /// </summary>
595 /// <param name="messageHandler">Message handler in case a circular ordering reference is found.</param>
596 /// <remarks>Because items don't know about their parent groups (and can, in fact, be in more
597 /// than one group at a time), we need to propagate the 'afters' from each parent item to its children
598 /// before we attempt to flatten the ordering.</remarks>
599 public void PropagateAfterToChildItems(IMessaging messageHandler)
600 {
601 if (this.ShouldItemPropagateChildOrdering())
602 {
603 foreach (Item childItem in this.ChildItems)
604 {
605 childItem.AddAfter(this.afterItems, messageHandler);
606 }
607 }
608 }
609
610 /// <summary>
611 /// Flattens the ordering dependency for this item.
612 /// </summary>
613 /// <param name="messageHandler">Message handler in case a circular ordering reference is found.</param>
614 public void FlattenAfters(IMessaging messageHandler)
615 {
616 if (this.flattenedAfterItems)
617 {
618 return;
619 }
620
621 this.flattenedAfterItems = true;
622
623 // Ensure that if we're after something (A), and *it's* after something (B),
624 // that we list ourselved as after both (A) *and* (B).
625 ItemCollection nestedAfterItems = new ItemCollection();
626
627 foreach (Item afterItem in this.afterItems)
628 {
629 afterItem.FlattenAfters(messageHandler);
630 nestedAfterItems.Add(afterItem.afterItems);
631
632 if (afterItem.ShouldItemPropagateChildOrdering())
633 {
634 // If we are after a group, it really means
635 // we are after all of the group's children.
636 foreach (Item childItem in afterItem.ChildItems)
637 {
638 childItem.FlattenAfters(messageHandler);
639 nestedAfterItems.Add(childItem.afterItems);
640 nestedAfterItems.Add(childItem);
641 }
642 }
643 }
644
645 this.AddAfter(nestedAfterItems, messageHandler);
646 }
647
648 // We *don't* propagate ordering information from Packages or
649 // Containers to their children, because ordering doesn't matter
650 // for them, and a Payload in two Packages (or Containers) can
651 // cause a circular reference to occur.
652 private bool ShouldItemPropagateChildOrdering()
653 {
654 if (String.Equals(nameof(ComplexReferenceParentType.Package), this.Type, StringComparison.Ordinal) ||
655 String.Equals(nameof(ComplexReferenceParentType.Container), this.Type, StringComparison.Ordinal))
656 {
657 return false;
658 }
659 return true;
660 }
661
662 /// <summary>
663 /// Helper IComparer class to make ordering easier.
664 /// </summary>
665 internal class AfterItemComparer : IComparer<Item>
666 {
667 public int Compare(Item x, Item y)
668 {
669 if (x.afterItems.Contains(y))
670 {
671 return 1;
672 }
673 else if (y.afterItems.Contains(x))
674 {
675 return -1;
676 }
677
678 return String.CompareOrdinal(x.Id, y.Id);
679 }
680 }
681 }
682 }
683}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Threading;
8 using WixToolset.Data;
9 using WixToolset.Extensibility;
10 using WixToolset.Extensibility.Data;
11
12 internal class LinkContext : ILinkContext
13 {
14 internal LinkContext(IServiceProvider serviceProvider)
15 {
16 this.ServiceProvider = serviceProvider;
17 }
18
19 public IServiceProvider ServiceProvider { get; }
20
21 public IReadOnlyCollection<ILinkerExtension> Extensions { get; set; }
22
23 public IReadOnlyCollection<IExtensionData> ExtensionData { get; set; }
24
25 public OutputType ExpectedOutputType { get; set; }
26
27 public IReadOnlyCollection<Intermediate> Intermediates { get; set; }
28
29 public ISymbolDefinitionCreator SymbolDefinitionCreator { get; set; }
30
31 public CancellationToken CancellationToken { get; set; }
32 }
33}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System;
6 using System.Collections;
7 using System.Collections.Generic;
8 using System.Diagnostics;
9 using System.Globalization;
10 using System.Linq;
11 using WixToolset.Core.Link;
12 using WixToolset.Data;
13 using WixToolset.Data.Symbols;
14 using WixToolset.Data.WindowsInstaller;
15 using WixToolset.Extensibility.Data;
16 using WixToolset.Extensibility.Services;
17
18 /// <summary>
19 /// Linker core of the WiX toolset.
20 /// </summary>
21 internal class Linker : ILinker
22 {
23 private static readonly string EmptyGuid = Guid.Empty.ToString("B");
24
25 private readonly bool sectionIdOnRows;
26
27 /// <summary>
28 /// Creates a linker.
29 /// </summary>
30 internal Linker(IServiceProvider serviceProvider)
31 {
32 this.ServiceProvider = serviceProvider;
33 this.Messaging = this.ServiceProvider.GetService<IMessaging>();
34 this.sectionIdOnRows = true; // TODO: what is the correct value for this?
35 }
36
37 private IServiceProvider ServiceProvider { get; }
38
39 private IMessaging Messaging { get; }
40
41 private ILinkContext Context { get; set; }
42
43 /// <summary>
44 /// Gets or sets the path to output unreferenced symbols to. If null or empty, there is no output.
45 /// </summary>
46 /// <value>The path to output the xml file.</value>
47 public string UnreferencedSymbolsFile { get; set; }
48
49 /// <summary>
50 /// Gets or sets the option to show pedantic messages.
51 /// </summary>
52 /// <value>The option to show pedantic messages.</value>
53 public bool ShowPedanticMessages { get; set; }
54
55 /// <summary>
56 /// Links a collection of sections into an output.
57 /// </summary>
58 /// <returns>Output intermediate from the linking.</returns>
59 public Intermediate Link(ILinkContext context)
60 {
61 this.Context = context;
62
63 if (this.Context.SymbolDefinitionCreator == null)
64 {
65 this.Context.SymbolDefinitionCreator = this.ServiceProvider.GetService<ISymbolDefinitionCreator>();
66 }
67
68 foreach (var extension in this.Context.Extensions)
69 {
70 extension.PreLink(this.Context);
71 }
72
73 var invalidIntermediates = this.Context.Intermediates.Where(i => !i.HasLevel(Data.IntermediateLevels.Compiled));
74 if (invalidIntermediates.Any())
75 {
76 this.Messaging.Write(ErrorMessages.IntermediatesMustBeCompiled(String.Join(", ", invalidIntermediates.Select(i => i.Id))));
77 }
78
79 Intermediate intermediate = null;
80 try
81 {
82 var sections = this.Context.Intermediates.SelectMany(i => i.Sections).ToList();
83 var localizations = this.Context.Intermediates.SelectMany(i => i.Localizations).ToList();
84
85 // Add sections from the extensions with data.
86 foreach (var data in this.Context.ExtensionData)
87 {
88 var library = data.GetLibrary(this.Context.SymbolDefinitionCreator);
89
90 if (library != null)
91 {
92 sections.AddRange(library.Sections);
93 }
94 }
95
96 //this.activeOutput = null;
97
98 var multipleFeatureComponents = new Hashtable();
99
100 var wixVariables = new Dictionary<string, WixVariableSymbol>();
101
102 // First find the entry section and while processing all sections load all the symbols from all of the sections.
103 var find = new FindEntrySectionAndLoadSymbolsCommand(this.Messaging, sections, this.Context.ExpectedOutputType);
104 find.Execute();
105
106 // Must have found the entry section by now.
107 if (null == find.EntrySection)
108 {
109 if (this.Context.ExpectedOutputType == OutputType.IntermediatePostLink || this.Context.ExpectedOutputType == OutputType.Unknown)
110 {
111 throw new WixException(ErrorMessages.MissingEntrySection());
112 }
113 else
114 {
115 throw new WixException(ErrorMessages.MissingEntrySection(this.Context.ExpectedOutputType.ToString()));
116 }
117 }
118
119 // Add the missing standard action and directory symbols.
120 this.LoadStandardSymbols(find.SymbolsByName);
121
122 // Resolve the symbol references to find the set of sections we care about for linking.
123 // Of course, we start with the entry section (that's how it got its name after all).
124 var resolve = new ResolveReferencesCommand(this.Messaging, find.EntrySection, find.SymbolsByName);
125 resolve.Execute();
126
127 if (this.Messaging.EncounteredError)
128 {
129 return null;
130 }
131
132 // Reset the sections to only those that were resolved then flatten the complex
133 // references that particpate in groups.
134 sections = resolve.ResolvedSections.ToList();
135
136 // TODO: consider filtering "localizations" down to only those localizations from
137 // intermediates in the sections.
138
139 this.FlattenSectionsComplexReferences(sections);
140
141 if (this.Messaging.EncounteredError)
142 {
143 return null;
144 }
145
146 // The hard part in linking is processing the complex references.
147 var referencedComponents = new HashSet<string>();
148 var componentsToFeatures = new ConnectToFeatureCollection();
149 var featuresToFeatures = new ConnectToFeatureCollection();
150 var modulesToFeatures = new ConnectToFeatureCollection();
151 this.ProcessComplexReferences(find.EntrySection, sections, referencedComponents, componentsToFeatures, featuresToFeatures, modulesToFeatures);
152
153 if (this.Messaging.EncounteredError)
154 {
155 return null;
156 }
157
158 // Display an error message for Components that were not referenced by a Feature.
159 foreach (var symbolWithSection in resolve.ReferencedSymbolWithSections.Where(s => s.Symbol.Definition.Type == SymbolDefinitionType.Component))
160 {
161 if (!referencedComponents.Contains(symbolWithSection.Name))
162 {
163 this.Messaging.Write(ErrorMessages.OrphanedComponent(symbolWithSection.Symbol.SourceLineNumbers, symbolWithSection.Symbol.Id.Id));
164 }
165 }
166
167 // Report duplicates that would ultimately end up being primary key collisions.
168 {
169 var reportDupes = new ReportConflictingSymbolsCommand(this.Messaging, find.PossibleConflicts, resolve.ResolvedSections);
170 reportDupes.Execute();
171 }
172
173 if (this.Messaging.EncounteredError)
174 {
175 return null;
176 }
177
178 // resolve the feature to feature connects
179 this.ResolveFeatureToFeatureConnects(featuresToFeatures, find.SymbolsByName);
180
181 // Create a new section to hold the linked content. Start with the entry section's
182 // metadata.
183 var resolvedSection = new IntermediateSection(find.EntrySection.Id, find.EntrySection.Type);
184
185 var sectionCount = 0;
186
187 foreach (var section in sections)
188 {
189 sectionCount++;
190
191 var sectionId = section.Id;
192 if (null == sectionId && this.sectionIdOnRows)
193 {
194 sectionId = "wix.section." + sectionCount.ToString(CultureInfo.InvariantCulture);
195 }
196
197 foreach (var symbol in section.Symbols)
198 {
199 if (find.RedundantSymbols.Contains(symbol))
200 {
201 continue;
202 }
203
204 var copySymbol = true; // by default, copy symbols.
205
206 // handle special tables
207 switch (symbol.Definition.Type)
208 {
209 case SymbolDefinitionType.Class:
210 if (SectionType.Product == resolvedSection.Type)
211 {
212 this.ResolveFeatures(symbol, (int)ClassSymbolFields.ComponentRef, (int)ClassSymbolFields.FeatureRef, componentsToFeatures, multipleFeatureComponents);
213 }
214 break;
215
216 case SymbolDefinitionType.Extension:
217 if (SectionType.Product == resolvedSection.Type)
218 {
219 this.ResolveFeatures(symbol, (int)ExtensionSymbolFields.ComponentRef, (int)ExtensionSymbolFields.FeatureRef, componentsToFeatures, multipleFeatureComponents);
220 }
221 break;
222
223 case SymbolDefinitionType.Assembly:
224 if (SectionType.Product == resolvedSection.Type)
225 {
226 this.ResolveFeatures(symbol, (int)AssemblySymbolFields.ComponentRef, (int)AssemblySymbolFields.FeatureRef, componentsToFeatures, multipleFeatureComponents);
227 }
228 break;
229
230 case SymbolDefinitionType.PublishComponent:
231 if (SectionType.Product == resolvedSection.Type)
232 {
233 this.ResolveFeatures(symbol, (int)PublishComponentSymbolFields.ComponentRef, (int)PublishComponentSymbolFields.FeatureRef, componentsToFeatures, multipleFeatureComponents);
234 }
235 break;
236
237 case SymbolDefinitionType.Shortcut:
238 if (SectionType.Product == resolvedSection.Type)
239 {
240 this.ResolveFeatures(symbol, (int)ShortcutSymbolFields.ComponentRef, (int)ShortcutSymbolFields.Target, componentsToFeatures, multipleFeatureComponents);
241 }
242 break;
243
244 case SymbolDefinitionType.TypeLib:
245 if (SectionType.Product == resolvedSection.Type)
246 {
247 this.ResolveFeatures(symbol, (int)TypeLibSymbolFields.ComponentRef, (int)TypeLibSymbolFields.FeatureRef, componentsToFeatures, multipleFeatureComponents);
248 }
249 break;
250
251 case SymbolDefinitionType.WixMerge:
252 if (SectionType.Product == resolvedSection.Type)
253 {
254 this.ResolveFeatures(symbol, -1, (int)WixMergeSymbolFields.FeatureRef, modulesToFeatures, null);
255 }
256 break;
257
258 case SymbolDefinitionType.WixComplexReference:
259 copySymbol = false;
260 break;
261
262 case SymbolDefinitionType.WixSimpleReference:
263 copySymbol = false;
264 break;
265
266 case SymbolDefinitionType.WixVariable:
267 this.AddWixVariable(wixVariables, (WixVariableSymbol)symbol);
268 copySymbol = false; // Do not copy the symbol, it will be added later after all overriding has been handled.
269 break;
270 }
271
272 if (copySymbol)
273 {
274 resolvedSection.AddSymbol(symbol);
275 }
276 }
277 }
278
279 // Copy the module to feature connections into the output.
280 foreach (ConnectToFeature connectToFeature in modulesToFeatures)
281 {
282 foreach (var feature in connectToFeature.ConnectFeatures)
283 {
284 resolvedSection.AddSymbol(new WixFeatureModulesSymbol
285 {
286 FeatureRef = feature,
287 WixMergeRef = connectToFeature.ChildId
288 });
289 }
290 }
291
292 // Correct the section Id in FeatureComponents table.
293 if (this.sectionIdOnRows)
294 {
295#if TODO_DO_SYMBOLS_NEED_SECTIONIDS
296 var componentSectionIds = resolvedSection.Symbols.OfType<ComponentSymbol>().ToDictionary(c => c.Id.Id, c => c.SectionId);
297
298 foreach (var featureComponentSymbol in resolvedSection.Symbols.OfType<FeatureComponentsSymbol>())
299 {
300 if (componentSectionIds.TryGetValue(featureComponentSymbol.ComponentRef, out var componentSectionId))
301 {
302 featureComponentSymbol.SectionId = componentSectionId;
303 }
304 }
305#endif
306 }
307
308 // Copy the wix variable rows to the output now that all overriding has been accounted for.
309 foreach (var symbol in wixVariables.Values)
310 {
311 resolvedSection.AddSymbol(symbol);
312 }
313
314 // Bundles have groups of data that must be flattened in a way different from other types.
315 if (resolvedSection.Type == SectionType.Bundle)
316 {
317 var command = new FlattenAndProcessBundleTablesCommand(resolvedSection, this.Messaging);
318 command.Execute();
319 }
320
321 if (this.Messaging.EncounteredError)
322 {
323 return null;
324 }
325
326 var collate = new CollateLocalizationsCommand(this.Messaging, localizations);
327 var localizationsByCulture = collate.Execute();
328
329 intermediate = new Intermediate(resolvedSection.Id, Data.IntermediateLevels.Linked, new[] { resolvedSection }, localizationsByCulture);
330 }
331 finally
332 {
333 foreach (var extension in this.Context.Extensions)
334 {
335 extension.PostLink(intermediate);
336 }
337 }
338
339 return this.Messaging.EncounteredError ? null : intermediate;
340 }
341
342 /// <summary>
343 /// Check for colliding values and collect the wix variable rows.
344 /// </summary>
345 /// <param name="wixVariables">Collection of WixVariableSymbols by id.</param>
346 /// <param name="symbol">WixVariableSymbol to add, if not overridden.</param>
347 private void AddWixVariable(Dictionary<string, WixVariableSymbol> wixVariables, WixVariableSymbol symbol)
348 {
349 var id = symbol.Id.Id;
350
351 if (wixVariables.TryGetValue(id, out var collidingSymbol))
352 {
353 if (collidingSymbol.Overridable && !symbol.Overridable)
354 {
355 wixVariables[id] = symbol;
356 }
357 else if (!symbol.Overridable || (collidingSymbol.Overridable && symbol.Overridable))
358 {
359 this.Messaging.Write(ErrorMessages.WixVariableCollision(symbol.SourceLineNumbers, id));
360 }
361 }
362 else
363 {
364 wixVariables.Add(id, symbol);
365 }
366 }
367
368 /// <summary>
369 /// Load the standard action and directory symbols.
370 /// </summary>
371 /// <param name="symbolsByName">Collection of symbols.</param>
372 private void LoadStandardSymbols(IDictionary<string, SymbolWithSection> symbolsByName)
373 {
374 foreach (var actionSymbol in WindowsInstallerStandard.StandardActions())
375 {
376 var symbolWithSection = new SymbolWithSection(null, actionSymbol);
377
378 // If the action's symbol has not already been defined (i.e. overriden by the user), add it now.
379 if (!symbolsByName.ContainsKey(symbolWithSection.Name))
380 {
381 symbolsByName.Add(symbolWithSection.Name, symbolWithSection);
382 }
383 }
384
385 foreach (var directorySymbol in WindowsInstallerStandard.StandardDirectories())
386 {
387 var symbolWithSection = new SymbolWithSection(null, directorySymbol);
388
389 // If the directory's symbol has not already been defined (i.e. overriden by the user), add it now.
390 if (!symbolsByName.ContainsKey(symbolWithSection.Name))
391 {
392 symbolsByName.Add(symbolWithSection.Name, symbolWithSection);
393 }
394 }
395 }
396
397 /// <summary>
398 /// Process the complex references.
399 /// </summary>
400 /// <param name="resolvedSection">Active section to add symbols to.</param>
401 /// <param name="sections">Sections that are referenced during the link process.</param>
402 /// <param name="referencedComponents">Collection of all components referenced by complex reference.</param>
403 /// <param name="componentsToFeatures">Component to feature complex references.</param>
404 /// <param name="featuresToFeatures">Feature to feature complex references.</param>
405 /// <param name="modulesToFeatures">Module to feature complex references.</param>
406 private void ProcessComplexReferences(IntermediateSection resolvedSection, IEnumerable<IntermediateSection> sections, ISet<string> referencedComponents, ConnectToFeatureCollection componentsToFeatures, ConnectToFeatureCollection featuresToFeatures, ConnectToFeatureCollection modulesToFeatures)
407 {
408 var componentsToModules = new Hashtable();
409
410 foreach (var section in sections)
411 {
412 // Need ToList since we might want to add symbols while processing.
413 foreach (var wixComplexReferenceRow in section.Symbols.OfType<WixComplexReferenceSymbol>().ToList())
414 {
415 ConnectToFeature connection;
416 switch (wixComplexReferenceRow.ParentType)
417 {
418 case ComplexReferenceParentType.Feature:
419 switch (wixComplexReferenceRow.ChildType)
420 {
421 case ComplexReferenceChildType.Component:
422 connection = componentsToFeatures[wixComplexReferenceRow.Child];
423 if (null == connection)
424 {
425 componentsToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.Child, wixComplexReferenceRow.Parent, wixComplexReferenceRow.IsPrimary));
426 }
427 else if (wixComplexReferenceRow.IsPrimary)
428 {
429 if (connection.IsExplicitPrimaryFeature)
430 {
431 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));
432 continue;
433 }
434 else
435 {
436 connection.ConnectFeatures.Add(connection.PrimaryFeature); // move the guessed primary feature to the list of connects
437 connection.PrimaryFeature = wixComplexReferenceRow.Parent; // set the new primary feature
438 connection.IsExplicitPrimaryFeature = true; // and make sure we remember that we set it so we can fail if we try to set it again
439 }
440 }
441 else
442 {
443 connection.ConnectFeatures.Add(wixComplexReferenceRow.Parent);
444 }
445
446 // add a row to the FeatureComponents table
447 section.AddSymbol(new FeatureComponentsSymbol
448 {
449 FeatureRef = wixComplexReferenceRow.Parent,
450 ComponentRef = wixComplexReferenceRow.Child,
451 });
452
453 // index the component for finding orphaned records
454 var symbolName = String.Concat("Component:", wixComplexReferenceRow.Child);
455 referencedComponents.Add(symbolName);
456
457 break;
458
459 case ComplexReferenceChildType.Feature:
460 connection = featuresToFeatures[wixComplexReferenceRow.Child];
461 if (null != connection)
462 {
463 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)));
464 continue;
465 }
466
467 featuresToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.Child, wixComplexReferenceRow.Parent, wixComplexReferenceRow.IsPrimary));
468 break;
469
470 case ComplexReferenceChildType.Module:
471 connection = modulesToFeatures[wixComplexReferenceRow.Child];
472 if (null == connection)
473 {
474 modulesToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.Child, wixComplexReferenceRow.Parent, wixComplexReferenceRow.IsPrimary));
475 }
476 else if (wixComplexReferenceRow.IsPrimary)
477 {
478 if (connection.IsExplicitPrimaryFeature)
479 {
480 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)));
481 continue;
482 }
483 else
484 {
485 connection.ConnectFeatures.Add(connection.PrimaryFeature); // move the guessed primary feature to the list of connects
486 connection.PrimaryFeature = wixComplexReferenceRow.Parent; // set the new primary feature
487 connection.IsExplicitPrimaryFeature = true; // and make sure we remember that we set it so we can fail if we try to set it again
488 }
489 }
490 else
491 {
492 connection.ConnectFeatures.Add(wixComplexReferenceRow.Parent);
493 }
494 break;
495
496 default:
497 throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType)));
498 }
499 break;
500
501 case ComplexReferenceParentType.Module:
502 switch (wixComplexReferenceRow.ChildType)
503 {
504 case ComplexReferenceChildType.Component:
505 if (componentsToModules.ContainsKey(wixComplexReferenceRow.Child))
506 {
507 this.Messaging.Write(ErrorMessages.ComponentReferencedTwice(wixComplexReferenceRow.SourceLineNumbers, wixComplexReferenceRow.Child));
508 continue;
509 }
510 else
511 {
512 componentsToModules.Add(wixComplexReferenceRow.Child, wixComplexReferenceRow); // should always be new
513
514 // add a row to the ModuleComponents table
515 section.AddSymbol(new ModuleComponentsSymbol
516 {
517 Component = wixComplexReferenceRow.Child,
518 ModuleID = wixComplexReferenceRow.Parent,
519 Language = Convert.ToInt32(wixComplexReferenceRow.ParentLanguage),
520 });
521 }
522
523 // index the component for finding orphaned records
524 var componentSymbolName = String.Concat("Component:", wixComplexReferenceRow.Child);
525 referencedComponents.Add(componentSymbolName);
526
527 break;
528
529 default:
530 throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType)));
531 }
532 break;
533
534 case ComplexReferenceParentType.Patch:
535 switch (wixComplexReferenceRow.ChildType)
536 {
537 case ComplexReferenceChildType.PatchFamily:
538 case ComplexReferenceChildType.PatchFamilyGroup:
539 break;
540
541 default:
542 throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType)));
543 }
544 break;
545
546 case ComplexReferenceParentType.Product:
547 switch (wixComplexReferenceRow.ChildType)
548 {
549 case ComplexReferenceChildType.Feature:
550 connection = featuresToFeatures[wixComplexReferenceRow.Child];
551 if (null != connection)
552 {
553 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)));
554 continue;
555 }
556
557 featuresToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.Child, null, wixComplexReferenceRow.IsPrimary));
558 break;
559
560 default:
561 throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType)));
562 }
563 break;
564
565 default:
566 // Note: Groups have been processed before getting here so they are not handled by any case above.
567 throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Unexpected complex reference child type: {0}", Enum.GetName(typeof(ComplexReferenceParentType), wixComplexReferenceRow.ParentType)));
568 }
569 }
570 }
571 }
572
573 /// <summary>
574 /// Flattens all complex references in all sections in the collection.
575 /// </summary>
576 /// <param name="sections">Sections that are referenced during the link process.</param>
577 private void FlattenSectionsComplexReferences(IEnumerable<IntermediateSection> sections)
578 {
579 var parentGroups = new Dictionary<string, List<WixComplexReferenceSymbol>>();
580 var parentGroupsSections = new Dictionary<string, IntermediateSection>();
581 var parentGroupsNeedingProcessing = new Dictionary<string, IntermediateSection>();
582
583 // DisplaySectionComplexReferences("--- section's complex references before flattening ---", sections);
584
585 // Step 1: Gather all of the complex references that are going to participate
586 // in the flatting process. This means complex references that have "grouping
587 // parents" of Features, Modules, and, of course, Groups. These references
588 // that participate in a "grouping parent" will be removed from their section
589 // now and after processing added back in Step 3 below.
590 foreach (var section in sections)
591 {
592 var removeSymbols = new List<IntermediateSymbol>();
593
594 foreach (var symbol in section.Symbols)
595 {
596 // Only process the "grouping parents" such as FeatureGroup, ComponentGroup, Feature,
597 // and Module. Non-grouping complex references are simple and
598 // resolved during normal complex reference resolutions.
599 if (symbol is WixComplexReferenceSymbol wixComplexReferenceRow &&
600 (ComplexReferenceParentType.FeatureGroup == wixComplexReferenceRow.ParentType ||
601 ComplexReferenceParentType.ComponentGroup == wixComplexReferenceRow.ParentType ||
602 ComplexReferenceParentType.Feature == wixComplexReferenceRow.ParentType ||
603 ComplexReferenceParentType.Module == wixComplexReferenceRow.ParentType ||
604 ComplexReferenceParentType.PatchFamilyGroup == wixComplexReferenceRow.ParentType ||
605 ComplexReferenceParentType.Product == wixComplexReferenceRow.ParentType))
606 {
607 var parentTypeAndId = this.CombineTypeAndId(wixComplexReferenceRow.ParentType, wixComplexReferenceRow.Parent);
608
609 // Group all complex references with a common parent
610 // together so we can find them quickly while processing in
611 // Step 2.
612 if (!parentGroups.TryGetValue(parentTypeAndId, out var childrenComplexRefs))
613 {
614 childrenComplexRefs = new List<WixComplexReferenceSymbol>();
615 parentGroups.Add(parentTypeAndId, childrenComplexRefs);
616 }
617
618 childrenComplexRefs.Add(wixComplexReferenceRow);
619 removeSymbols.Add(wixComplexReferenceRow);
620
621 // Remember the mapping from set of complex references with a common
622 // parent to their section. We'll need this to add them back to the
623 // correct section in Step 3.
624 if (!parentGroupsSections.TryGetValue(parentTypeAndId, out var parentSection))
625 {
626 parentGroupsSections.Add(parentTypeAndId, section);
627 }
628
629 // If the child of the complex reference is another group, then in Step 2
630 // we're going to have to process this complex reference again to copy
631 // the child group's references into the parent group.
632 if ((ComplexReferenceChildType.ComponentGroup == wixComplexReferenceRow.ChildType) ||
633 (ComplexReferenceChildType.FeatureGroup == wixComplexReferenceRow.ChildType) ||
634 (ComplexReferenceChildType.PatchFamilyGroup == wixComplexReferenceRow.ChildType))
635 {
636 if (!parentGroupsNeedingProcessing.ContainsKey(parentTypeAndId))
637 {
638 parentGroupsNeedingProcessing.Add(parentTypeAndId, section);
639 }
640 }
641 }
642 }
643
644 foreach (var removeSymbol in removeSymbols)
645 {
646 section.RemoveSymbol(removeSymbol);
647 }
648 }
649
650 Debug.Assert(parentGroups.Count == parentGroupsSections.Count);
651 Debug.Assert(parentGroupsNeedingProcessing.Count <= parentGroups.Count);
652
653 // DisplaySectionComplexReferences("\r\n\r\n--- section's complex references middle of flattening ---", sections);
654
655 // Step 2: Loop through the parent groups that have nested groups removing
656 // them from the hash table as they are processed. At the end of this the
657 // complex references should all be flattened.
658 var keys = parentGroupsNeedingProcessing.Keys.ToList();
659
660 foreach (var key in keys)
661 {
662 if (parentGroupsNeedingProcessing.ContainsKey(key))
663 {
664 var loopDetector = new Stack<string>();
665 this.FlattenGroup(key, loopDetector, parentGroups, parentGroupsNeedingProcessing);
666 }
667 else
668 {
669 // the group must have allready been procesed and removed from the hash table
670 }
671 }
672 Debug.Assert(0 == parentGroupsNeedingProcessing.Count);
673
674 // Step 3: Finally, ensure that all of the groups that were removed
675 // in Step 1 and flattened in Step 2 are added to their appropriate
676 // section. This is where we will toss out the final no-longer-needed
677 // groups.
678 foreach (var parentGroup in parentGroups.Keys)
679 {
680 var section = parentGroupsSections[parentGroup];
681
682 foreach (var wixComplexReferenceRow in parentGroups[parentGroup])
683 {
684 if ((ComplexReferenceParentType.FeatureGroup != wixComplexReferenceRow.ParentType) &&
685 (ComplexReferenceParentType.ComponentGroup != wixComplexReferenceRow.ParentType) &&
686 (ComplexReferenceParentType.PatchFamilyGroup != wixComplexReferenceRow.ParentType))
687 {
688 section.AddSymbol(wixComplexReferenceRow);
689 }
690 }
691 }
692
693 // DisplaySectionComplexReferences("\r\n\r\n--- section's complex references after flattening ---", sections);
694 }
695
696 private string CombineTypeAndId(ComplexReferenceParentType type, string id)
697 {
698 return String.Concat(type.ToString(), ":", id);
699 }
700
701 private string CombineTypeAndId(ComplexReferenceChildType type, string id)
702 {
703 return String.Concat(type.ToString(), ":", id);
704 }
705
706 /// <summary>
707 /// Recursively processes the group.
708 /// </summary>
709 /// <param name="parentTypeAndId">String combination type and id of group to process next.</param>
710 /// <param name="loopDetector">Stack of groups processed thus far. Used to detect loops.</param>
711 /// <param name="parentGroups">Hash table of complex references grouped by parent id.</param>
712 /// <param name="parentGroupsNeedingProcessing">Hash table of parent groups that still have nested groups that need to be flattened.</param>
713 private void FlattenGroup(string parentTypeAndId, Stack<string> loopDetector, Dictionary<string, List<WixComplexReferenceSymbol>> parentGroups, Dictionary<string, IntermediateSection> parentGroupsNeedingProcessing)
714 {
715 Debug.Assert(parentGroupsNeedingProcessing.ContainsKey(parentTypeAndId));
716 loopDetector.Push(parentTypeAndId); // push this complex reference parent identfier into the stack for loop verifying
717
718 var allNewChildComplexReferences = new List<WixComplexReferenceSymbol>();
719
720 var referencesToParent = parentGroups[parentTypeAndId];
721 foreach (var wixComplexReferenceRow in referencesToParent)
722 {
723 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);
724 Debug.Assert(parentTypeAndId == this.CombineTypeAndId(wixComplexReferenceRow.ParentType, wixComplexReferenceRow.Parent));
725
726 // We are only interested processing when the child is a group.
727 if ((ComplexReferenceChildType.ComponentGroup == wixComplexReferenceRow.ChildType) ||
728 (ComplexReferenceChildType.FeatureGroup == wixComplexReferenceRow.ChildType) ||
729 (ComplexReferenceChildType.PatchFamilyGroup == wixComplexReferenceRow.ChildType))
730 {
731 var childTypeAndId = this.CombineTypeAndId(wixComplexReferenceRow.ChildType, wixComplexReferenceRow.Child);
732 if (loopDetector.Contains(childTypeAndId))
733 {
734 // Create a comma delimited list of the references that participate in the
735 // loop for the error message. Start at the bottom of the stack and work the
736 // way up to present the loop as a directed graph.
737 var loop = String.Join(" -> ", loopDetector);
738
739 this.Messaging.Write(ErrorMessages.ReferenceLoopDetected(wixComplexReferenceRow?.SourceLineNumbers, loop));
740
741 // Cleanup the parentGroupsNeedingProcessing and the loopDetector just like the
742 // exit of this method does at the end because we are exiting early.
743 loopDetector.Pop();
744 parentGroupsNeedingProcessing.Remove(parentTypeAndId);
745
746 return; // bail
747 }
748
749 // Check to see if the child group still needs to be processed. If so,
750 // go do that so that we'll get all of that children's (and children's
751 // children) complex references correctly merged into our parent group.
752 if (parentGroupsNeedingProcessing.ContainsKey(childTypeAndId))
753 {
754 this.FlattenGroup(childTypeAndId, loopDetector, parentGroups, parentGroupsNeedingProcessing);
755 }
756
757 // If the child is a parent to anything (i.e. the parent has grandchildren)
758 // clone each of the children's complex references, repoint them to the parent
759 // complex reference (because we're moving references up the tree), and finally
760 // add the cloned child's complex reference to the list of complex references
761 // that we'll eventually add to the parent group.
762 if (parentGroups.TryGetValue(childTypeAndId, out var referencesToChild))
763 {
764 foreach (var crefChild in referencesToChild)
765 {
766 // Only merge up the non-group items since groups are purged
767 // after this part of the processing anyway (cloning them would
768 // be a complete waste of time).
769 if ((ComplexReferenceChildType.FeatureGroup != crefChild.ChildType) ||
770 (ComplexReferenceChildType.ComponentGroup != crefChild.ChildType) ||
771 (ComplexReferenceChildType.PatchFamilyGroup != crefChild.ChildType))
772 {
773 var crefChildClone = crefChild.Clone();
774 Debug.Assert(crefChildClone.Parent == wixComplexReferenceRow.Child);
775
776 crefChildClone.Reparent(wixComplexReferenceRow);
777 allNewChildComplexReferences.Add(crefChildClone);
778 }
779 }
780 }
781 }
782 }
783
784 // Add the children group's complex references to the parent
785 // group. Clean out any left over groups and quietly remove any
786 // duplicate complex references that occurred during the merge.
787 referencesToParent.AddRange(allNewChildComplexReferences);
788 referencesToParent.Sort(ComplexReferenceComparision);
789 for (var i = referencesToParent.Count - 1; i >= 0; --i)
790 {
791 var wixComplexReferenceRow = referencesToParent[i];
792
793 if ((ComplexReferenceChildType.FeatureGroup == wixComplexReferenceRow.ChildType) ||
794 (ComplexReferenceChildType.ComponentGroup == wixComplexReferenceRow.ChildType) ||
795 (ComplexReferenceChildType.PatchFamilyGroup == wixComplexReferenceRow.ChildType))
796 {
797 referencesToParent.RemoveAt(i);
798 }
799 else if (i > 0)
800 {
801 // Since the list is already sorted, we can find duplicates by simply
802 // looking at the next sibling in the list and tossing out one if they
803 // match.
804 var crefCompare = referencesToParent[i - 1];
805 if (0 == wixComplexReferenceRow.CompareToWithoutConsideringPrimary(crefCompare))
806 {
807 referencesToParent.RemoveAt(i);
808 }
809 }
810 }
811
812 int ComplexReferenceComparision(WixComplexReferenceSymbol x, WixComplexReferenceSymbol y)
813 {
814 var comparison = x.ChildType - y.ChildType;
815 if (0 == comparison)
816 {
817 comparison = String.Compare(x.Child, y.Child, StringComparison.Ordinal);
818 if (0 == comparison)
819 {
820 comparison = x.ParentType - y.ParentType;
821 if (0 == comparison)
822 {
823 comparison = String.Compare(x.ParentLanguage ?? String.Empty, y.ParentLanguage ?? String.Empty, StringComparison.Ordinal);
824 if (0 == comparison)
825 {
826 comparison = String.Compare(x.Parent, y.Parent, StringComparison.Ordinal);
827 }
828 }
829 }
830 }
831
832 return comparison;
833 }
834
835 loopDetector.Pop(); // pop this complex reference off the stack since we're done verify the loop here
836 parentGroupsNeedingProcessing.Remove(parentTypeAndId); // remove the newly processed complex reference
837 }
838
839 /*
840 /// <summary>
841 /// Debugging method for displaying the section complex references.
842 /// </summary>
843 /// <param name="header">The header.</param>
844 /// <param name="sections">The sections to display.</param>
845 private void DisplaySectionComplexReferences(string header, SectionCollection sections)
846 {
847 Console.WriteLine(header);
848 foreach (Section section in sections)
849 {
850 Table wixComplexReferenceTable = section.Tables["WixComplexReference"];
851
852 foreach (WixComplexReferenceRow cref in wixComplexReferenceTable.Rows)
853 {
854 Console.WriteLine("Section: {0} Parent: {1} Type: {2} Child: {3} Primary: {4}", section.Id, cref.ParentId, cref.ParentType, cref.ChildId, cref.IsPrimary);
855 }
856 }
857 }
858 */
859
860 /// <summary>
861 /// Resolves the features connected to other features in the active output.
862 /// </summary>
863 /// <param name="featuresToFeatures">Feature to feature complex references.</param>
864 /// <param name="allSymbols">All symbols loaded from the sections.</param>
865 private void ResolveFeatureToFeatureConnects(ConnectToFeatureCollection featuresToFeatures, IDictionary<string, SymbolWithSection> allSymbols)
866 {
867 foreach (ConnectToFeature connection in featuresToFeatures)
868 {
869 var wixSimpleReferenceRow = new WixSimpleReferenceSymbol
870 {
871 Table = "Feature",
872 PrimaryKeys = connection.ChildId
873 };
874
875 if (allSymbols.TryGetValue(wixSimpleReferenceRow.SymbolicName, out var symbol))
876 {
877 var featureSymbol = (FeatureSymbol)symbol.Symbol;
878 featureSymbol.ParentFeatureRef = connection.PrimaryFeature;
879 }
880 }
881 }
882
883 /// <summary>
884 /// Resolve features for columns that have null guid placeholders.
885 /// </summary>
886 /// <param name="symbol">Symbol to resolve.</param>
887 /// <param name="connectionColumn">Number of the column containing the connection identifier.</param>
888 /// <param name="featureColumn">Number of the column containing the feature.</param>
889 /// <param name="connectToFeatures">Connect to feature complex references.</param>
890 /// <param name="multipleFeatureComponents">Hashtable of known components under multiple features.</param>
891 private void ResolveFeatures(IntermediateSymbol symbol, int connectionColumn, int featureColumn, ConnectToFeatureCollection connectToFeatures, Hashtable multipleFeatureComponents)
892 {
893 var connectionId = connectionColumn < 0 ? symbol.Id.Id : symbol.AsString(connectionColumn);
894 var featureId = symbol.AsString(featureColumn);
895
896 if (EmptyGuid == featureId)
897 {
898 var connection = connectToFeatures[connectionId];
899
900 if (null == connection)
901 {
902 // display an error for the component or merge module as appropriate
903 if (null != multipleFeatureComponents)
904 {
905 this.Messaging.Write(ErrorMessages.ComponentExpectedFeature(symbol.SourceLineNumbers, connectionId, symbol.Definition.Name, symbol.Id.Id));
906 }
907 else
908 {
909 this.Messaging.Write(ErrorMessages.MergeModuleExpectedFeature(symbol.SourceLineNumbers, connectionId));
910 }
911 }
912 else
913 {
914 // check for unique, implicit, primary feature parents with multiple possible parent features
915 if (this.ShowPedanticMessages &&
916 !connection.IsExplicitPrimaryFeature &&
917 0 < connection.ConnectFeatures.Count)
918 {
919 // display a warning for the component or merge module as approrpriate
920 if (null != multipleFeatureComponents)
921 {
922 if (!multipleFeatureComponents.Contains(connectionId))
923 {
924 this.Messaging.Write(WarningMessages.ImplicitComponentPrimaryFeature(connectionId));
925
926 // remember this component so only one warning is generated for it
927 multipleFeatureComponents[connectionId] = null;
928 }
929 }
930 else
931 {
932 this.Messaging.Write(WarningMessages.ImplicitMergeModulePrimaryFeature(connectionId));
933 }
934 }
935
936 // set the feature
937 symbol.Set(featureColumn, connection.PrimaryFeature);
938 }
939 }
940 }
941 }
942}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using WixToolset.Data;
6
7 internal static class LinkerErrors
8 {
9 public static Message OrphanedPayload(SourceLineNumber sourceLineNumbers, string payloadId)
10 {
11 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);
12 }
13
14 public static Message PackageInMultipleContainers(SourceLineNumber sourceLineNumbers, string packageId, string containerId1, string containerId2)
15 {
16 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);
17 }
18
19 public static Message PayloadSharedWithBA(SourceLineNumber sourceLineNumbers, string payloadId)
20 {
21 return Message(sourceLineNumbers, Ids.PayloadSharedWithBA, "The Payload '{0}' is shared with the BootstrapperApplication. This is not currently supported.", payloadId);
22 }
23
24 public static Message UnscheduledChainPackage(SourceLineNumber sourceLineNumbers, string packageId)
25 {
26 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);
27 }
28
29 public static Message UnscheduledRollbackBoundary(SourceLineNumber sourceLineNumbers, string rollbackBoundaryId)
30 {
31 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);
32 }
33
34 private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args)
35 {
36 return new Message(sourceLineNumber, MessageLevel.Error, (int)id, format, args);
37 }
38
39 public enum Ids
40 {
41 OrphanedPayload = 7000,
42 PackageInMultipleContainers = 7001,
43 PayloadSharedWithBA = 7002,
44 UnscheduledChainPackage = 7003,
45 UnscheduledRollbackBoundary = 7004,
46 } // last available is 7099. 7100 is WindowsInstallerBackendWarnings.
47 }
48}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using WixToolset.Data;
6
7 internal static class LinkerWarnings
8 {
9 public static Message LayoutPayloadInContainer(SourceLineNumber sourceLineNumbers, string payloadId, string containerId)
10 {
11 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);
12 }
13
14 public static Message PayloadInMultipleContainers(SourceLineNumber sourceLineNumbers, string payloadId, string containerId1, string containerId2)
15 {
16 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);
17 }
18
19 public static Message UncompressedPayloadInContainer(SourceLineNumber sourceLineNumbers, string payloadId, string containerId)
20 {
21 return Message(sourceLineNumbers, Ids.UncompressedPayloadInContainer, "The Payload '{0}' is being added to Container '{1}', overriding its Compressed value of 'no'.", payloadId, containerId);
22 }
23
24 private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args)
25 {
26 return new Message(sourceLineNumber, MessageLevel.Warning, (int)id, format, args);
27 }
28
29 public enum Ids
30 {
31 LayoutPayloadInContainer = 6900,
32 PayloadInMultipleContainers = 6901,
33 UncompressedPayloadInContainer = 6902,
34 } // last available is 6999. 7000 is LinkerErrors.
35 }
36}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Xml.Linq;
8 using WixToolset.Data;
9 using WixToolset.Data.Bind;
10 using WixToolset.Extensibility;
11 using WixToolset.Extensibility.Services;
12
13 internal class LocalizationParser : ILocalizationParser
14 {
15 public static readonly XNamespace WxlNamespace = "http://wixtoolset.org/schemas/v4/wxl";
16 private const string XmlElementName = "WixLocalization";
17
18 internal LocalizationParser(IServiceProvider serviceProvider)
19 {
20 this.Messaging = serviceProvider.GetService<IMessaging>();
21 }
22
23 private IMessaging Messaging { get; }
24
25 public Localization ParseLocalization(string path)
26 {
27 var document = XDocument.Load(path);
28 return this.ParseLocalization(document);
29 }
30
31 public Localization ParseLocalization(XDocument document)
32 {
33 var root = document.Root;
34 Localization localization = null;
35
36 var sourceLineNumbers = SourceLineNumber.CreateFromXObject(root);
37 if (LocalizationParser.XmlElementName == root.Name.LocalName)
38 {
39 if (LocalizationParser.WxlNamespace == root.Name.Namespace)
40 {
41 localization = ParseWixLocalizationElement(this.Messaging, root);
42 }
43 else // invalid or missing namespace
44 {
45 if (null == root.Name.Namespace)
46 {
47 this.Messaging.Write(ErrorMessages.InvalidWixXmlNamespace(sourceLineNumbers, LocalizationParser.XmlElementName, LocalizationParser.WxlNamespace.NamespaceName));
48 }
49 else
50 {
51 this.Messaging.Write(ErrorMessages.InvalidWixXmlNamespace(sourceLineNumbers, LocalizationParser.XmlElementName, root.Name.LocalName, LocalizationParser.WxlNamespace.NamespaceName));
52 }
53 }
54 }
55 else
56 {
57 this.Messaging.Write(ErrorMessages.InvalidDocumentElement(sourceLineNumbers, root.Name.LocalName, "localization", LocalizationParser.XmlElementName));
58 }
59
60 return localization;
61 }
62
63 /// <summary>
64 /// Adds a WixVariableRow to a dictionary while performing the expected override checks.
65 /// </summary>
66 /// <param name="messaging"></param>
67 /// <param name="variables">Dictionary of variable rows.</param>
68 /// <param name="wixVariableRow">Row to add to the variables dictionary.</param>
69 private static void AddWixVariable(IMessaging messaging, IDictionary<string, BindVariable> variables, BindVariable wixVariableRow)
70 {
71 if (!variables.TryGetValue(wixVariableRow.Id, out var existingWixVariableRow) || (existingWixVariableRow.Overridable && !wixVariableRow.Overridable))
72 {
73 variables[wixVariableRow.Id] = wixVariableRow;
74 }
75 else if (!wixVariableRow.Overridable)
76 {
77 messaging.Write(ErrorMessages.DuplicateLocalizationIdentifier(wixVariableRow.SourceLineNumbers, wixVariableRow.Id));
78 }
79 }
80
81 /// <summary>
82 /// Parses the WixLocalization element.
83 /// </summary>
84 /// <param name="messaging"></param>
85 /// <param name="node">Element to parse.</param>
86 private static Localization ParseWixLocalizationElement(IMessaging messaging, XElement node)
87 {
88 var sourceLineNumbers = SourceLineNumber.CreateFromXObject(node);
89 int? codepage = null;
90 int? summaryInformationCodepage = null;
91 string culture = null;
92
93 foreach (var attrib in node.Attributes())
94 {
95 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || LocalizationParser.WxlNamespace == attrib.Name.Namespace)
96 {
97 switch (attrib.Name.LocalName)
98 {
99 case "Codepage":
100 codepage = Common.GetValidCodePage(attrib.Value, true, false, sourceLineNumbers);
101 break;
102 case "SummaryInformationCodepage":
103 summaryInformationCodepage = Common.GetValidCodePage(attrib.Value, true, false, sourceLineNumbers);
104 break;
105 case "Culture":
106 culture = attrib.Value;
107 break;
108 case "Language":
109 // do nothing; @Language is used for locutil which can't convert Culture to lcid
110 break;
111 default:
112 Common.UnexpectedAttribute(messaging, sourceLineNumbers, attrib);
113 break;
114 }
115 }
116 else
117 {
118 Common.UnexpectedAttribute(messaging, sourceLineNumbers, attrib);
119 }
120 }
121
122 var variables = new Dictionary<string, BindVariable>();
123 var localizedControls = new Dictionary<string, LocalizedControl>();
124
125 foreach (var child in node.Elements())
126 {
127 if (LocalizationParser.WxlNamespace == child.Name.Namespace)
128 {
129 switch (child.Name.LocalName)
130 {
131 case "String":
132 LocalizationParser.ParseString(messaging, child, variables);
133 break;
134
135 case "UI":
136 LocalizationParser.ParseUI(messaging, child, localizedControls);
137 break;
138
139 default:
140 messaging.Write(ErrorMessages.UnexpectedElement(sourceLineNumbers, node.Name.ToString(), child.Name.ToString()));
141 break;
142 }
143 }
144 else
145 {
146 messaging.Write(ErrorMessages.UnsupportedExtensionElement(sourceLineNumbers, node.Name.ToString(), child.Name.ToString()));
147 }
148 }
149
150 return messaging.EncounteredError ? null : new Localization(codepage, summaryInformationCodepage, culture, variables, localizedControls);
151 }
152
153 /// <summary>
154 /// Parse a localization string into a WixVariableRow.
155 /// </summary>
156 /// <param name="messaging"></param>
157 /// <param name="node">Element to parse.</param>
158 /// <param name="variables"></param>
159 private static void ParseString(IMessaging messaging, XElement node, IDictionary<string, BindVariable> variables)
160 {
161 string id = null;
162 var overridable = false;
163 var sourceLineNumbers = SourceLineNumber.CreateFromXObject(node);
164
165 foreach (var attrib in node.Attributes())
166 {
167 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || LocalizationParser.WxlNamespace == attrib.Name.Namespace)
168 {
169 switch (attrib.Name.LocalName)
170 {
171 case "Id":
172 id = Common.GetAttributeIdentifierValue(messaging, sourceLineNumbers, attrib);
173 break;
174 case "Overridable":
175 overridable = YesNoType.Yes == Common.GetAttributeYesNoValue(messaging, sourceLineNumbers, attrib);
176 break;
177 case "Localizable":
178 ; // do nothing
179 break;
180 default:
181 messaging.Write(ErrorMessages.UnexpectedAttribute(sourceLineNumbers, attrib.Parent.Name.ToString(), attrib.Name.ToString()));
182 break;
183 }
184 }
185 else
186 {
187 messaging.Write(ErrorMessages.UnsupportedExtensionAttribute(sourceLineNumbers, attrib.Parent.Name.ToString(), attrib.Name.ToString()));
188 }
189 }
190
191 var value = Common.GetInnerText(node);
192
193 if (null == id)
194 {
195 messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, "String", "Id"));
196 }
197 else if (0 == id.Length)
198 {
199 messaging.Write(ErrorMessages.IllegalIdentifier(sourceLineNumbers, "String", "Id", 0));
200 }
201
202 if (!messaging.EncounteredError)
203 {
204 var variable = new BindVariable
205 {
206 SourceLineNumbers = sourceLineNumbers,
207 Id = id,
208 Overridable = overridable,
209 Value = value,
210 };
211
212 LocalizationParser.AddWixVariable(messaging, variables, variable);
213 }
214 }
215
216 /// <summary>
217 /// Parse a localized control.
218 /// </summary>
219 /// <param name="messaging"></param>
220 /// <param name="node">Element to parse.</param>
221 /// <param name="localizedControls">Dictionary of localized controls.</param>
222 private static void ParseUI(IMessaging messaging, XElement node, IDictionary<string, LocalizedControl> localizedControls)
223 {
224 string dialog = null;
225 string control = null;
226 var x = CompilerConstants.IntegerNotSet;
227 var y = CompilerConstants.IntegerNotSet;
228 var width = CompilerConstants.IntegerNotSet;
229 var height = CompilerConstants.IntegerNotSet;
230 var sourceLineNumbers = SourceLineNumber.CreateFromXObject(node);
231 var rightToLeft = false;
232 var rightAligned = false;
233 var leftScroll = false;
234
235 foreach (var attrib in node.Attributes())
236 {
237 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || LocalizationParser.WxlNamespace == attrib.Name.Namespace)
238 {
239 switch (attrib.Name.LocalName)
240 {
241 case "Dialog":
242 dialog = Common.GetAttributeIdentifierValue(messaging, sourceLineNumbers, attrib);
243 break;
244 case "Control":
245 control = Common.GetAttributeIdentifierValue(messaging, sourceLineNumbers, attrib);
246 break;
247 case "X":
248 x = Common.GetAttributeIntegerValue(messaging, sourceLineNumbers, attrib, 0, Int16.MaxValue);
249 break;
250 case "Y":
251 y = Common.GetAttributeIntegerValue(messaging, sourceLineNumbers, attrib, 0, Int16.MaxValue);
252 break;
253 case "Width":
254 width = Common.GetAttributeIntegerValue(messaging, sourceLineNumbers, attrib, 0, Int16.MaxValue);
255 break;
256 case "Height":
257 height = Common.GetAttributeIntegerValue(messaging, sourceLineNumbers, attrib, 0, Int16.MaxValue);
258 break;
259 case "RightToLeft":
260 rightToLeft = YesNoType.Yes == Common.GetAttributeYesNoValue(messaging, sourceLineNumbers, attrib);
261 break;
262 case "RightAligned":
263 rightAligned = YesNoType.Yes == Common.GetAttributeYesNoValue(messaging, sourceLineNumbers, attrib);
264 break;
265 case "LeftScroll":
266 leftScroll = YesNoType.Yes == Common.GetAttributeYesNoValue(messaging, sourceLineNumbers, attrib);
267 break;
268 default:
269 Common.UnexpectedAttribute(messaging, sourceLineNumbers, attrib);
270 break;
271 }
272 }
273 else
274 {
275 Common.UnexpectedAttribute(messaging, sourceLineNumbers, attrib);
276 }
277 }
278
279 var text = Common.GetInnerText(node);
280
281 if (String.IsNullOrEmpty(control) && (rightToLeft || rightAligned || leftScroll))
282 {
283 if (rightToLeft)
284 {
285 messaging.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.ToString(), "RightToLeft", "Control"));
286 }
287
288 if (rightAligned)
289 {
290 messaging.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.ToString(), "RightAligned", "Control"));
291 }
292
293 if (leftScroll)
294 {
295 messaging.Write(ErrorMessages.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.ToString(), "LeftScroll", "Control"));
296 }
297 }
298
299 if (String.IsNullOrEmpty(control) && String.IsNullOrEmpty(dialog))
300 {
301 messaging.Write(ErrorMessages.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.ToString(), "Dialog", "Control"));
302 }
303
304 if (!messaging.EncounteredError)
305 {
306 var localizedControl = new LocalizedControl(dialog, control, x, y, width, height, rightToLeft, rightAligned, leftScroll, text);
307 var key = localizedControl.GetKey();
308 if (localizedControls.ContainsKey(key))
309 {
310 if (String.IsNullOrEmpty(localizedControl.Control))
311 {
312 messaging.Write(ErrorMessages.DuplicatedUiLocalization(sourceLineNumbers, localizedControl.Dialog));
313 }
314 else
315 {
316 messaging.Write(ErrorMessages.DuplicatedUiLocalization(sourceLineNumbers, localizedControl.Dialog, localizedControl.Control));
317 }
318 }
319 else
320 {
321 localizedControls.Add(key, localizedControl);
322 }
323 }
324 }
325 }
326}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 internal class ParsedWixVariable
6 {
7 public int Index { get; set; }
8
9 public int Length { get; set; }
10
11 public string Namespace { get; set; }
12
13 public string Name { get; set; }
14
15 public string Scope { get; set; }
16
17 public string DefaultValue { get; set; }
18 }
19}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Preprocess
4{
5 /// <summary>
6 /// Context for an if statement in the preprocessor.
7 /// </summary>
8 internal class IfContext
9 {
10 private bool keep;
11
12 /// <summary>
13 /// Creates a default if context object, which are used for if's within an inactive preprocessor block
14 /// </summary>
15 public IfContext()
16 {
17 this.WasEverTrue = true;
18 this.IfState = IfState.If;
19 }
20
21 /// <summary>
22 /// Creates an if context object.
23 /// </summary>
24 /// <param name="active">Flag if context is currently active.</param>
25 /// <param name="keep">Flag if context is currently true.</param>
26 /// <param name="state">State of context to start in.</param>
27 public IfContext(bool active, bool keep, IfState state)
28 {
29 this.Active = active;
30 this.keep = keep;
31 this.WasEverTrue = keep;
32 this.IfState = IfState.If;
33 }
34
35 /// <summary>
36 /// Gets and sets if this if context is currently active.
37 /// </summary>
38 /// <value>true if context is active.</value>
39 public bool Active { get; set; }
40
41 /// <summary>
42 /// Gets and sets if context is current true.
43 /// </summary>
44 /// <value>true if context is currently true.</value>
45 public bool IsTrue
46 {
47 get
48 {
49 return this.keep;
50 }
51
52 set
53 {
54 this.keep = value;
55 if (this.keep)
56 {
57 this.WasEverTrue = true;
58 }
59 }
60 }
61
62 /// <summary>
63 /// Gets if the context was ever true.
64 /// </summary>
65 /// <value>True if context was ever true.</value>
66 public bool WasEverTrue { get; private set; }
67
68 /// <summary>
69 /// Gets the current state of the if context.
70 /// </summary>
71 /// <value>Current state of context.</value>
72 public IfState IfState { get; set; }
73 }
74}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Preprocess
4{
5 using System;
6 using WixToolset.Data;
7
8 internal delegate void IfDefEventHandler(object sender, IfDefEventArgs e);
9
10 internal class IfDefEventArgs : EventArgs
11 {
12 public IfDefEventArgs(SourceLineNumber sourceLineNumbers, bool isIfDef, bool isDefined, string variableName)
13 {
14 this.SourceLineNumbers = sourceLineNumbers;
15 this.IsIfDef = isIfDef;
16 this.IsDefined = isDefined;
17 this.VariableName = variableName;
18 }
19
20 public SourceLineNumber SourceLineNumbers { get; }
21
22 public bool IsDefined { get; }
23
24 public bool IsIfDef { get; }
25
26 public string VariableName { get; }
27 }
28}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Preprocess
4{
5 /// <summary>
6 /// Current state of the if context.
7 /// </summary>
8 internal enum IfState
9 {
10 /// <summary>Context currently in unknown state.</summary>
11 Unknown,
12
13 /// <summary>Context currently inside if statement.</summary>
14 If,
15
16 /// <summary>Context currently inside elseif statement..</summary>
17 ElseIf,
18
19 /// <summary>Conext currently inside else statement.</summary>
20 Else,
21 }
22}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Preprocess
4{
5 using System;
6 using WixToolset.Data;
7
8 /// <summary>
9 /// Included file event handler delegate.
10 /// </summary>
11 /// <param name="sender">Sender of the message.</param>
12 /// <param name="e">Arguments for the included file event.</param>
13 internal delegate void IncludedFileEventHandler(object sender, IncludedFileEventArgs e);
14
15 /// <summary>
16 /// Event args for included file event.
17 /// </summary>
18 internal class IncludedFileEventArgs : EventArgs
19 {
20 /// <summary>
21 /// Creates a new IncludedFileEventArgs.
22 /// </summary>
23 /// <param name="sourceLineNumbers">Source line numbers for the included file.</param>
24 /// <param name="fullName">The full path of the included file.</param>
25 public IncludedFileEventArgs(SourceLineNumber sourceLineNumbers, string fullName)
26 {
27 this.SourceLineNumbers = sourceLineNumbers;
28 this.FullName = fullName;
29 }
30
31 /// <summary>
32 /// Gets the full path of the included file.
33 /// </summary>
34 /// <value>The full path of the included file.</value>
35 public string FullName { get; }
36
37 /// <summary>
38 /// Gets the source line numbers.
39 /// </summary>
40 /// <value>The source line numbers.</value>
41 public SourceLineNumber SourceLineNumbers { get; }
42 }
43}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Preprocess
4{
5 /// <summary>
6 /// Enumeration for preprocessor operations in if statements.
7 /// </summary>
8 internal enum PreprocessorOperation
9 {
10 /// <summary>The and operator.</summary>
11 And,
12
13 /// <summary>The or operator.</summary>
14 Or,
15
16 /// <summary>The not operator.</summary>
17 Not
18 }
19}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Preprocess
4{
5 using System;
6 using System.Xml.Linq;
7
8 /// <summary>
9 /// Preprocessed output stream event handler delegate.
10 /// </summary>
11 /// <param name="sender">Sender of the message.</param>
12 /// <param name="e">Arguments for the preprocessed stream event.</param>
13 internal delegate void ProcessedStreamEventHandler(object sender, ProcessedStreamEventArgs e);
14
15 /// <summary>
16 /// Event args for preprocessed stream event.
17 /// </summary>
18 internal class ProcessedStreamEventArgs : EventArgs
19 {
20 /// <summary>
21 /// Creates a new ProcessedStreamEventArgs.
22 /// </summary>
23 /// <param name="sourceFile">Source file that is preprocessed.</param>
24 /// <param name="document">Preprocessed output document.</param>
25 public ProcessedStreamEventArgs(string sourceFile, XDocument document)
26 {
27 this.SourceFile = sourceFile;
28 this.Document = document;
29 }
30
31 /// <summary>
32 /// Gets the full path of the source file.
33 /// </summary>
34 /// <value>The full path of the source file.</value>
35 public string SourceFile { get; }
36
37 /// <summary>
38 /// Gets the preprocessed output stream.
39 /// </summary>
40 /// <value>The the preprocessed output stream.</value>
41 public XDocument Document { get; }
42 }
43}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Preprocess
4{
5 using System;
6 using WixToolset.Data;
7
8 internal delegate void ResolvedVariableEventHandler(object sender, ResolvedVariableEventArgs e);
9
10 internal class ResolvedVariableEventArgs : EventArgs
11 {
12 public ResolvedVariableEventArgs(SourceLineNumber sourceLineNumbers, string variableName, string variableValue)
13 {
14 this.SourceLineNumbers = sourceLineNumbers;
15 this.VariableName = variableName;
16 this.VariableValue = variableValue;
17 }
18
19 public SourceLineNumber SourceLineNumbers { get; }
20
21 public string VariableName { get; }
22
23 public string VariableValue { get; }
24 }
25}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Threading;
8 using WixToolset.Data;
9 using WixToolset.Extensibility;
10 using WixToolset.Extensibility.Data;
11
12 internal class PreprocessContext : IPreprocessContext
13 {
14 internal PreprocessContext(IServiceProvider serviceProvider)
15 {
16 this.ServiceProvider = serviceProvider;
17 }
18
19 public IServiceProvider ServiceProvider { get; }
20
21 public IReadOnlyCollection<IPreprocessorExtension> Extensions { get; set; }
22
23 public Platform Platform { get; set; }
24
25 public IReadOnlyCollection<string> IncludeSearchPaths { get; set; }
26
27 public string SourcePath { get; set; }
28
29 public IDictionary<string, string> Variables { get; set; }
30
31 public SourceLineNumber CurrentSourceLineNumber { get; set; }
32
33 public CancellationToken CancellationToken { get; set; }
34 }
35}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System.Collections.Generic;
6 using System.Xml.Linq;
7 using WixToolset.Extensibility.Data;
8
9 internal class PreprocessResult : IPreprocessResult
10 {
11 public XDocument Document { get; set; }
12
13 public IReadOnlyCollection<IIncludedFile> IncludedFiles { get; set; }
14 }
15}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.IO;
9 using System.Text;
10 using System.Text.RegularExpressions;
11 using System.Xml;
12 using System.Xml.Linq;
13 using WixToolset.Core.Preprocess;
14 using WixToolset.Data;
15 using WixToolset.Extensibility;
16 using WixToolset.Extensibility.Data;
17 using WixToolset.Extensibility.Services;
18
19 /// <summary>
20 /// Preprocessor object
21 /// </summary>
22 internal class Preprocessor : IPreprocessor
23 {
24 private static readonly Regex DefineRegex = new Regex(@"^\s*(?<varName>.+?)\s*(=\s*(?<varValue>.+?)\s*)?$", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture);
25 private static readonly Regex PragmaRegex = new Regex(@"^\s*(?<pragmaName>.+?)(?<pragmaValue>[\s\(].+?)?$", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture);
26
27 private static readonly XmlReaderSettings DocumentXmlReaderSettings = new XmlReaderSettings()
28 {
29 ValidationFlags = System.Xml.Schema.XmlSchemaValidationFlags.None,
30 XmlResolver = null,
31 };
32
33 private static readonly XmlReaderSettings FragmentXmlReaderSettings = new XmlReaderSettings()
34 {
35 ConformanceLevel = ConformanceLevel.Fragment,
36 ValidationFlags = System.Xml.Schema.XmlSchemaValidationFlags.None,
37 XmlResolver = null,
38 };
39
40 internal Preprocessor(IServiceProvider serviceProvider)
41 {
42 this.ServiceProvider = serviceProvider;
43
44 this.Messaging = this.ServiceProvider.GetService<IMessaging>();
45 }
46
47 private IServiceProvider ServiceProvider { get; }
48
49 private IMessaging Messaging { get; }
50
51 /// <summary>
52 /// Event for ifdef/ifndef directives.
53 /// </summary>
54 public event IfDefEventHandler IfDef;
55
56 /// <summary>
57 /// Event for included files.
58 /// </summary>
59 public event IncludedFileEventHandler IncludedFile;
60
61 /// <summary>
62 /// Event for preprocessed stream.
63 /// </summary>
64 public event ProcessedStreamEventHandler ProcessedStream;
65
66 // <summary>
67 // Event for resolved variables.
68 // </summary>
69 // TOOD: Remove?
70 //public event ResolvedVariableEventHandler ResolvedVariable;
71
72 /// <summary>
73 /// Get the source line information for the current element. The precompiler will insert
74 /// special source line number information for each element that it encounters.
75 /// </summary>
76 /// <param name="node">Element to get source line information for.</param>
77 /// <returns>
78 /// The source line number used to author the element being processed or
79 /// null if the preprocessor did not process the element or the node is
80 /// not an element.
81 /// </returns>
82 public static SourceLineNumber GetSourceLineNumbers(XObject node)
83 {
84 return SourceLineNumber.GetFromXAnnotation(node);
85 }
86
87 /// <summary>
88 /// Preprocesses a file.
89 /// </summary>
90 /// <param name="context">The preprocessing context.</param>
91 /// <returns>XDocument with the postprocessed data.</returns>
92 public IPreprocessResult Preprocess(IPreprocessContext context)
93 {
94 var state = new ProcessingState(this.ServiceProvider, context);
95
96 this.PreProcess(state);
97
98 IPreprocessResult result;
99 using (var reader = XmlReader.Create(state.Context.SourcePath, DocumentXmlReaderSettings))
100 {
101 result = this.Process(state, reader);
102 }
103
104 this.PostProcess(state, result);
105
106 return result;
107 }
108
109 /// <summary>
110 /// Preprocesses a file.
111 /// </summary>
112 /// <param name="context">The preprocessing context.</param>
113 /// <param name="reader">XmlReader to processing the context.</param>
114 /// <returns>XDocument with the postprocessed data.</returns>
115 public IPreprocessResult Preprocess(IPreprocessContext context, XmlReader reader)
116 {
117 if (String.IsNullOrEmpty(context.SourcePath) && !String.IsNullOrEmpty(reader.BaseURI))
118 {
119 var uri = new Uri(reader.BaseURI);
120 context.SourcePath = uri.AbsolutePath;
121 }
122
123 var state = new ProcessingState(this.ServiceProvider, context);
124
125 this.PreProcess(state);
126
127 var result = this.Process(state, reader);
128
129 this.PostProcess(state, result);
130
131 return result;
132 }
133
134 /// <summary>
135 /// Preprocesses a file.
136 /// </summary>
137 /// <param name="state">The preprocessing context.</param>
138 /// <param name="reader">XmlReader to processing the context.</param>
139 /// <returns>XDocument with the postprocessed data.</returns>
140 private IPreprocessResult Process(ProcessingState state, XmlReader reader)
141 {
142 state.CurrentFileStack.Push(state.Helper.GetVariableValue(state.Context, "sys", "SOURCEFILEDIR"));
143
144 // Process the reader into the output.
145 IPreprocessResult result = null;
146 try
147 {
148 this.PreprocessReader(state, false, reader, state.Output, 0);
149
150 // Fire event with post-processed document.
151 this.ProcessedStream?.Invoke(this, new ProcessedStreamEventArgs(state.Context.SourcePath, state.Output));
152
153 if (!this.Messaging.EncounteredError)
154 {
155 result = this.ServiceProvider.GetService<IPreprocessResult>();
156 result.Document = state.Output;
157 result.IncludedFiles = state.IncludedFiles;
158 }
159 }
160 catch (XmlException e)
161 {
162 this.UpdateCurrentLineNumber(state, reader, 0);
163 throw new WixException(ErrorMessages.InvalidXml(state.Context.CurrentSourceLineNumber, "source", e.Message));
164 }
165
166 return result;
167 }
168
169 /// <summary>
170 /// Determins if string is an operator.
171 /// </summary>
172 /// <param name="operation">String to check.</param>
173 /// <returns>true if string is an operator.</returns>
174 private static bool IsOperator(string operation)
175 {
176 if (operation == null)
177 {
178 return false;
179 }
180
181 operation = operation.Trim();
182 if (0 == operation.Length)
183 {
184 return false;
185 }
186
187 if ("=" == operation ||
188 "!=" == operation ||
189 "<" == operation ||
190 "<=" == operation ||
191 ">" == operation ||
192 ">=" == operation ||
193 "~=" == operation)
194 {
195 return true;
196 }
197 return false;
198 }
199
200 /// <summary>
201 /// Determines if expression is currently inside quotes.
202 /// </summary>
203 /// <param name="expression">Expression to evaluate.</param>
204 /// <param name="index">Index to start searching in expression.</param>
205 /// <returns>true if expression is inside in quotes.</returns>
206 private static bool InsideQuotes(string expression, int index)
207 {
208 if (index == -1)
209 {
210 return false;
211 }
212
213 var numQuotes = 0;
214 var tmpIndex = 0;
215 while (-1 != (tmpIndex = expression.IndexOf('\"', tmpIndex, index - tmpIndex)))
216 {
217 numQuotes++;
218 tmpIndex++;
219 }
220
221 // found an even number of quotes before the index, so we're not inside
222 if (numQuotes % 2 == 0)
223 {
224 return false;
225 }
226
227 // found an odd number of quotes, so we are inside
228 return true;
229 }
230
231 /// <summary>
232 /// Tests expression to see if it starts with a keyword.
233 /// </summary>
234 /// <param name="expression">Expression to test.</param>
235 /// <param name="operation">Operation to test for.</param>
236 /// <returns>true if expression starts with a keyword.</returns>
237 private static bool StartsWithKeyword(string expression, PreprocessorOperation operation)
238 {
239 expression = expression.ToUpperInvariant();
240 switch (operation)
241 {
242 case PreprocessorOperation.Not:
243 if (expression.StartsWith("NOT ", StringComparison.Ordinal) || expression.StartsWith("NOT(", StringComparison.Ordinal))
244 {
245 return true;
246 }
247 break;
248 case PreprocessorOperation.And:
249 if (expression.StartsWith("AND ", StringComparison.Ordinal) || expression.StartsWith("AND(", StringComparison.Ordinal))
250 {
251 return true;
252 }
253 break;
254 case PreprocessorOperation.Or:
255 if (expression.StartsWith("OR ", StringComparison.Ordinal) || expression.StartsWith("OR(", StringComparison.Ordinal))
256 {
257 return true;
258 }
259 break;
260 default:
261 break;
262 }
263 return false;
264 }
265
266 /// <summary>
267 /// Processes an xml reader into an xml writer.
268 /// </summary>
269 /// <param name="state"></param>
270 /// <param name="include">Specifies if reader is from an included file.</param>
271 /// <param name="reader">Reader for the source document.</param>
272 /// <param name="container">Node where content should be added.</param>
273 /// <param name="offset">Original offset for the line numbers being processed.</param>
274 private void PreprocessReader(ProcessingState state, bool include, XmlReader reader, XContainer container, int offset)
275 {
276 var currentContainer = container;
277 var containerStack = new Stack<XContainer>();
278
279 var ifContext = new IfContext(true, true, IfState.Unknown); // start by assuming we want to keep the nodes in the source code
280 var ifStack = new Stack<IfContext>();
281
282 // process the reader into the writer
283 while (reader.Read())
284 {
285 // update information here in case an error occurs before the next read
286 this.UpdateCurrentLineNumber(state, reader, offset);
287
288 var sourceLineNumbers = state.Context.CurrentSourceLineNumber;
289
290 // check for changes in conditional processing
291 if (XmlNodeType.ProcessingInstruction == reader.NodeType)
292 {
293 var ignore = false;
294 string name = null;
295
296 switch (reader.LocalName)
297 {
298 case "if":
299 ifStack.Push(ifContext);
300 if (ifContext.IsTrue)
301 {
302 ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, this.EvaluateExpression(state, reader.Value), IfState.If);
303 }
304 else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true
305 {
306 ifContext = new IfContext();
307 }
308 ignore = true;
309 break;
310
311 case "ifdef":
312 ifStack.Push(ifContext);
313 name = reader.Value.Trim();
314 if (ifContext.IsTrue)
315 {
316 ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, (null != state.Helper.GetVariableValue(state.Context, name, true)), IfState.If);
317 }
318 else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true
319 {
320 ifContext = new IfContext();
321 }
322 ignore = true;
323 this.IfDef?.Invoke(this, new IfDefEventArgs(sourceLineNumbers, true, ifContext.IsTrue, name));
324 break;
325
326 case "ifndef":
327 ifStack.Push(ifContext);
328 name = reader.Value.Trim();
329 if (ifContext.IsTrue)
330 {
331 ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, (null == state.Helper.GetVariableValue(state.Context, name, true)), IfState.If);
332 }
333 else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true
334 {
335 ifContext = new IfContext();
336 }
337 ignore = true;
338 this.IfDef?.Invoke(this, new IfDefEventArgs(sourceLineNumbers, false, !ifContext.IsTrue, name));
339 break;
340
341 case "elseif":
342 if (0 == ifStack.Count)
343 {
344 throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "elseif"));
345 }
346
347 if (IfState.If != ifContext.IfState && IfState.ElseIf != ifContext.IfState)
348 {
349 throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "elseif"));
350 }
351
352 ifContext.IfState = IfState.ElseIf; // we're now in an elseif
353 if (!ifContext.WasEverTrue) // if we've never evaluated the if context to true, then we can try this test
354 {
355 ifContext.IsTrue = this.EvaluateExpression(state, reader.Value);
356 }
357 else if (ifContext.IsTrue)
358 {
359 ifContext.IsTrue = false;
360 }
361 ignore = true;
362 break;
363
364 case "else":
365 if (0 == ifStack.Count)
366 {
367 throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "else"));
368 }
369
370 if (IfState.If != ifContext.IfState && IfState.ElseIf != ifContext.IfState)
371 {
372 throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "else"));
373 }
374
375 ifContext.IfState = IfState.Else; // we're now in an else
376 ifContext.IsTrue = !ifContext.WasEverTrue; // if we were never true, we can be true now
377 ignore = true;
378 break;
379
380 case "endif":
381 if (0 == ifStack.Count)
382 {
383 throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "if", "endif"));
384 }
385
386 ifContext = ifStack.Pop();
387 ignore = true;
388 break;
389 }
390
391 if (ignore) // ignore this node since we just handled it above
392 {
393 continue;
394 }
395 }
396
397 if (!ifContext.Active || !ifContext.IsTrue) // if our context is not true then skip the rest of the processing and just read the next thing
398 {
399 continue;
400 }
401
402 switch (reader.NodeType)
403 {
404 case XmlNodeType.XmlDeclaration:
405 var document = currentContainer as XDocument;
406 if (null != document)
407 {
408 document.Declaration = new XDeclaration(null, null, null);
409 while (reader.MoveToNextAttribute())
410 {
411 switch (reader.LocalName)
412 {
413 case "version":
414 document.Declaration.Version = reader.Value;
415 break;
416
417 case "encoding":
418 document.Declaration.Encoding = reader.Value;
419 break;
420
421 case "standalone":
422 document.Declaration.Standalone = reader.Value;
423 break;
424 }
425 }
426
427 }
428 //else
429 //{
430 // display an error? Can this happen?
431 //}
432 break;
433
434 case XmlNodeType.ProcessingInstruction:
435 switch (reader.LocalName)
436 {
437 case "define":
438 this.PreprocessDefine(state, reader.Value);
439 break;
440
441 case "error":
442 this.PreprocessError(state, reader.Value);
443 break;
444
445 case "warning":
446 this.PreprocessWarning(state, reader.Value);
447 break;
448
449 case "undef":
450 this.PreprocessUndef(state, reader.Value);
451 break;
452
453 case "include":
454 this.UpdateCurrentLineNumber(state, reader, offset);
455 this.PreprocessInclude(state, reader.Value, currentContainer);
456 break;
457
458 case "foreach":
459 this.PreprocessForeach(state, reader, currentContainer, offset);
460 break;
461
462 case "endforeach": // endforeach is handled in PreprocessForeach, so seeing it here is an error
463 throw new WixException(ErrorMessages.UnmatchedPreprocessorInstruction(sourceLineNumbers, "foreach", "endforeach"));
464
465 case "pragma":
466 this.PreprocessPragma(state, reader.Value, currentContainer);
467 break;
468
469 default:
470 // unknown processing instructions are currently ignored
471 break;
472 }
473 break;
474
475 case XmlNodeType.Element:
476 if (0 < state.IncludeNextStack.Count && state.IncludeNextStack.Peek())
477 {
478 if ("Include" != reader.LocalName)
479 {
480 this.Messaging.Write(ErrorMessages.InvalidDocumentElement(sourceLineNumbers, reader.Name, "include", "Include"));
481 }
482
483 state.IncludeNextStack.Pop();
484 state.IncludeNextStack.Push(false);
485 break;
486 }
487
488 var empty = reader.IsEmptyElement;
489 var ns = XNamespace.Get(reader.NamespaceURI);
490 var element = new XElement(ns + reader.LocalName);
491 currentContainer.Add(element);
492
493 this.UpdateCurrentLineNumber(state, reader, offset);
494 element.AddAnnotation(sourceLineNumbers);
495
496 while (reader.MoveToNextAttribute())
497 {
498 var value = state.Helper.PreprocessString(state.Context, reader.Value);
499
500 var attribNamespace = XNamespace.Get(reader.NamespaceURI);
501 attribNamespace = XNamespace.Xmlns == attribNamespace && reader.LocalName.Equals("xmlns") ? XNamespace.None : attribNamespace;
502
503 element.Add(new XAttribute(attribNamespace + reader.LocalName, value));
504 }
505
506 if (!empty)
507 {
508 containerStack.Push(currentContainer);
509 currentContainer = element;
510 }
511 break;
512
513 case XmlNodeType.EndElement:
514 if (0 < reader.Depth || !include)
515 {
516 currentContainer = containerStack.Pop();
517 }
518 break;
519
520 case XmlNodeType.Text:
521 var postprocessedText = state.Helper.PreprocessString(state.Context, reader.Value);
522 currentContainer.Add(postprocessedText);
523 break;
524
525 case XmlNodeType.CDATA:
526 var postprocessedValue = state.Helper.PreprocessString(state.Context, reader.Value);
527 currentContainer.Add(new XCData(postprocessedValue));
528 break;
529
530 default:
531 break;
532 }
533 }
534
535 if (0 != ifStack.Count)
536 {
537 throw new WixException(ErrorMessages.NonterminatedPreprocessorInstruction(state.Context.CurrentSourceLineNumber, "if", "endif"));
538 }
539
540 // TODO: can this actually happen?
541 if (0 != containerStack.Count)
542 {
543 throw new WixException(ErrorMessages.NonterminatedPreprocessorInstruction(state.Context.CurrentSourceLineNumber, "nodes", "nodes"));
544 }
545 }
546
547 /// <summary>
548 /// Processes an error processing instruction.
549 /// </summary>
550 /// <param name="state"></param>
551 /// <param name="errorMessage">Text from source.</param>
552 private void PreprocessError(ProcessingState state, string errorMessage)
553 {
554 // Resolve other variables in the error message.
555 errorMessage = state.Helper.PreprocessString(state.Context, errorMessage);
556
557 throw new WixException(ErrorMessages.PreprocessorError(state.Context.CurrentSourceLineNumber, errorMessage));
558 }
559
560 /// <summary>
561 /// Processes a warning processing instruction.
562 /// </summary>
563 /// <param name="state"></param>
564 /// <param name="warningMessage">Text from source.</param>
565 private void PreprocessWarning(ProcessingState state, string warningMessage)
566 {
567 // Resolve other variables in the warning message.
568 warningMessage = state.Helper.PreprocessString(state.Context, warningMessage);
569
570 this.Messaging.Write(WarningMessages.PreprocessorWarning(state.Context.CurrentSourceLineNumber, warningMessage));
571 }
572
573 /// <summary>
574 /// Processes a define processing instruction and creates the appropriate parameter.
575 /// </summary>
576 /// <param name="state"></param>
577 /// <param name="originalDefine">Text from source.</param>
578 private void PreprocessDefine(ProcessingState state, string originalDefine)
579 {
580 var match = DefineRegex.Match(originalDefine);
581
582 if (!match.Success)
583 {
584 throw new WixException(ErrorMessages.IllegalDefineStatement(state.Context.CurrentSourceLineNumber, originalDefine));
585 }
586
587 var defineName = match.Groups["varName"].Value;
588 var defineValue = match.Groups["varValue"].Value;
589
590 // strip off the optional quotes
591 if (1 < defineValue.Length &&
592 ((defineValue.StartsWith("\"", StringComparison.Ordinal) && defineValue.EndsWith("\"", StringComparison.Ordinal))
593 || (defineValue.StartsWith("'", StringComparison.Ordinal) && defineValue.EndsWith("'", StringComparison.Ordinal))))
594 {
595 defineValue = defineValue.Substring(1, defineValue.Length - 2);
596 }
597
598 // resolve other variables in the variable value
599 defineValue = state.Helper.PreprocessString(state.Context, defineValue);
600
601 if (defineName.StartsWith("var.", StringComparison.Ordinal))
602 {
603 state.Helper.AddVariable(state.Context, defineName.Substring(4), defineValue);
604 }
605 else
606 {
607 state.Helper.AddVariable(state.Context, defineName, defineValue);
608 }
609 }
610
611 /// <summary>
612 /// Processes an undef processing instruction and creates the appropriate parameter.
613 /// </summary>
614 /// <param name="state"></param>
615 /// <param name="originalDefine">Text from source.</param>
616 private void PreprocessUndef(ProcessingState state, string originalDefine)
617 {
618 var name = state.Helper.PreprocessString(state.Context, originalDefine.Trim());
619
620 if (name.StartsWith("var.", StringComparison.Ordinal))
621 {
622 state.Helper.RemoveVariable(state.Context, name.Substring(4));
623 }
624 else
625 {
626 state.Helper.RemoveVariable(state.Context, name);
627 }
628 }
629
630 /// <summary>
631 /// Processes an included file.
632 /// </summary>
633 /// <param name="state"></param>
634 /// <param name="includePath">Path to included file.</param>
635 /// <param name="parent">Parent container for included content.</param>
636 private void PreprocessInclude(ProcessingState state, string includePath, XContainer parent)
637 {
638 var sourceLineNumbers = state.Context.CurrentSourceLineNumber;
639
640 // Preprocess variables in the path.
641 includePath = state.Helper.PreprocessString(state.Context, includePath);
642
643 var includeFile = this.GetIncludeFile(state, includePath);
644
645 if (null == includeFile)
646 {
647 throw new WixException(ErrorMessages.FileNotFound(sourceLineNumbers, includePath, "include"));
648 }
649
650 using (var reader = XmlReader.Create(includeFile, DocumentXmlReaderSettings))
651 {
652 this.PushInclude(state, includeFile);
653
654 // process the included reader into the writer
655 try
656 {
657 this.PreprocessReader(state, true, reader, parent, 0);
658 }
659 catch (XmlException e)
660 {
661 this.UpdateCurrentLineNumber(state, reader, 0);
662 throw new WixException(ErrorMessages.InvalidXml(sourceLineNumbers, "source", e.Message));
663 }
664
665 this.IncludedFile?.Invoke(this, new IncludedFileEventArgs(sourceLineNumbers, includeFile));
666
667 var includedFile = this.ServiceProvider.GetService<IIncludedFile>();
668 includedFile.Path = includeFile;
669 includedFile.SourceLineNumbers = sourceLineNumbers;
670
671 state.IncludedFiles.Add(includedFile);
672
673 this.PopInclude(state);
674 }
675 }
676
677 /// <summary>
678 /// Preprocess a foreach processing instruction.
679 /// </summary>
680 /// <param name="state"></param>
681 /// <param name="reader">The xml reader.</param>
682 /// <param name="container">The container where to output processed data.</param>
683 /// <param name="offset">Offset for the line numbers.</param>
684 private void PreprocessForeach(ProcessingState state, XmlReader reader, XContainer container, int offset)
685 {
686 // Find the "in" token.
687 var indexOfInToken = reader.Value.IndexOf(" in ", StringComparison.Ordinal);
688 if (0 > indexOfInToken)
689 {
690 throw new WixException(ErrorMessages.IllegalForeach(state.Context.CurrentSourceLineNumber, reader.Value));
691 }
692
693 // parse out the variable name
694 var varName = reader.Value.Substring(0, indexOfInToken).Trim();
695 var varValuesString = reader.Value.Substring(indexOfInToken + 4).Trim();
696
697 // preprocess the variable values string because it might be a variable itself
698 varValuesString = state.Helper.PreprocessString(state.Context, varValuesString);
699
700 var varValues = varValuesString.Split(';');
701
702 // go through all the empty strings
703 while (reader.Read() && XmlNodeType.Whitespace == reader.NodeType)
704 {
705 }
706
707 // get the offset of this xml fragment (for some reason its always off by 1)
708 var lineInfoReader = reader as IXmlLineInfo;
709 if (null != lineInfoReader)
710 {
711 offset += lineInfoReader.LineNumber - 1;
712 }
713
714 var textReader = reader as XmlTextReader;
715 // dump the xml to a string (maintaining whitespace if possible)
716 if (null != textReader)
717 {
718 textReader.WhitespaceHandling = WhitespaceHandling.All;
719 }
720
721 var fragmentBuilder = new StringBuilder();
722 var nestedForeachCount = 1;
723 while (nestedForeachCount != 0)
724 {
725 if (reader.NodeType == XmlNodeType.ProcessingInstruction)
726 {
727 switch (reader.LocalName)
728 {
729 case "foreach":
730 ++nestedForeachCount;
731 // Output the foreach statement
732 fragmentBuilder.AppendFormat("<?foreach {0}?>", reader.Value);
733 break;
734
735 case "endforeach":
736 --nestedForeachCount;
737 if (0 != nestedForeachCount)
738 {
739 fragmentBuilder.Append("<?endforeach ?>");
740 }
741 break;
742
743 default:
744 fragmentBuilder.AppendFormat("<?{0} {1}?>", reader.LocalName, reader.Value);
745 break;
746 }
747 }
748 else if (reader.NodeType == XmlNodeType.Element)
749 {
750 fragmentBuilder.Append(reader.ReadOuterXml());
751 continue;
752 }
753 else if (reader.NodeType == XmlNodeType.Whitespace)
754 {
755 // Or output the whitespace
756 fragmentBuilder.Append(reader.Value);
757 }
758 else if (reader.NodeType == XmlNodeType.None)
759 {
760 throw new WixException(ErrorMessages.ExpectedEndforeach(state.Context.CurrentSourceLineNumber));
761 }
762
763 reader.Read();
764 }
765
766 using (var fragmentStream = new MemoryStream(Encoding.UTF8.GetBytes(fragmentBuilder.ToString())))
767 {
768 // process each iteration, updating the variable's value each time
769 foreach (var varValue in varValues)
770 {
771 using (var loopReader = XmlReader.Create(fragmentStream, FragmentXmlReaderSettings))
772 {
773 // Always overwrite foreach variables.
774 state.Helper.AddVariable(state.Context, varName, varValue, false);
775
776 try
777 {
778 this.PreprocessReader(state, false, loopReader, container, offset);
779 }
780 catch (XmlException e)
781 {
782 this.UpdateCurrentLineNumber(state, loopReader, offset);
783 throw new WixException(ErrorMessages.InvalidXml(state.Context.CurrentSourceLineNumber, "source", e.Message));
784 }
785
786 fragmentStream.Position = 0; // seek back to the beginning for the next loop.
787 }
788 }
789 }
790 }
791
792 /// <summary>
793 /// Processes a pragma processing instruction
794 /// </summary>
795 /// <param name="state"></param>
796 /// <param name="pragmaText">Text from source.</param>
797 /// <param name="parent"></param>
798 private void PreprocessPragma(ProcessingState state, string pragmaText, XContainer parent)
799 {
800 var match = PragmaRegex.Match(pragmaText);
801
802 if (!match.Success)
803 {
804 throw new WixException(ErrorMessages.InvalidPreprocessorPragma(state.Context.CurrentSourceLineNumber, pragmaText));
805 }
806
807 // resolve other variables in the pragma argument(s)
808 var pragmaArgs = state.Helper.PreprocessString(state.Context, match.Groups["pragmaValue"].Value).Trim();
809
810 try
811 {
812 state.Helper.PreprocessPragma(state.Context, match.Groups["pragmaName"].Value.Trim(), pragmaArgs, parent);
813 }
814 catch (Exception e)
815 {
816 throw new WixException(ErrorMessages.PreprocessorExtensionPragmaFailed(state.Context.CurrentSourceLineNumber, pragmaText, e.Message));
817 }
818 }
819
820 /// <summary>
821 /// Gets the next token in an expression.
822 /// </summary>
823 /// <param name="state"></param>
824 /// <param name="originalExpression">Expression to parse.</param>
825 /// <param name="expression">Expression with token removed.</param>
826 /// <param name="stringLiteral">Flag if token is a string literal instead of a variable.</param>
827 /// <returns>Next token.</returns>
828 private string GetNextToken(ProcessingState state, string originalExpression, ref string expression, out bool stringLiteral)
829 {
830 stringLiteral = false;
831 var token = String.Empty;
832 expression = expression.Trim();
833 if (0 == expression.Length)
834 {
835 return String.Empty;
836 }
837
838 if (expression.StartsWith("\"", StringComparison.Ordinal))
839 {
840 stringLiteral = true;
841 var endingQuotes = expression.IndexOf('\"', 1);
842 if (-1 == endingQuotes)
843 {
844 throw new WixException(ErrorMessages.UnmatchedQuotesInExpression(state.Context.CurrentSourceLineNumber, originalExpression));
845 }
846
847 // cut the quotes off the string
848 token = state.Helper.PreprocessString(state.Context, expression.Substring(1, endingQuotes - 1));
849
850 // advance past this string
851 expression = expression.Substring(endingQuotes + 1).Trim();
852 }
853 else if (expression.StartsWith("$(", StringComparison.Ordinal))
854 {
855 // Find the ending paren of the expression
856 var endingParen = -1;
857 var openedCount = 1;
858 for (var i = 2; i < expression.Length; i++)
859 {
860 if ('(' == expression[i])
861 {
862 openedCount++;
863 }
864 else if (')' == expression[i])
865 {
866 openedCount--;
867 }
868
869 if (openedCount == 0)
870 {
871 endingParen = i;
872 break;
873 }
874 }
875
876 if (-1 == endingParen)
877 {
878 throw new WixException(ErrorMessages.UnmatchedParenthesisInExpression(state.Context.CurrentSourceLineNumber, originalExpression));
879 }
880 token = expression.Substring(0, endingParen + 1);
881
882 // Advance past this variable
883 expression = expression.Substring(endingParen + 1).Trim();
884 }
885 else
886 {
887 // Cut the token off at the next equal, space, inequality operator,
888 // or end of string, whichever comes first
889 var space = expression.IndexOf(" ", StringComparison.Ordinal);
890 var equals = expression.IndexOf("=", StringComparison.Ordinal);
891 var lessThan = expression.IndexOf("<", StringComparison.Ordinal);
892 var lessThanEquals = expression.IndexOf("<=", StringComparison.Ordinal);
893 var greaterThan = expression.IndexOf(">", StringComparison.Ordinal);
894 var greaterThanEquals = expression.IndexOf(">=", StringComparison.Ordinal);
895 var notEquals = expression.IndexOf("!=", StringComparison.Ordinal);
896 var equalsNoCase = expression.IndexOf("~=", StringComparison.Ordinal);
897 int closingIndex;
898
899 if (space == -1)
900 {
901 space = Int32.MaxValue;
902 }
903
904 if (equals == -1)
905 {
906 equals = Int32.MaxValue;
907 }
908
909 if (lessThan == -1)
910 {
911 lessThan = Int32.MaxValue;
912 }
913
914 if (lessThanEquals == -1)
915 {
916 lessThanEquals = Int32.MaxValue;
917 }
918
919 if (greaterThan == -1)
920 {
921 greaterThan = Int32.MaxValue;
922 }
923
924 if (greaterThanEquals == -1)
925 {
926 greaterThanEquals = Int32.MaxValue;
927 }
928
929 if (notEquals == -1)
930 {
931 notEquals = Int32.MaxValue;
932 }
933
934 if (equalsNoCase == -1)
935 {
936 equalsNoCase = Int32.MaxValue;
937 }
938
939 closingIndex = Math.Min(space, Math.Min(equals, Math.Min(lessThan, Math.Min(lessThanEquals, Math.Min(greaterThan, Math.Min(greaterThanEquals, Math.Min(equalsNoCase, notEquals)))))));
940
941 if (Int32.MaxValue == closingIndex)
942 {
943 closingIndex = expression.Length;
944 }
945
946 // If the index is 0, we hit an operator, so return it
947 if (0 == closingIndex)
948 {
949 // Length 2 operators
950 if (closingIndex == lessThanEquals || closingIndex == greaterThanEquals || closingIndex == notEquals || closingIndex == equalsNoCase)
951 {
952 closingIndex = 2;
953 }
954 else // Length 1 operators
955 {
956 closingIndex = 1;
957 }
958 }
959
960 // Cut out the new token
961 token = expression.Substring(0, closingIndex).Trim();
962 expression = expression.Substring(closingIndex).Trim();
963 }
964
965 return token;
966 }
967
968 /// <summary>
969 /// Gets the value for a variable.
970 /// </summary>
971 /// <param name="state"></param>
972 /// <param name="originalExpression">Original expression for error message.</param>
973 /// <param name="variable">Variable to evaluate.</param>
974 /// <returns>Value of variable.</returns>
975 private string EvaluateVariable(ProcessingState state, string originalExpression, string variable)
976 {
977 // By default it's a literal and will only be evaluated if it
978 // matches the variable format
979 var varValue = variable;
980
981 if (variable.StartsWith("$(", StringComparison.Ordinal))
982 {
983 try
984 {
985 varValue = state.Helper.PreprocessString(state.Context, variable);
986 }
987 catch (ArgumentNullException)
988 {
989 // non-existent variables are expected
990 varValue = null;
991 }
992 }
993 else if (variable.IndexOf("(", StringComparison.Ordinal) != -1 || variable.IndexOf(")", StringComparison.Ordinal) != -1)
994 {
995 // make sure it doesn't contain parenthesis
996 throw new WixException(ErrorMessages.UnmatchedParenthesisInExpression(state.Context.CurrentSourceLineNumber, originalExpression));
997 }
998 else if (variable.IndexOf("\"", StringComparison.Ordinal) != -1)
999 {
1000 // shouldn't contain quotes
1001 throw new WixException(ErrorMessages.UnmatchedQuotesInExpression(state.Context.CurrentSourceLineNumber, originalExpression));
1002 }
1003
1004 return varValue;
1005 }
1006
1007 /// <summary>
1008 /// Gets the left side value, operator, and right side value of an expression.
1009 /// </summary>
1010 /// <param name="state"></param>
1011 /// <param name="originalExpression">Original expression to evaluate.</param>
1012 /// <param name="expression">Expression modified while processing.</param>
1013 /// <param name="leftValue">Left side value from expression.</param>
1014 /// <param name="operation">Operation in expression.</param>
1015 /// <param name="rightValue">Right side value from expression.</param>
1016 private void GetNameValuePair(ProcessingState state, string originalExpression, ref string expression, out string leftValue, out string operation, out string rightValue)
1017 {
1018 leftValue = this.GetNextToken(state, originalExpression, ref expression, out var stringLiteral);
1019
1020 // If it wasn't a string literal, evaluate it
1021 if (!stringLiteral)
1022 {
1023 leftValue = this.EvaluateVariable(state, originalExpression, leftValue);
1024 }
1025
1026 // Get the operation
1027 operation = this.GetNextToken(state, originalExpression, ref expression, out stringLiteral);
1028 if (IsOperator(operation))
1029 {
1030 if (stringLiteral)
1031 {
1032 throw new WixException(ErrorMessages.UnmatchedQuotesInExpression(state.Context.CurrentSourceLineNumber, originalExpression));
1033 }
1034
1035 rightValue = this.GetNextToken(state, originalExpression, ref expression, out stringLiteral);
1036
1037 // If it wasn't a string literal, evaluate it
1038 if (!stringLiteral)
1039 {
1040 rightValue = this.EvaluateVariable(state, originalExpression, rightValue);
1041 }
1042 }
1043 else
1044 {
1045 // Prepend the token back on the expression since it wasn't an operator
1046 // and put the quotes back on the literal if necessary
1047
1048 if (stringLiteral)
1049 {
1050 operation = "\"" + operation + "\"";
1051 }
1052 expression = (operation + " " + expression).Trim();
1053
1054 // If no operator, just check for existence
1055 operation = "";
1056 rightValue = "";
1057 }
1058 }
1059
1060 /// <summary>
1061 /// Evaluates an expression.
1062 /// </summary>
1063 /// <param name="state"></param>
1064 /// <param name="originalExpression">Original expression to evaluate.</param>
1065 /// <param name="expression">Expression modified while processing.</param>
1066 /// <returns>true if expression evaluates to true.</returns>
1067 private bool EvaluateAtomicExpression(ProcessingState state, string originalExpression, ref string expression)
1068 {
1069 // Quick test to see if the first token is a variable
1070 var startsWithVariable = expression.StartsWith("$(", StringComparison.Ordinal);
1071 this.GetNameValuePair(state, originalExpression, ref expression, out var leftValue, out var operation, out var rightValue);
1072
1073 var expressionValue = false;
1074
1075 // If the variables don't exist, they were evaluated to null
1076 if (null == leftValue || null == rightValue)
1077 {
1078 if (operation.Length > 0)
1079 {
1080 throw new WixException(ErrorMessages.ExpectedVariable(state.Context.CurrentSourceLineNumber, originalExpression));
1081 }
1082
1083 // false expression
1084 }
1085 else if (operation.Length == 0)
1086 {
1087 // There is no right side of the equation.
1088 // If the variable was evaluated, it exists, so the expression is true
1089 if (startsWithVariable)
1090 {
1091 expressionValue = true;
1092 }
1093 else
1094 {
1095 throw new WixException(ErrorMessages.UnexpectedLiteral(state.Context.CurrentSourceLineNumber, originalExpression));
1096 }
1097 }
1098 else
1099 {
1100 leftValue = leftValue.Trim();
1101 rightValue = rightValue.Trim();
1102 if ("=" == operation)
1103 {
1104 if (leftValue == rightValue)
1105 {
1106 expressionValue = true;
1107 }
1108 }
1109 else if ("!=" == operation)
1110 {
1111 if (leftValue != rightValue)
1112 {
1113 expressionValue = true;
1114 }
1115 }
1116 else if ("~=" == operation)
1117 {
1118 if (String.Equals(leftValue, rightValue, StringComparison.OrdinalIgnoreCase))
1119 {
1120 expressionValue = true;
1121 }
1122 }
1123 else
1124 {
1125 // Convert the numbers from strings
1126 int rightInt;
1127 int leftInt;
1128 try
1129 {
1130 rightInt = Int32.Parse(rightValue, CultureInfo.InvariantCulture);
1131 leftInt = Int32.Parse(leftValue, CultureInfo.InvariantCulture);
1132 }
1133 catch (FormatException)
1134 {
1135 throw new WixException(ErrorMessages.IllegalIntegerInExpression(state.Context.CurrentSourceLineNumber, originalExpression));
1136 }
1137 catch (OverflowException)
1138 {
1139 throw new WixException(ErrorMessages.IllegalIntegerInExpression(state.Context.CurrentSourceLineNumber, originalExpression));
1140 }
1141
1142 // Compare the numbers
1143 if ("<" == operation && leftInt < rightInt ||
1144 "<=" == operation && leftInt <= rightInt ||
1145 ">" == operation && leftInt > rightInt ||
1146 ">=" == operation && leftInt >= rightInt)
1147 {
1148 expressionValue = true;
1149 }
1150 }
1151 }
1152
1153 return expressionValue;
1154 }
1155
1156 /// <summary>
1157 /// Gets a sub-expression in parenthesis.
1158 /// </summary>
1159 /// <param name="state"></param>
1160 /// <param name="originalExpression">Original expression to evaluate.</param>
1161 /// <param name="expression">Expression modified while processing.</param>
1162 /// <param name="endSubExpression">Index of end of sub-expression.</param>
1163 /// <returns>Sub-expression in parenthesis.</returns>
1164 private string GetParenthesisExpression(ProcessingState state, string originalExpression, string expression, out int endSubExpression)
1165 {
1166 endSubExpression = 0;
1167
1168 // if the expression doesn't start with parenthesis, leave it alone
1169 if (!expression.StartsWith("(", StringComparison.Ordinal))
1170 {
1171 return expression;
1172 }
1173
1174 // search for the end of the expression with the matching paren
1175 var openParenIndex = 0;
1176 var closeParenIndex = 1;
1177 while (openParenIndex != -1 && openParenIndex < closeParenIndex)
1178 {
1179 closeParenIndex = expression.IndexOf(')', closeParenIndex);
1180 if (closeParenIndex == -1)
1181 {
1182 throw new WixException(ErrorMessages.UnmatchedParenthesisInExpression(state.Context.CurrentSourceLineNumber, originalExpression));
1183 }
1184
1185 if (InsideQuotes(expression, closeParenIndex))
1186 {
1187 // ignore stuff inside quotes (it's a string literal)
1188 }
1189 else
1190 {
1191 // Look to see if there is another open paren before the close paren
1192 // and skip over the open parens while they are in a string literal
1193 do
1194 {
1195 openParenIndex++;
1196 openParenIndex = expression.IndexOf('(', openParenIndex, closeParenIndex - openParenIndex);
1197 }
1198 while (InsideQuotes(expression, openParenIndex));
1199 }
1200
1201 // Advance past the closing paren
1202 closeParenIndex++;
1203 }
1204
1205 endSubExpression = closeParenIndex;
1206
1207 // Return the expression minus the parenthesis
1208 return expression.Substring(1, closeParenIndex - 2);
1209 }
1210
1211 /// <summary>
1212 /// Updates expression based on operation.
1213 /// </summary>
1214 /// <param name="state"></param>
1215 /// <param name="currentValue">State to update.</param>
1216 /// <param name="operation">Operation to apply to current value.</param>
1217 /// <param name="prevResult">Previous result.</param>
1218 private void UpdateExpressionValue(ProcessingState state, ref bool currentValue, PreprocessorOperation operation, bool prevResult)
1219 {
1220 switch (operation)
1221 {
1222 case PreprocessorOperation.And:
1223 currentValue = currentValue && prevResult;
1224 break;
1225 case PreprocessorOperation.Or:
1226 currentValue = currentValue || prevResult;
1227 break;
1228 case PreprocessorOperation.Not:
1229 currentValue = !currentValue;
1230 break;
1231 default:
1232 throw new WixException(ErrorMessages.UnexpectedPreprocessorOperator(state.Context.CurrentSourceLineNumber, operation.ToString()));
1233 }
1234 }
1235
1236 /// <summary>
1237 /// Evaluate an expression.
1238 /// </summary>
1239 /// <param name="state"></param>
1240 /// <param name="expression">Expression to evaluate.</param>
1241 /// <returns>Boolean result of expression.</returns>
1242 private bool EvaluateExpression(ProcessingState state, string expression)
1243 {
1244 var tmpExpression = expression;
1245 return this.EvaluateExpressionRecurse(state, expression, ref tmpExpression, PreprocessorOperation.And, true);
1246 }
1247
1248 /// <summary>
1249 /// Recurse through the expression to evaluate if it is true or false.
1250 /// The expression is evaluated left to right.
1251 /// The expression is case-sensitive (converted to upper case) with the
1252 /// following exceptions: variable names and keywords (and, not, or).
1253 /// Comparisons with = and != are string comparisons.
1254 /// Comparisons with inequality operators must be done on valid integers.
1255 ///
1256 /// The operator precedence is:
1257 /// ""
1258 /// ()
1259 /// &lt;, &gt;, &lt;=, &gt;=, =, !=
1260 /// Not
1261 /// And, Or
1262 ///
1263 /// Valid expressions include:
1264 /// not $(var.B) or not $(var.C)
1265 /// (($(var.A))and $(var.B) ="2")or Not((($(var.C))) and $(var.A))
1266 /// (($(var.A)) and $(var.B) = " 3 ") or $(var.C)
1267 /// $(var.A) and $(var.C) = "3" or $(var.C) and $(var.D) = $(env.windir)
1268 /// $(var.A) and $(var.B)>2 or $(var.B) &lt;= 2
1269 /// $(var.A) != "2"
1270 /// </summary>
1271 /// <param name="state"></param>
1272 /// <param name="originalExpression">The original expression</param>
1273 /// <param name="expression">The expression currently being evaluated</param>
1274 /// <param name="prevResultOperation">The operation to apply to this result</param>
1275 /// <param name="prevResult">The previous result to apply to this result</param>
1276 /// <returns>Boolean to indicate if the expression is true or false</returns>
1277 private bool EvaluateExpressionRecurse(ProcessingState state, string originalExpression, ref string expression, PreprocessorOperation prevResultOperation, bool prevResult)
1278 {
1279 var expressionValue = false;
1280 expression = expression.Trim();
1281 if (expression.Length == 0)
1282 {
1283 throw new WixException(ErrorMessages.UnexpectedEmptySubexpression(state.Context.CurrentSourceLineNumber, originalExpression));
1284 }
1285
1286 // If the expression starts with parenthesis, evaluate it
1287 if (expression.IndexOf('(') == 0)
1288 {
1289 var subExpression = this.GetParenthesisExpression(state, originalExpression, expression, out var endSubExpressionIndex);
1290 expressionValue = this.EvaluateExpressionRecurse(state, originalExpression, ref subExpression, PreprocessorOperation.And, true);
1291
1292 // Now get the rest of the expression that hasn't been evaluated
1293 expression = expression.Substring(endSubExpressionIndex).Trim();
1294 }
1295 else
1296 {
1297 // Check for NOT
1298 if (StartsWithKeyword(expression, PreprocessorOperation.Not))
1299 {
1300 expression = expression.Substring(3).Trim();
1301 if (expression.Length == 0)
1302 {
1303 throw new WixException(ErrorMessages.ExpectedExpressionAfterNot(state.Context.CurrentSourceLineNumber, originalExpression));
1304 }
1305
1306 expressionValue = this.EvaluateExpressionRecurse(state, originalExpression, ref expression, PreprocessorOperation.Not, true);
1307 }
1308 else // Expect a literal
1309 {
1310 expressionValue = this.EvaluateAtomicExpression(state, originalExpression, ref expression);
1311
1312 // Expect the literal that was just evaluated to already be cut off
1313 }
1314 }
1315 this.UpdateExpressionValue(state, ref expressionValue, prevResultOperation, prevResult);
1316
1317 // If there's still an expression left, it must start with AND or OR.
1318 if (expression.Trim().Length > 0)
1319 {
1320 if (StartsWithKeyword(expression, PreprocessorOperation.And))
1321 {
1322 expression = expression.Substring(3);
1323 return this.EvaluateExpressionRecurse(state, originalExpression, ref expression, PreprocessorOperation.And, expressionValue);
1324 }
1325 else if (StartsWithKeyword(expression, PreprocessorOperation.Or))
1326 {
1327 expression = expression.Substring(2);
1328 return this.EvaluateExpressionRecurse(state, originalExpression, ref expression, PreprocessorOperation.Or, expressionValue);
1329 }
1330 else
1331 {
1332 throw new WixException(ErrorMessages.InvalidSubExpression(state.Context.CurrentSourceLineNumber, expression, originalExpression));
1333 }
1334 }
1335
1336 return expressionValue;
1337 }
1338
1339 /// <summary>
1340 /// Update the current line number with the reader's current state.
1341 /// </summary>
1342 /// <param name="state"></param>
1343 /// <param name="reader">The xml reader for the preprocessor.</param>
1344 /// <param name="offset">This is the artificial offset of the line numbers from the reader. Used for the foreach processing.</param>
1345 private void UpdateCurrentLineNumber(ProcessingState state, XmlReader reader, int offset)
1346 {
1347 var lineInfoReader = reader as IXmlLineInfo;
1348 if (null != lineInfoReader)
1349 {
1350 var newLine = lineInfoReader.LineNumber + offset;
1351
1352 if (state.Context.CurrentSourceLineNumber.LineNumber != newLine)
1353 {
1354 state.Context.CurrentSourceLineNumber = new SourceLineNumber(state.Context.CurrentSourceLineNumber.FileName, state.Context.CurrentSourceLineNumber.Parent, newLine);
1355 }
1356 }
1357 }
1358
1359 /// <summary>
1360 /// Pushes a file name on the stack of included files.
1361 /// </summary>
1362 /// <param name="state"></param>
1363 /// <param name="fileName">Name to push on to the stack of included files.</param>
1364 private void PushInclude(ProcessingState state, string fileName)
1365 {
1366 if (1023 < state.CurrentFileStack.Count)
1367 {
1368 throw new WixException(ErrorMessages.TooDeeplyIncluded(state.Context.CurrentSourceLineNumber, state.CurrentFileStack.Count));
1369 }
1370
1371 var path = Path.GetFullPath(fileName);
1372
1373 state.CurrentFileStack.Push(path);
1374 state.SourceStack.Push(state.Context.CurrentSourceLineNumber);
1375 state.Context.CurrentSourceLineNumber = new SourceLineNumber(path, state.Context.CurrentSourceLineNumber);
1376 state.IncludeNextStack.Push(true);
1377 }
1378
1379 /// <summary>
1380 /// Pops a file name from the stack of included files.
1381 /// </summary>
1382 private void PopInclude(ProcessingState state)
1383 {
1384 state.Context.CurrentSourceLineNumber = state.SourceStack.Pop();
1385
1386 state.CurrentFileStack.Pop();
1387 state.IncludeNextStack.Pop();
1388 }
1389
1390 /// <summary>
1391 /// Go through search paths, looking for a matching include file.
1392 /// Start the search in the directory of the source file, then go
1393 /// through the search paths in the order given on the command line
1394 /// (leftmost first, ...).
1395 /// </summary>
1396 /// <param name="state"></param>
1397 /// <param name="includePath">User-specified path to the included file (usually just the file name).</param>
1398 /// <returns>Returns a FileInfo for the found include file, or null if the file cannot be found.</returns>
1399 private string GetIncludeFile(ProcessingState state, string includePath)
1400 {
1401 string finalIncludePath = null;
1402
1403 includePath = includePath.Trim();
1404
1405 // remove quotes (only if they match)
1406 if ((includePath.StartsWith("\"", StringComparison.Ordinal) && includePath.EndsWith("\"", StringComparison.Ordinal)) ||
1407 (includePath.StartsWith("'", StringComparison.Ordinal) && includePath.EndsWith("'", StringComparison.Ordinal)))
1408 {
1409 includePath = includePath.Substring(1, includePath.Length - 2);
1410 }
1411
1412 // check if the include file is a full path
1413 if (Path.IsPathRooted(includePath))
1414 {
1415 if (File.Exists(includePath))
1416 {
1417 finalIncludePath = includePath;
1418 }
1419 }
1420 else // relative path
1421 {
1422 // build a string to test the directory containing the source file first
1423 var currentFolder = state.CurrentFileStack.Peek();
1424 var includeTestPath = Path.Combine(Path.GetDirectoryName(currentFolder), includePath);
1425
1426 // test the source file directory
1427 if (File.Exists(includeTestPath))
1428 {
1429 finalIncludePath = includeTestPath;
1430 }
1431 else if (state.Context.IncludeSearchPaths != null) // test all search paths in the order specified on the command line
1432 {
1433 foreach (var includeSearchPath in state.Context.IncludeSearchPaths)
1434 {
1435 // if the path exists, we have found the final string
1436 includeTestPath = Path.Combine(includeSearchPath, includePath);
1437 if (File.Exists(includeTestPath))
1438 {
1439 finalIncludePath = includeTestPath;
1440 break;
1441 }
1442 }
1443 }
1444 }
1445
1446 return finalIncludePath;
1447 }
1448
1449 private void PreProcess(ProcessingState state)
1450 {
1451 if (state.Context.Extensions == null)
1452 {
1453 return;
1454 }
1455
1456 foreach (var extension in state.Context.Extensions)
1457 {
1458 if (extension.Prefixes != null)
1459 {
1460 foreach (var prefix in extension.Prefixes)
1461 {
1462 if (!state.ExtensionsByPrefix.TryGetValue(prefix, out var collidingExtension))
1463 {
1464 state.ExtensionsByPrefix.Add(prefix, extension);
1465 }
1466 else
1467 {
1468 this.Messaging.Write(ErrorMessages.DuplicateExtensionPreprocessorType(extension.GetType().ToString(), prefix, collidingExtension.GetType().ToString()));
1469 }
1470 }
1471 }
1472
1473 extension.PrePreprocess(state.Context);
1474 }
1475 }
1476
1477 private void PostProcess(ProcessingState state, IPreprocessResult result)
1478 {
1479 if (state.Context.Extensions == null)
1480 {
1481 return;
1482 }
1483
1484 foreach (var extension in state.Context.Extensions)
1485 {
1486 extension.PostPreprocess(result);
1487 }
1488 }
1489
1490 private class ProcessingState
1491 {
1492 public ProcessingState(IServiceProvider serviceProvider, IPreprocessContext context)
1493 {
1494 var path = Path.GetFullPath(context.SourcePath);
1495
1496 this.Context = context;
1497 this.Context.CurrentSourceLineNumber = new SourceLineNumber(path);
1498 this.Context.Variables = this.Context.Variables == null ? new Dictionary<string, string>() : new Dictionary<string, string>(this.Context.Variables);
1499
1500 this.Helper = serviceProvider.GetService<IPreprocessHelper>();
1501 }
1502
1503 public IPreprocessContext Context { get; }
1504
1505 public IPreprocessHelper Helper { get; }
1506
1507 public List<IIncludedFile> IncludedFiles { get; } = new List<IIncludedFile>();
1508
1509 public XDocument Output { get; } = new XDocument();
1510
1511 public Stack<string> CurrentFileStack { get; } = new Stack<string>();
1512
1513 public Dictionary<string, IPreprocessorExtension> ExtensionsByPrefix { get; } = new Dictionary<string, IPreprocessorExtension>();
1514
1515 public Stack<bool> IncludeNextStack { get; } = new Stack<bool>();
1516
1517 public Stack<SourceLineNumber> SourceStack { get; } = new Stack<SourceLineNumber>();
1518 }
1519 }
1520}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3using System;
4using System.Reflection;
5using System.Runtime.InteropServices;
6
7[assembly: AssemblyCulture("")]
8[assembly: CLSCompliant(false)]
9[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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Threading;
8 using WixToolset.Data;
9 using WixToolset.Extensibility;
10 using WixToolset.Extensibility.Data;
11 using WixToolset.Extensibility.Services;
12
13 internal class ResolveContext : IResolveContext
14 {
15 internal ResolveContext(IServiceProvider serviceProvider)
16 {
17 this.ServiceProvider = serviceProvider;
18 }
19
20 public IServiceProvider ServiceProvider { get; }
21
22 public IReadOnlyCollection<IBindPath> BindPaths { get; set; }
23
24 public IReadOnlyCollection<IResolverExtension> Extensions { get; set; }
25
26 public IReadOnlyCollection<IExtensionData> ExtensionData { get; set; }
27
28 public IReadOnlyCollection<string> FilterCultures { get; set; }
29
30 public string IntermediateFolder { get; set; }
31
32 public Intermediate IntermediateRepresentation { get; set; }
33
34 public IReadOnlyCollection<Localization> Localizations { get; set; }
35
36 public IVariableResolver VariableResolver { get; set; }
37
38 public bool AllowUnresolvedVariables { get; set; }
39
40 public CancellationToken CancellationToken { get; set; }
41 }
42}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System.Collections.Generic;
6 using WixToolset.Extensibility.Data;
7
8 internal class ResolveFileResult : IResolveFileResult
9 {
10 public string Path { get; set; }
11
12 public IReadOnlyCollection<string> CheckedPaths { get; set; }
13 }
14}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System.Collections.Generic;
6 using WixToolset.Data;
7 using WixToolset.Extensibility.Data;
8
9 internal class ResolveResult : IResolveResult
10 {
11 public int? Codepage { get; set; }
12
13 public int? SummaryInformationCodepage { get; set; }
14
15 public int? PackageLcid { get; set; }
16
17 public IReadOnlyCollection<IDelayedField> DelayedFields { get; set; }
18
19 public IReadOnlyCollection<IExpectedExtractFile> ExpectedEmbeddedFiles { get; set; }
20
21 public Intermediate IntermediateRepresentation { get; set; }
22 }
23}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using WixToolset.Extensibility.Data;
6
7 /// <summary>
8 /// Data returned from build file manager ResolveCabinet callback.
9 /// </summary>
10 internal class ResolvedCabinet : IResolvedCabinet
11 {
12 /// <summary>
13 /// Gets or sets the build option for the resolved cabinet.
14 /// </summary>
15 public CabinetBuildOption BuildOption { get; set; }
16
17 /// <summary>
18 /// Gets or sets the path for the resolved cabinet.
19 /// </summary>
20 public string Path { get; set; }
21 }
22}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.Linq;
9 using WixToolset.Core.Bind;
10 using WixToolset.Data;
11 using WixToolset.Data.Symbols;
12 using WixToolset.Extensibility;
13 using WixToolset.Extensibility.Data;
14 using WixToolset.Extensibility.Services;
15
16 /// <summary>
17 /// Resolver for the WiX toolset.
18 /// </summary>
19 internal class Resolver : IResolver
20 {
21 internal Resolver(IServiceProvider serviceProvider)
22 {
23 this.ServiceProvider = serviceProvider;
24
25 this.Messaging = serviceProvider.GetService<IMessaging>();
26 }
27
28 private IServiceProvider ServiceProvider { get; }
29
30 private IMessaging Messaging { get; }
31
32 public IResolveResult Resolve(IResolveContext context)
33 {
34 foreach (var extension in context.Extensions)
35 {
36 extension.PreResolve(context);
37 }
38
39 ResolveResult resolveResult = null;
40 try
41 {
42 var filteredLocalizations = FilterLocalizations(context);
43
44 var variableResolver = this.CreateVariableResolver(context, filteredLocalizations);
45
46 this.LocalizeUI(variableResolver, context.IntermediateRepresentation);
47
48 resolveResult = this.DoResolve(context, variableResolver);
49
50 var primaryLocalization = filteredLocalizations.FirstOrDefault();
51
52 if (primaryLocalization != null)
53 {
54 this.TryGetCultureInfo(primaryLocalization.Culture, out var cultureInfo);
55
56 resolveResult.Codepage = primaryLocalization.Codepage ?? cultureInfo?.TextInfo.ANSICodePage;
57
58 resolveResult.SummaryInformationCodepage = primaryLocalization.SummaryInformationCodepage ?? primaryLocalization.Codepage ?? cultureInfo?.TextInfo.ANSICodePage;
59
60 resolveResult.PackageLcid = cultureInfo?.LCID;
61 }
62 }
63 finally
64 {
65 foreach (var extension in context.Extensions)
66 {
67 extension.PostResolve(resolveResult);
68 }
69 }
70
71 return resolveResult;
72 }
73
74 private ResolveResult DoResolve(IResolveContext context, IVariableResolver variableResolver)
75 {
76 var buildingPatch = context.IntermediateRepresentation.Sections.Any(s => s.Type == SectionType.Patch);
77
78 var filesWithEmbeddedFiles = new ExtractEmbeddedFiles();
79
80 IReadOnlyCollection<DelayedField> delayedFields;
81 {
82 var command = new ResolveFieldsCommand();
83 command.Messaging = this.Messaging;
84 command.BuildingPatch = buildingPatch;
85 command.VariableResolver = variableResolver;
86 command.BindPaths = context.BindPaths;
87 command.Extensions = context.Extensions;
88 command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles;
89 command.IntermediateFolder = context.IntermediateFolder;
90 command.Intermediate = context.IntermediateRepresentation;
91 command.SupportDelayedResolution = true;
92 command.AllowUnresolvedVariables = context.AllowUnresolvedVariables;
93 command.Execute();
94
95 delayedFields = command.DelayedFields;
96 }
97
98#if TODO_PATCHING
99 if (context.IntermediateRepresentation.SubStorages != null)
100 {
101 foreach (SubStorage transform in context.IntermediateRepresentation.SubStorages)
102 {
103 var command = new ResolveFieldsCommand();
104 command.BuildingPatch = buildingPatch;
105 command.BindVariableResolver = context.WixVariableResolver;
106 command.BindPaths = context.BindPaths;
107 command.Extensions = context.Extensions;
108 command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles;
109 command.IntermediateFolder = context.IntermediateFolder;
110 command.Intermediate = context.IntermediateRepresentation;
111 command.SupportDelayedResolution = false;
112 command.Execute();
113 }
114 }
115#endif
116
117 var expectedEmbeddedFiles = filesWithEmbeddedFiles.GetExpectedEmbeddedFiles();
118
119 context.IntermediateRepresentation.UpdateLevel(IntermediateLevels.Resolved);
120
121 return new ResolveResult
122 {
123 ExpectedEmbeddedFiles = expectedEmbeddedFiles,
124 DelayedFields = delayedFields,
125 IntermediateRepresentation = context.IntermediateRepresentation
126 };
127 }
128
129 /// <summary>
130 /// Localize dialogs and controls.
131 /// </summary>
132 private void LocalizeUI(IVariableResolver variableResolver, Intermediate intermediate)
133 {
134 foreach (var section in intermediate.Sections)
135 {
136 foreach (var symbol in section.Symbols.OfType<DialogSymbol>())
137 {
138 if (variableResolver.TryGetLocalizedControl(symbol.Id.Id, null, out var localizedControl))
139 {
140 if (CompilerConstants.IntegerNotSet != localizedControl.X)
141 {
142 symbol.HCentering = localizedControl.X;
143 }
144
145 if (CompilerConstants.IntegerNotSet != localizedControl.Y)
146 {
147 symbol.VCentering = localizedControl.Y;
148 }
149
150 if (CompilerConstants.IntegerNotSet != localizedControl.Width)
151 {
152 symbol.Width = localizedControl.Width;
153 }
154
155 if (CompilerConstants.IntegerNotSet != localizedControl.Height)
156 {
157 symbol.Height = localizedControl.Height;
158 }
159
160 symbol.RightAligned |= localizedControl.RightAligned;
161 symbol.RightToLeft |= localizedControl.RightToLeft;
162 symbol.LeftScroll |= localizedControl.LeftScroll;
163
164 if (!String.IsNullOrEmpty(localizedControl.Text))
165 {
166 symbol.Title = localizedControl.Text;
167 }
168 }
169 }
170
171 foreach (var symbol in section.Symbols.OfType<ControlSymbol>())
172 {
173 if (variableResolver.TryGetLocalizedControl(symbol.DialogRef, symbol.Control, out var localizedControl))
174 {
175 if (CompilerConstants.IntegerNotSet != localizedControl.X)
176 {
177 symbol.X = localizedControl.X;
178 }
179
180 if (CompilerConstants.IntegerNotSet != localizedControl.Y)
181 {
182 symbol.Y = localizedControl.Y;
183 }
184
185 if (CompilerConstants.IntegerNotSet != localizedControl.Width)
186 {
187 symbol.Width = localizedControl.Width;
188 }
189
190 if (CompilerConstants.IntegerNotSet != localizedControl.Height)
191 {
192 symbol.Height = localizedControl.Height;
193 }
194
195 symbol.RightAligned |= localizedControl.RightAligned;
196 symbol.RightToLeft |= localizedControl.RightToLeft;
197 symbol.LeftScroll |= localizedControl.LeftScroll;
198
199 if (!String.IsNullOrEmpty(localizedControl.Text))
200 {
201 symbol.Text = localizedControl.Text;
202 }
203 }
204 }
205 }
206 }
207
208 private IVariableResolver CreateVariableResolver(IResolveContext context, IEnumerable<Localization> filteredLocalizations)
209 {
210 var variableResolver = this.ServiceProvider.GetService<IVariableResolver>();
211
212 foreach (var localization in filteredLocalizations)
213 {
214 variableResolver.AddLocalization(localization);
215 }
216
217 // Gather all the wix variables.
218 var wixVariableSymbols = context.IntermediateRepresentation.Sections.SelectMany(s => s.Symbols).OfType<WixVariableSymbol>();
219 foreach (var symbol in wixVariableSymbols)
220 {
221 variableResolver.AddVariable(symbol.SourceLineNumbers, symbol.Id.Id, symbol.Value, symbol.Overridable);
222 }
223
224 return variableResolver;
225 }
226
227 private bool TryGetCultureInfo(string culture, out CultureInfo cultureInfo)
228 {
229 cultureInfo = null;
230
231 if (!String.IsNullOrEmpty(culture))
232 {
233 try
234 {
235 cultureInfo = new CultureInfo(culture, useUserOverride: false);
236 }
237 catch
238 {
239 this.Messaging.Write("");
240 }
241 }
242
243 return cultureInfo != null;
244 }
245
246 private static IEnumerable<Localization> FilterLocalizations(IResolveContext context)
247 {
248 var result = new List<Localization>();
249 var filter = CalculateCultureFilter(context);
250
251 var localizations = context.Localizations.Concat(context.IntermediateRepresentation.Localizations).ToList();
252
253 AddFilteredLocalizations(result, filter, localizations);
254
255 // Filter localizations provided by extensions with data.
256 var creator = context.ServiceProvider.GetService<ISymbolDefinitionCreator>();
257
258 foreach (var data in context.ExtensionData)
259 {
260 var library = data.GetLibrary(creator);
261
262 if (library?.Localizations != null && library.Localizations.Any())
263 {
264 var extensionFilter = (!filter.Any() && data.DefaultCulture != null) ? new[] { data.DefaultCulture } : filter;
265
266 AddFilteredLocalizations(result, extensionFilter, library.Localizations);
267 }
268 }
269
270 return result;
271 }
272
273 private static IEnumerable<string> CalculateCultureFilter(IResolveContext context)
274 {
275 var filter = context.FilterCultures ?? Array.Empty<string>();
276
277 // If no filter was specified, look for a language neutral localization file specified
278 // from the command-line (not embedded in the intermediate). If found, filter on language
279 // neutral.
280 if (!filter.Any() && context.Localizations.Any(l => String.IsNullOrEmpty(l.Culture)))
281 {
282 filter = new[] { String.Empty };
283 }
284
285 return filter;
286 }
287
288 private static void AddFilteredLocalizations(List<Localization> result, IEnumerable<string> filter, IEnumerable<Localization> localizations)
289 {
290 // If there is no filter, return all localizations.
291 if (!filter.Any())
292 {
293 result.AddRange(localizations);
294 }
295 else // filter localizations in order specified by the filter
296 {
297 foreach (var culture in filter)
298 {
299 result.AddRange(localizations.Where(l => culture.Equals(l.Culture, StringComparison.OrdinalIgnoreCase) || String.IsNullOrEmpty(l.Culture)));
300 }
301 }
302 }
303 }
304}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 internal class SourceFile
6 {
7 public SourceFile(string sourcePath, string outputPath)
8 {
9 this.SourcePath = sourcePath;
10 this.OutputPath = outputPath;
11 }
12
13 public string OutputPath { get; }
14
15 public string SourcePath { get; }
16 }
17}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System;
6 using WixToolset.Extensibility.Data;
7
8 internal class UnbindContext : IUnbindContext
9 {
10 internal UnbindContext(IServiceProvider serviceProvider)
11 {
12 this.ServiceProvider = serviceProvider;
13 }
14
15 public IServiceProvider ServiceProvider { get; }
16
17 public string ExportBasePath { get; set; }
18
19 public string InputFilePath { get; set; }
20
21 public string IntermediateFolder { get; set; }
22
23 public bool IsAdminImage { get; set; }
24
25 public bool SuppressExtractCabinets { get; set; }
26
27 public bool SuppressDemodularization { get; set; }
28 }
29}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using WixToolset.Data;
9 using WixToolset.Extensibility;
10 using WixToolset.Extensibility.Services;
11
12 /// <summary>
13 /// Unbinder core of the WiX toolset.
14 /// </summary>
15 internal sealed class Unbinder : IUnbinder
16 {
17 public Unbinder(IServiceProvider serviceProvider)
18 {
19 this.ServiceProvider = serviceProvider;
20
21 var extensionManager = this.ServiceProvider.GetService<IExtensionManager>();
22 this.BackendFactories = extensionManager.GetServices<IBackendFactory>();
23 }
24
25 public IServiceProvider ServiceProvider { get; }
26
27 public IEnumerable<IBackendFactory> BackendFactories { get; }
28
29 /// <summary>
30 /// Gets or sets whether the input msi is an admin image.
31 /// </summary>
32 /// <value>Set to true if the input msi is part of an admin image.</value>
33 public bool IsAdminImage { get; set; }
34
35 /// <summary>
36 /// Gets or sets the option to suppress demodularizing values.
37 /// </summary>
38 /// <value>The option to suppress demodularizing values.</value>
39 public bool SuppressDemodularization { get; set; }
40
41 /// <summary>
42 /// Gets or sets the option to suppress extracting cabinets.
43 /// </summary>
44 /// <value>The option to suppress extracting cabinets.</value>
45 public bool SuppressExtractCabinets { get; set; }
46
47 /// <summary>
48 /// Gets or sets the temporary path for the Binder. If left null, the binder
49 /// will use %TEMP% environment variable.
50 /// </summary>
51 /// <value>Path to temp files.</value>
52 public string TempFilesLocation => Path.GetTempPath();
53
54 /// <summary>
55 /// Unbind a Windows Installer file.
56 /// </summary>
57 /// <param name="file">The Windows Installer file.</param>
58 /// <param name="outputType">The type of output to create.</param>
59 /// <param name="exportBasePath">The path where files should be exported.</param>
60 /// <returns>The output representing the database.</returns>
61 public Intermediate Unbind(string file, OutputType outputType, string exportBasePath)
62 {
63 if (!File.Exists(file))
64 {
65 if (OutputType.Transform == outputType)
66 {
67 throw new WixException(ErrorMessages.FileNotFound(null, file, "Transform"));
68 }
69 else
70 {
71 throw new WixException(ErrorMessages.FileNotFound(null, file, "Database"));
72 }
73 }
74
75 // if we don't have the temporary files object yet, get one
76 Directory.CreateDirectory(this.TempFilesLocation); // ensure the base path is there
77
78 var context = new UnbindContext(this.ServiceProvider);
79 context.InputFilePath = file;
80 context.ExportBasePath = exportBasePath;
81 context.IntermediateFolder = this.TempFilesLocation;
82 context.IsAdminImage = this.IsAdminImage;
83 context.SuppressDemodularization = this.SuppressDemodularization;
84 context.SuppressExtractCabinets = this.SuppressExtractCabinets;
85
86 foreach (var factory in this.BackendFactories)
87 {
88 if (factory.TryCreateBackend(outputType.ToString(), file, out var backend))
89 {
90 return backend.Unbind(context);
91 }
92 }
93
94 // TODO: Display message that could not find a unbinder for output type?
95
96 return null;
97 }
98 }
99}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using WixToolset.Extensibility.Services;
6
7 internal class VariableResolution : IVariableResolution
8 {
9 /// <summary>
10 /// Indicates whether the variable should be delay resolved.
11 /// </summary>
12 public bool DelayedResolve { get; set; }
13
14 /// <summary>
15 /// Indicates whether the value is the default value of the variable.
16 /// </summary>
17 public bool IsDefault { get; set; }
18
19 /// <summary>
20 /// Indicates whether the value changed.
21 /// </summary>
22 public bool UpdatedValue { get; set; }
23
24 /// <summary>
25 /// Resolved value.
26 /// </summary>
27 public string Value { get; set; }
28 }
29} \ 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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Text;
8 using WixToolset.Data;
9 using WixToolset.Data.Bind;
10 using WixToolset.Extensibility.Services;
11
12 /// <summary>
13 /// WiX variable resolver.
14 /// </summary>
15 internal class VariableResolver : IVariableResolver
16 {
17 private readonly Dictionary<string, BindVariable> locVariables;
18 private readonly Dictionary<string, BindVariable> wixVariables;
19 private readonly Dictionary<string, LocalizedControl> localizedControls;
20
21 /// <summary>
22 /// Instantiate a new VariableResolver.
23 /// </summary>
24 internal VariableResolver(IServiceProvider serviceProvider)
25 {
26 this.ServiceProvider = serviceProvider;
27 this.Messaging = serviceProvider.GetService<IMessaging>();
28
29 this.locVariables = new Dictionary<string, BindVariable>();
30 this.wixVariables = new Dictionary<string, BindVariable>();
31 this.localizedControls = new Dictionary<string, LocalizedControl>();
32 }
33
34 private IServiceProvider ServiceProvider { get; }
35
36 private IMessaging Messaging { get; }
37
38 public int VariableCount => this.wixVariables.Count;
39
40 public void AddLocalization(Localization localization)
41 {
42 foreach (var variable in localization.Variables)
43 {
44 if (!TryAddWixVariable(this.locVariables, variable))
45 {
46 this.Messaging.Write(ErrorMessages.DuplicateLocalizationIdentifier(variable.SourceLineNumbers, variable.Id));
47 }
48 }
49
50 foreach (KeyValuePair<string, LocalizedControl> localizedControl in localization.LocalizedControls)
51 {
52 if (!this.localizedControls.ContainsKey(localizedControl.Key))
53 {
54 this.localizedControls.Add(localizedControl.Key, localizedControl.Value);
55 }
56 }
57 }
58
59 public void AddVariable(SourceLineNumber sourceLineNumber, string name, string value, bool overridable)
60 {
61 var bindVariable = new BindVariable { Id = name, Value = value, Overridable = overridable, SourceLineNumbers = sourceLineNumber };
62
63 if (!TryAddWixVariable(this.wixVariables, bindVariable))
64 {
65 this.Messaging.Write(ErrorMessages.WixVariableCollision(sourceLineNumber, name));
66 }
67 }
68
69 public IVariableResolution ResolveVariables(SourceLineNumber sourceLineNumbers, string value)
70 {
71 return this.ResolveVariables(sourceLineNumbers, value, errorOnUnknown: true);
72 }
73
74 public bool TryGetLocalizedControl(string dialog, string control, out LocalizedControl localizedControl)
75 {
76 var key = LocalizedControl.GetKey(dialog, control);
77 return this.localizedControls.TryGetValue(key, out localizedControl);
78 }
79
80 public IVariableResolution ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool errorOnUnknown)
81 {
82 var start = 0;
83 var defaulted = true;
84 var delayed = false;
85 var updated = false;
86
87 while (Common.TryParseWixVariable(value, start, out var parsed))
88 {
89 var variableNamespace = parsed.Namespace;
90 var variableId = parsed.Name;
91 var variableDefaultValue = parsed.DefaultValue;
92
93 // check for an escape sequence of !! indicating the match is not a variable expression
94 if (0 < parsed.Index && '!' == value[parsed.Index - 1])
95 {
96 var sb = new StringBuilder(value);
97 sb.Remove(parsed.Index - 1, 1);
98 value = sb.ToString();
99
100 updated = true;
101 start = parsed.Index + parsed.Length - 1;
102
103 continue;
104 }
105
106 string resolvedValue = null;
107
108 if ("loc" == variableNamespace)
109 {
110 // localization variables do not support inline default values
111 if (variableDefaultValue != null)
112 {
113 this.Messaging.Write(ErrorMessages.IllegalInlineLocVariable(sourceLineNumbers, variableId, variableDefaultValue));
114 continue;
115 }
116
117 if (this.locVariables.TryGetValue(variableId, out var bindVariable))
118 {
119 resolvedValue = bindVariable.Value;
120 }
121 }
122 else if ("wix" == variableNamespace)
123 {
124 if (this.wixVariables.TryGetValue(variableId, out var bindVariable))
125 {
126 resolvedValue = bindVariable.Value ?? String.Empty;
127 defaulted = false;
128 }
129 else if (null != variableDefaultValue) // default the resolved value to the inline value if one was specified
130 {
131 resolvedValue = variableDefaultValue;
132 }
133 }
134
135 if ("bind" == variableNamespace)
136 {
137 // Can't resolve these yet, but keep track of where we find them so they can be resolved later with less effort.
138 delayed = true;
139 start = parsed.Index + parsed.Length - 1;
140 }
141 else
142 {
143 // insert the resolved value if it was found or display an error
144 if (null != resolvedValue)
145 {
146 if (parsed.Index == 0 && parsed.Length == value.Length)
147 {
148 value = resolvedValue;
149 }
150 else
151 {
152 var sb = new StringBuilder(value);
153 sb.Remove(parsed.Index, parsed.Length);
154 sb.Insert(parsed.Index, resolvedValue);
155 value = sb.ToString();
156 }
157
158 updated = true;
159 start = parsed.Index;
160 }
161 else
162 {
163 if ("loc" == variableNamespace && errorOnUnknown) // unresolved loc variable
164 {
165 this.Messaging.Write(ErrorMessages.LocalizationVariableUnknown(sourceLineNumbers, variableId));
166 }
167 else if ("wix" == variableNamespace && errorOnUnknown) // unresolved wix variable
168 {
169 this.Messaging.Write(ErrorMessages.WixVariableUnknown(sourceLineNumbers, variableId));
170 }
171
172 start = parsed.Index + parsed.Length;
173 }
174 }
175 }
176
177 return new VariableResolution
178 {
179 DelayedResolve = delayed,
180 IsDefault = defaulted,
181 UpdatedValue = updated,
182 Value = value,
183 };
184 }
185
186 private static bool TryAddWixVariable(IDictionary<string, BindVariable> variables, BindVariable variable)
187 {
188 if (!variables.TryGetValue(variable.Id, out var existingWixVariableRow) || (existingWixVariableRow.Overridable && !variable.Overridable))
189 {
190 variables[variable.Id] = variable;
191 return true;
192 }
193
194 return variable.Overridable;
195 }
196 }
197}
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project Sdk="Microsoft.NET.Sdk">
5 <PropertyGroup>
6 <TargetFrameworks>netstandard2.0</TargetFrameworks>
7 <TargetFrameworks Condition=" '$(Configuration)'=='Release' ">$(TargetFrameworks);net461;net472</TargetFrameworks>
8 <Description>Core</Description>
9 <Title>WiX Toolset Core</Title>
10 <DebugType>embedded</DebugType>
11 <PublishRepositoryUrl>true</PublishRepositoryUrl>
12 <NBGV_EmitThisAssemblyClass>true</NBGV_EmitThisAssemblyClass>
13 <CreateDocumentationFile>true</CreateDocumentationFile>
14 </PropertyGroup>
15
16 <ItemGroup>
17 <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
18 <_Parameter1>WixToolset.Core.TestPackage, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9967ec28982f42ee51a47dd5204315975a6ed69294b982146a99a70130a2fa13e226aaddde14c17d1bf3af69e8956d69a86585e74d208efcc5ac98a0686055327b2e87960d3c39bf3a6bc1e572863327d19dbf4fd2616dda124dbea260755a2d1d39d3cf1049ea526493eb2bf996b8ad985e3012308529e5b9b0f5cd5fa04bd</_Parameter1>
19 </AssemblyAttribute>
20 <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
21 <_Parameter1>WixToolsetTest.Core.Burn, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9967ec28982f42ee51a47dd5204315975a6ed69294b982146a99a70130a2fa13e226aaddde14c17d1bf3af69e8956d69a86585e74d208efcc5ac98a0686055327b2e87960d3c39bf3a6bc1e572863327d19dbf4fd2616dda124dbea260755a2d1d39d3cf1049ea526493eb2bf996b8ad985e3012308529e5b9b0f5cd5fa04bd</_Parameter1>
22 </AssemblyAttribute>
23 <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
24 <_Parameter1>WixToolsetTest.CoreIntegration, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a9967ec28982f42ee51a47dd5204315975a6ed69294b982146a99a70130a2fa13e226aaddde14c17d1bf3af69e8956d69a86585e74d208efcc5ac98a0686055327b2e87960d3c39bf3a6bc1e572863327d19dbf4fd2616dda124dbea260755a2d1d39d3cf1049ea526493eb2bf996b8ad985e3012308529e5b9b0f5cd5fa04bd</_Parameter1>
25 </AssemblyAttribute>
26 </ItemGroup>
27
28 <ItemGroup>
29 <PackageReference Include="WixToolset.Data" Version="4.0.*" />
30 <PackageReference Include="WixToolset.Extensibility" Version="4.0.*" />
31 <PackageReference Include="WixToolset.Core.Native" Version="4.0.*" />
32 </ItemGroup>
33
34 <!--
35 These package references are duplicated in WixToolset.Core.TestPackage.csproj. If
36 you update these here, be sure to update them there.
37 -->
38 <ItemGroup>
39 <PackageReference Include="System.Text.Encoding.CodePages" Version="4.6.0" />
40 <PackageReference Include="NuGet.Versioning" Version="5.6.0" />
41 </ItemGroup>
42
43 <ItemGroup>
44 <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
45 <PackageReference Include="Nerdbank.GitVersioning" Version="3.3.37" PrivateAssets="All" />
46 </ItemGroup>
47</Project>
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 @@
1<ProjectConfiguration>
2 <Settings>
3 <AdditionalFilesToIncludeForProject>
4 <Value>..\..\version.json</Value>
5 </AdditionalFilesToIncludeForProject>
6 </Settings>
7</ProjectConfiguration> \ 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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using System;
6 using System.Collections.Generic;
7 using WixToolset.Core.CommandLine;
8 using WixToolset.Core.ExtensibilityServices;
9 using WixToolset.Data;
10 using WixToolset.Extensibility.Data;
11 using WixToolset.Extensibility.Services;
12
13 internal class WixToolsetServiceProvider : IWixToolsetCoreServiceProvider
14 {
15 public WixToolsetServiceProvider()
16 {
17 this.CreationFunctions = new Dictionary<Type, Func<IWixToolsetCoreServiceProvider, Dictionary<Type, object>, object>>();
18 this.Singletons = new Dictionary<Type, object>();
19
20 // Singletons.
21 this.AddService((provider, singletons) => AddSingleton<IExtensionManager>(singletons, new ExtensionManager(provider)));
22 this.AddService((provider, singletons) => AddSingleton<IMessaging>(singletons, new Messaging()));
23 this.AddService((provider, singletons) => AddSingleton<ISymbolDefinitionCreator>(singletons, new SymbolDefinitionCreator(provider)));
24 this.AddService((provider, singletons) => AddSingleton<IParseHelper>(singletons, new ParseHelper(provider)));
25 this.AddService((provider, singletons) => AddSingleton<IPreprocessHelper>(singletons, new PreprocessHelper(provider)));
26 this.AddService((provider, singletons) => AddSingleton<IBackendHelper>(singletons, new BackendHelper(provider)));
27 this.AddService((provider, singletons) => AddSingleton<IPathResolver>(singletons, new PathResolver()));
28 this.AddService((provider, singletons) => AddSingleton<IWixBranding>(singletons, new WixBranding()));
29
30 // Transients.
31 this.AddService<ICommandLineArguments>((provider, singletons) => new CommandLineArguments(provider));
32 this.AddService<ICommandLineContext>((provider, singletons) => new CommandLineContext(provider));
33 this.AddService<ICommandLine>((provider, singletons) => new CommandLine.CommandLine(provider));
34 this.AddService<IPreprocessContext>((provider, singletons) => new PreprocessContext(provider));
35 this.AddService<ICompileContext>((provider, singletons) => new CompileContext(provider));
36 this.AddService<ILibraryContext>((provider, singletons) => new LibraryContext(provider));
37 this.AddService<ILinkContext>((provider, singletons) => new LinkContext(provider));
38 this.AddService<IResolveContext>((provider, singletons) => new ResolveContext(provider));
39 this.AddService<IBindContext>((provider, singletons) => new BindContext(provider));
40 this.AddService<IDecompileContext>((provider, singletons) => new DecompileContext(provider));
41 this.AddService<ILayoutContext>((provider, singletons) => new LayoutContext(provider));
42 this.AddService<IInscribeContext>((provider, singletons) => new InscribeContext(provider));
43 this.AddService<IUnbindContext>((provider, singletons) => new UnbindContext(provider));
44
45 this.AddService<IBindFileWithPath>((provider, singletons) => new BindFileWithPath());
46 this.AddService<IBindPath>((provider, singletons) => new BindPath());
47 this.AddService<IBindResult>((provider, singletons) => new BindResult());
48 this.AddService<IComponentKeyPath>((provider, singletons) => new ComponentKeyPath());
49 this.AddService<IDecompileResult>((provider, singletons) => new DecompileResult());
50 this.AddService<IIncludedFile>((provider, singletons) => new IncludedFile());
51 this.AddService<IPreprocessResult>((provider, singletons) => new PreprocessResult());
52 this.AddService<IResolvedDirectory>((provider, singletons) => new ResolvedDirectory());
53 this.AddService<IResolveFileResult>((provider, singletons) => new ResolveFileResult());
54 this.AddService<IResolveResult>((provider, singletons) => new ResolveResult());
55 this.AddService<IResolvedCabinet>((provider, singletons) => new ResolvedCabinet());
56 this.AddService<IVariableResolution>((provider, singletons) => new VariableResolution());
57
58 this.AddService<IBinder>((provider, singletons) => new Binder(provider));
59 this.AddService<ICompiler>((provider, singletons) => new Compiler(provider));
60 this.AddService<IDecompiler>((provider, singletons) => new Decompiler(provider));
61 this.AddService<ILayoutCreator>((provider, singletons) => new LayoutCreator(provider));
62 this.AddService<IPreprocessor>((provider, singletons) => new Preprocessor(provider));
63 this.AddService<ILibrarian>((provider, singletons) => new Librarian(provider));
64 this.AddService<ILinker>((provider, singletons) => new Linker(provider));
65 this.AddService<IResolver>((provider, singletons) => new Resolver(provider));
66 this.AddService<IUnbinder>((provider, singletons) => new Unbinder(provider));
67
68 this.AddService<ILocalizationParser>((provider, singletons) => new LocalizationParser(provider));
69 this.AddService<IVariableResolver>((provider, singletons) => new VariableResolver(provider));
70 }
71
72 private Dictionary<Type, Func<IWixToolsetCoreServiceProvider, Dictionary<Type, object>, object>> CreationFunctions { get; }
73
74 private Dictionary<Type, object> Singletons { get; }
75
76 public object GetService(Type serviceType)
77 {
78 if (serviceType == null)
79 {
80 throw new ArgumentNullException(nameof(serviceType));
81 }
82
83 if (!this.Singletons.TryGetValue(serviceType, out var service))
84 {
85 if (this.CreationFunctions.TryGetValue(serviceType, out var creationFunction))
86 {
87 service = creationFunction(this, this.Singletons);
88
89#if DEBUG
90 if (!serviceType.IsAssignableFrom(service?.GetType()))
91 {
92 throw new InvalidOperationException($"Creation function for service type: {serviceType.Name} created incompatible service with type: {service?.GetType()}");
93 }
94#endif
95 }
96 }
97
98 return service;
99 }
100
101 public void AddService(Type serviceType, Func<IWixToolsetCoreServiceProvider, Dictionary<Type, object>, object> creationFunction)
102 {
103 this.CreationFunctions[serviceType] = creationFunction;
104 }
105
106 public void AddService<T>(Func<IWixToolsetCoreServiceProvider, Dictionary<Type, object>, T> creationFunction) where T : class
107 {
108 this.AddService(typeof(T), creationFunction);
109 }
110
111 private static T AddSingleton<T>(Dictionary<Type, object> singletons, T service) where T : class
112 {
113 singletons.Add(typeof(T), service);
114 return service;
115 }
116 }
117}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core
4{
5 using WixToolset.Extensibility.Services;
6
7 /// <summary>
8 /// Class for creating <see cref="IWixToolsetCoreServiceProvider"/>.
9 /// </summary>
10 public static class WixToolsetServiceProviderFactory
11 {
12 /// <summary>
13 /// Creates a new <see cref="IWixToolsetCoreServiceProvider"/>.
14 /// </summary>
15 /// <returns>The created <see cref="IWixToolsetCoreServiceProvider"/></returns>
16 public static IWixToolsetCoreServiceProvider CreateServiceProvider()
17 {
18 return new WixToolsetServiceProvider();
19 }
20 }
21}
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 @@
1@setlocal
2@pushd %~dp0
3@set _P=%~dp0build\Release\publish
4@set _C=Release
5@if /i "%1"=="debug" set _C=Debug
6
7:: Restore
8msbuild -p:Configuration=%_C% -t:Restore || exit /b
9
10:: Build
11msbuild -p:Configuration=%_C% || exit /b
12
13:: Test
14dotnet test -c %_C% --no-build || exit /b
15
16:: Pack
17msbuild -p:Configuration=%_C% -p:NoBuild=true -t:Pack || exit /b
18
19@popd
20@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 @@
1# Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2#
3# Do NOT modify this file. Update the canonical version in Home\repo-template\src\appveyor.yml
4# then update all of the repos.
5
6branches:
7 only:
8 - master
9 - develop
10
11image: Visual Studio 2019
12
13version: 0.0.0.{build}
14configuration: Release
15
16environment:
17 DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
18 DOTNET_CLI_TELEMETRY_OPTOUT: 1
19 NUGET_XMLDOC_MODE: skip
20
21build_script:
22 - appveyor.cmd
23
24test: off
25
26pull_requests:
27 do_not_increment_build_number: true
28
29nuget:
30 disable_publish_on_pr: true
31
32skip_branch_with_pr: true
33skip_tags: true
34
35artifacts:
36- path: build\Release\**\*.nupkg
37 name: nuget
38- path: build\Release\**\*.snupkg
39 name: snupkg
40
41notifications:
42- provider: Slack
43 incoming_webhook:
44 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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<configuration>
3 <packageSources>
4 <clear />
5 <add key="wixtoolset-burn" value="https://ci.appveyor.com/nuget/wixtoolset-burn" />
6 <add key="wixtoolset-core-native" value="https://ci.appveyor.com/nuget/wixtoolset-core-native" />
7 <add key="wixtoolset-data" value="https://ci.appveyor.com/nuget/wixtoolset-data" />
8 <add key="wixtoolset-dtf" value="https://ci.appveyor.com/nuget/wixtoolset-dtf" />
9 <add key="wixtoolset-extensibility" value="https://ci.appveyor.com/nuget/wixtoolset-extensibility" />
10 <add key="wixbuildtools" value="https://ci.appveyor.com/nuget/wixbuildtools" />
11 <add key="api.nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
12 </packageSources>
13</configuration> \ 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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project Sdk="Microsoft.NET.Sdk">
5 <PropertyGroup>
6 <TargetFramework>netcoreapp3.1</TargetFramework>
7 <IsPackable>false</IsPackable>
8 <OutputType>Exe</OutputType>
9 </PropertyGroup>
10
11 <ItemGroup>
12 <ProjectReference Include="..\..\WixToolset.Core.TestPackage\WixToolset.Core.TestPackage.csproj" />
13 </ItemGroup>
14
15 <ItemGroup>
16 <ExtensionWxs Include="..\Example.Extension\Data\example.wxs">
17 <WixlibPath>$(BaseOutputPath)TestData\$(Configuration)\example.wixlib</WixlibPath>
18 </ExtensionWxs>
19 </ItemGroup>
20
21 <Target Name="BuildExtensionWixlibs"
22 AfterTargets="AfterBuild"
23 Inputs="@(ExtensionWxs)"
24 Outputs="%(ExtensionWxs.WixlibPath)"
25 Condition=" '$(NCrunch)'!='1' ">
26
27 <Exec Command="dotnet @(TargetPathWithTargetPlatformMoniker) &quot;$(IntermediateOutputPath) &quot; &quot;%(ExtensionWxs.WixlibPath)&quot; &quot;%(ExtensionWxs.Filename)%(ExtensionWxs.Extension)&quot;"
28 WorkingDirectory="%(ExtensionWxs.RelativeDir)" />
29
30 <Message Importance="high" Text="@(ExtensionWxs) -&gt; %(ExtensionWxs.WixlibPath)" />
31 </Target>
32</Project>
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 @@
1// Copyright(c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3using System.Collections.Generic;
4using WixToolset.Core.TestPackage;
5
6namespace CompileCoreTestExtensionWixlib
7{
8 // We want to be able to test Core with extensions, but there's no easy way to build an extension without Tools.
9 // So we have this helper exe.
10 public class Program
11 {
12 public static void Main(string[] args)
13 {
14 var intermediateFolder = args[0];
15 var wixlibPath = args[1];
16
17 var buildArgs = new List<string>();
18 buildArgs.Add("build");
19 buildArgs.Add("-bindfiles");
20 buildArgs.Add("-bindpath");
21 buildArgs.Add("Data");
22 buildArgs.Add("-intermediateFolder");
23 buildArgs.Add(intermediateFolder);
24 buildArgs.Add("-o");
25 buildArgs.Add(wixlibPath);
26
27 foreach (var path in args[2].Split(';'))
28 {
29 buildArgs.Add(path);
30 }
31
32 var result = WixRunner.Execute(buildArgs.ToArray());
33
34 result.AssertSuccess();
35 }
36 }
37}
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Fragment>
3 <Property Id="PropertyFromExampleWir" Value="FromWir" />
4
5 <Binary Id="BinFromWir" SourceFile="example.txt" />
6 </Fragment>
7 <Fragment>
8 <BootstrapperApplication Id="fakeba">
9 <BootstrapperApplicationDll SourceFile="example.txt" />
10 </BootstrapperApplication>
11 </Fragment>
12 <Fragment>
13 <BundleExtension Id="ExampleBundleExtension" SourceFile="example.txt" />
14 </Fragment>
15</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project Sdk="Microsoft.NET.Sdk">
5 <PropertyGroup>
6 <TargetFramework>netcoreapp3.1</TargetFramework>
7 <IsPackable>false</IsPackable>
8 <DebugType>embedded</DebugType>
9 </PropertyGroup>
10
11 <ItemGroup>
12 <!-- This .wixlib is built by CompileCoreTestExtensionWixlib.csproj -->
13 <EmbeddedResource Include="$(BaseOutputPath)TestData\$(Configuration)\Example.wixlib" />
14 </ItemGroup>
15
16 <ItemGroup>
17 <ProjectReference Include="..\CompileCoreTestExtensionWixlib\CompileCoreTestExtensionWixlib.csproj" />
18 </ItemGroup>
19
20 <ItemGroup>
21 <PackageReference Include="WixToolset.Extensibility" Version="4.0.*" />
22 </ItemGroup>
23
24</Project>
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace Example.Extension
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Xml.Linq;
8 using WixToolset.Data;
9 using WixToolset.Extensibility;
10
11 internal class ExampleCompilerExtension : BaseCompilerExtension
12 {
13 public override XNamespace Namespace => "http://www.example.com/scheams/v1/wxs";
14 public string BundleExtensionId => "ExampleBundleExtension";
15
16 public override void ParseElement(Intermediate intermediate, IntermediateSection section, XElement parentElement, XElement element, IDictionary<string, string> context)
17 {
18 var processed = false;
19
20 switch (parentElement.Name.LocalName)
21 {
22 case "Bundle":
23 case "Fragment":
24 switch (element.Name.LocalName)
25 {
26 case "ExampleEnsureTable":
27 this.ParseExampleEnsureTableElement(intermediate, section, element);
28 processed = true;
29 break;
30 case "ExampleSearch":
31 this.ParseExampleSearchElement(intermediate, section, element);
32 processed = true;
33 break;
34 case "ExampleSearchRef":
35 this.ParseExampleSearchRefElement(intermediate, section, element);
36 processed = true;
37 break;
38 }
39 break;
40 case "Component":
41 switch (element.Name.LocalName)
42 {
43 case "Example":
44 this.ParseExampleElement(intermediate, section, element);
45 processed = true;
46 break;
47 }
48 break;
49 }
50
51 if (!processed)
52 {
53 base.ParseElement(intermediate, section, parentElement, element, context);
54 }
55 }
56
57 private void ParseExampleElement(Intermediate intermediate, IntermediateSection section, XElement element)
58 {
59 var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element);
60 Identifier id = null;
61 string value = null;
62
63 foreach (var attrib in element.Attributes())
64 {
65 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace)
66 {
67 switch (attrib.Name.LocalName)
68 {
69 case "Id":
70 id = this.ParseHelper.GetAttributeIdentifier(sourceLineNumbers, attrib);
71 break;
72
73 case "Value":
74 value = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib);
75 break;
76
77 default:
78 this.ParseHelper.UnexpectedAttribute(element, attrib);
79 break;
80 }
81 }
82 else
83 {
84 this.ParseAttribute(intermediate, section, element, attrib, null);
85 }
86 }
87
88 if (null == id)
89 {
90 //this.Messaging(WixErrors.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "Id"));
91 }
92
93 if (!this.Messaging.EncounteredError)
94 {
95 var symbol = this.ParseHelper.CreateSymbol(section, sourceLineNumbers, "Example", id);
96 symbol.Set(0, value);
97 }
98 }
99
100 private void ParseExampleEnsureTableElement(Intermediate intermediate, IntermediateSection section, XElement element)
101 {
102 var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element);
103 this.ParseHelper.EnsureTable(section, sourceLineNumbers, ExampleTableDefinitions.NotInAll);
104 }
105
106 private void ParseExampleSearchElement(Intermediate intermediate, IntermediateSection section, XElement element)
107 {
108 var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element);
109 Identifier id = null;
110 string searchFor = null;
111 string variable = null;
112 string condition = null;
113 string after = null;
114
115 foreach (var attrib in element.Attributes())
116 {
117 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace)
118 {
119 switch (attrib.Name.LocalName)
120 {
121 case "Id":
122 id = this.ParseHelper.GetAttributeIdentifier(sourceLineNumbers, attrib);
123 break;
124 case "Variable":
125 variable = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib);
126 break;
127 case "Condition":
128 condition = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib);
129 break;
130 case "After":
131 after = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib);
132 break;
133 case "SearchFor":
134 searchFor = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib);
135 break;
136
137 default:
138 this.ParseHelper.UnexpectedAttribute(element, attrib);
139 break;
140 }
141 }
142 else
143 {
144 this.ParseAttribute(intermediate, section, element, attrib, null);
145 }
146 }
147
148 if (null == id)
149 {
150 this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "Id"));
151 }
152
153 if (!this.Messaging.EncounteredError)
154 {
155 this.ParseHelper.CreateWixSearchSymbol(section, sourceLineNumbers, element.Name.LocalName, id, variable, condition, after, this.BundleExtensionId);
156 }
157
158 if (!this.Messaging.EncounteredError)
159 {
160 var symbol = section.AddSymbol(new ExampleSearchSymbol(sourceLineNumbers, id)
161 {
162 SearchFor = searchFor,
163 });
164 }
165 }
166
167 private void ParseExampleSearchRefElement(Intermediate intermediate, IntermediateSection section, XElement element)
168 {
169 var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element);
170
171 foreach (var attrib in element.Attributes())
172 {
173 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace)
174 {
175 switch (attrib.Name.LocalName)
176 {
177 case "Id":
178 var refId = this.ParseHelper.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
179 this.ParseHelper.CreateSimpleReference(section, sourceLineNumbers, ExampleSymbolDefinitions.ExampleSearch, refId);
180 break;
181 default:
182 this.ParseHelper.UnexpectedAttribute(element, attrib);
183 break;
184 }
185 }
186 else
187 {
188 this.ParseHelper.ParseExtensionAttribute(this.Context.Extensions, intermediate, section, element, attrib);
189 }
190 }
191
192 this.ParseHelper.ParseForExtensionElements(this.Context.Extensions, intermediate, section, element);
193 }
194 }
195}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace Example.Extension
4{
5 using WixToolset.Data;
6 using WixToolset.Extensibility;
7
8 internal class ExampleExtensionData : IExtensionData
9 {
10 public string DefaultCulture => null;
11
12 public Intermediate GetLibrary(ISymbolDefinitionCreator symbolDefinitions)
13 {
14 return Intermediate.Load(typeof(ExampleExtensionData).Assembly, "Example.Extension.Example.wixlib", symbolDefinitions);
15 }
16
17 public bool TryGetSymbolDefinitionByName(string name, out IntermediateSymbolDefinition symbolDefinition)
18 {
19 symbolDefinition = ExampleSymbolDefinitions.ByName(name);
20 return symbolDefinition != null;
21 }
22 }
23} \ 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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace Example.Extension
4{
5 using System;
6 using WixToolset.Extensibility;
7 using WixToolset.Extensibility.Services;
8
9 public class ExampleExtensionFactory : IExtensionFactory
10 {
11 private ExamplePreprocessorExtensionAndCommandLine preprocessorExtension;
12
13 public ExampleExtensionFactory(IWixToolsetCoreServiceProvider serviceProvider)
14 {
15 this.ServiceProvider = serviceProvider;
16 }
17
18 /// <summary>
19 /// This exists just to show it is possible to get a service provider to the extension factory.
20 /// </summary>
21 private IWixToolsetCoreServiceProvider ServiceProvider { get; }
22
23 public bool TryCreateExtension(Type extensionType, out object extension)
24 {
25 if (extensionType == typeof(IExtensionCommandLine) || extensionType == typeof(IPreprocessorExtension))
26 {
27 if (this.preprocessorExtension == null)
28 {
29 this.preprocessorExtension = new ExamplePreprocessorExtensionAndCommandLine();
30 }
31
32 extension = this.preprocessorExtension;
33 }
34 else if (extensionType == typeof(ICompilerExtension))
35 {
36 extension = new ExampleCompilerExtension();
37 }
38 else if (extensionType == typeof(IExtensionData))
39 {
40 extension = new ExampleExtensionData();
41 }
42 else if (extensionType == typeof(IWindowsInstallerBackendBinderExtension))
43 {
44 extension = new ExampleWindowsInstallerBackendExtension();
45 }
46 else
47 {
48 extension = null;
49 }
50
51 return extension != null;
52 }
53 }
54}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace Example.Extension
4{
5 using System;
6 using System.Collections.Generic;
7 using WixToolset.Extensibility;
8 using WixToolset.Extensibility.Data;
9 using WixToolset.Extensibility.Services;
10
11 internal class ExamplePreprocessorExtensionAndCommandLine : BasePreprocessorExtension, IExtensionCommandLine
12 {
13 private string exampleValueFromCommandLine;
14
15 public IReadOnlyCollection<ExtensionCommandLineSwitch> CommandLineSwitches => throw new NotImplementedException();
16
17 public ExamplePreprocessorExtensionAndCommandLine()
18 {
19 this.Prefixes = new[] { "ex" };
20 }
21
22 public void PreParse(ICommandLineContext context)
23 {
24 }
25
26 public bool TryParseArgument(ICommandLineParser parser, string argument)
27 {
28 if (parser.IsSwitch(argument) && argument.Substring(1).Equals("example", StringComparison.OrdinalIgnoreCase))
29 {
30 this.exampleValueFromCommandLine = parser.GetNextArgumentOrError(argument);
31 return true;
32 }
33
34 return false;
35 }
36
37 public bool TryParseCommand(ICommandLineParser parser, string argument, out ICommandLineCommand command)
38 {
39 command = null;
40 return false;
41 }
42
43 public void PostParse()
44 {
45 }
46
47 public override string GetVariableValue(string prefix, string name)
48 {
49 if (prefix == "ex" && "test".Equals(name, StringComparison.OrdinalIgnoreCase))
50 {
51 return String.IsNullOrWhiteSpace(this.exampleValueFromCommandLine) ? "(null)" : this.exampleValueFromCommandLine;
52 }
53
54 return null;
55 }
56 }
57}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace Example.Extension
4{
5 using WixToolset.Data;
6 using WixToolset.Data.WindowsInstaller;
7
8 public class ExampleRow : Row
9 {
10 public ExampleRow(SourceLineNumber sourceLineNumbers, Table table)
11 : base(sourceLineNumbers, table)
12 {
13 }
14
15 public ExampleRow(SourceLineNumber sourceLineNumbers, TableDefinition tableDefinition)
16 : base(sourceLineNumbers, tableDefinition)
17 {
18 }
19
20 public string Example
21 {
22 get { return (string)this.Fields[0].Data; }
23 set { this.Fields[0].Data = value; }
24 }
25
26 public string Value
27 {
28 get { return (string)this.Fields[1].Data; }
29 set { this.Fields[1].Data = value; }
30 }
31 }
32}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace Example.Extension
4{
5 using WixToolset.Data;
6
7 public enum ExampleSearchSymbolFields
8 {
9 SearchFor,
10 }
11
12 public class ExampleSearchSymbol : IntermediateSymbol
13 {
14 public ExampleSearchSymbol() : base(ExampleSymbolDefinitions.ExampleSearch, null, null)
15 {
16 }
17
18 public ExampleSearchSymbol(SourceLineNumber sourceLineNumber, Identifier id = null) : base(ExampleSymbolDefinitions.ExampleSearch, sourceLineNumber, id)
19 {
20 }
21
22 public IntermediateField this[ExampleSymbolFields index] => this.Fields[(int)index];
23
24 public string SearchFor
25 {
26 get => this.Fields[(int)ExampleSearchSymbolFields.SearchFor]?.AsString();
27 set => this.Set((int)ExampleSearchSymbolFields.SearchFor, value);
28 }
29 }
30}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace Example.Extension
4{
5 using WixToolset.Data;
6
7 public enum ExampleSymbolFields
8 {
9 Value,
10 }
11
12 public class ExampleSymbol : IntermediateSymbol
13 {
14 public ExampleSymbol() : base(ExampleSymbolDefinitions.Example, null, null)
15 {
16 }
17
18 public ExampleSymbol(SourceLineNumber sourceLineNumber, Identifier id = null) : base(ExampleSymbolDefinitions.Example, sourceLineNumber, id)
19 {
20 }
21
22 public IntermediateField this[ExampleSymbolFields index] => this.Fields[(int)index];
23
24 public string Value
25 {
26 get => this.Fields[(int)ExampleSymbolFields.Value]?.AsString();
27 set => this.Set((int)ExampleSymbolFields.Value, value);
28 }
29 }
30}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace Example.Extension
4{
5 using System;
6 using WixToolset.Data;
7 using WixToolset.Data.Burn;
8
9 public enum ExampleSymbolDefinitionType
10 {
11 Example,
12 ExampleSearch,
13 }
14
15 public static class ExampleSymbolDefinitions
16 {
17 public static readonly IntermediateSymbolDefinition Example = new IntermediateSymbolDefinition(
18 ExampleSymbolDefinitionType.Example.ToString(),
19 new[]
20 {
21 new IntermediateFieldDefinition(nameof(ExampleSymbolFields.Value), IntermediateFieldType.String),
22 },
23 typeof(ExampleSymbol));
24
25 public static readonly IntermediateSymbolDefinition ExampleSearch = new IntermediateSymbolDefinition(
26 ExampleSymbolDefinitionType.ExampleSearch.ToString(),
27 new[]
28 {
29 new IntermediateFieldDefinition(nameof(ExampleSearchSymbolFields.SearchFor), IntermediateFieldType.String),
30 },
31 typeof(ExampleSearchSymbol));
32
33 static ExampleSymbolDefinitions()
34 {
35 ExampleSearch.AddTag(BurnConstants.BundleExtensionSearchSymbolDefinitionTag);
36 }
37
38 public static bool TryGetSymbolType(string name, out ExampleSymbolDefinitionType type)
39 {
40 return Enum.TryParse(name, out type);
41 }
42
43 public static IntermediateSymbolDefinition ByName(string name)
44 {
45 if (!TryGetSymbolType(name, out var type))
46 {
47 return null;
48 }
49 return ByType(type);
50 }
51
52 public static IntermediateSymbolDefinition ByType(ExampleSymbolDefinitionType type)
53 {
54 switch (type)
55 {
56 case ExampleSymbolDefinitionType.Example:
57 return ExampleSymbolDefinitions.Example;
58
59 case ExampleSymbolDefinitionType.ExampleSearch:
60 return ExampleSymbolDefinitions.ExampleSearch;
61
62 default:
63 throw new ArgumentOutOfRangeException(nameof(type));
64 }
65 }
66 }
67}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace Example.Extension
4{
5 using WixToolset.Data.WindowsInstaller;
6
7 public static class ExampleTableDefinitions
8 {
9 public static readonly TableDefinition ExampleTable = new TableDefinition(
10 "Wix4Example",
11 ExampleSymbolDefinitions.Example,
12 new[]
13 {
14 new ColumnDefinition("Example", ColumnType.String, 72, true, false, ColumnCategory.Identifier),
15 new ColumnDefinition("Value", ColumnType.String, 0, false, false, ColumnCategory.Formatted),
16 },
17 strongRowType: typeof(ExampleRow),
18 symbolIdIsPrimaryKey: true
19 );
20
21 public static readonly TableDefinition NotInAll = new TableDefinition(
22 "TableDefinitionNotExposedByExtension",
23 null,
24 new[]
25 {
26 new ColumnDefinition("Example", ColumnType.String, 72, true, false, ColumnCategory.Identifier),
27 new ColumnDefinition("Value", ColumnType.String, 0, false, false, ColumnCategory.Formatted),
28 },
29 symbolIdIsPrimaryKey: true
30 );
31
32 public static readonly TableDefinition[] All = new[] { ExampleTable };
33 }
34}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace Example.Extension
4{
5 using System.Collections.Generic;
6 using WixToolset.Data;
7 using WixToolset.Data.WindowsInstaller;
8 using WixToolset.Extensibility;
9
10 internal class ExampleWindowsInstallerBackendExtension : BaseWindowsInstallerBackendBinderExtension
11 {
12 public override IReadOnlyCollection<TableDefinition> TableDefinitions => ExampleTableDefinitions.All;
13
14 public override bool TryProcessSymbol(IntermediateSection section, IntermediateSymbol symbol, WindowsInstallerData output, TableDefinitionCollection tableDefinitions)
15 {
16 if (ExampleSymbolDefinitions.TryGetSymbolType(symbol.Definition.Name, out var symbolType))
17 {
18 switch (symbolType)
19 {
20 case ExampleSymbolDefinitionType.Example:
21 {
22 var row = (ExampleRow)this.BackendHelper.CreateRow(section, symbol, output, ExampleTableDefinitions.ExampleTable);
23 row.Example = symbol.Id.Id;
24 row.Value = symbol[0].AsString();
25 }
26 return true;
27 }
28 }
29
30 return base.TryProcessSymbol(section, symbol, output, tableDefinitions);
31 }
32 }
33}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.Core.Burn
4{
5 using System;
6 using WixToolset.Core.Burn.Bundles;
7 using Xunit;
8
9 public class BurnReaderFixture
10 {
11 [Fact]
12 public void CanReadUInt16Max()
13 {
14 var bytes = new byte[] { 0xFF, 0xFF };
15 var offset = 0u;
16
17 var result = BurnCommon.ReadUInt16(bytes, offset);
18
19 Assert.Equal(UInt16.MaxValue, result);
20 }
21
22 [Fact]
23 public void CanReadUInt32Max()
24 {
25 var bytes = new byte[] { 0xFF, 0xFF, 0xFF, 0xFF };
26 var offset = 0u;
27
28 var result = BurnCommon.ReadUInt32(bytes, offset);
29
30 Assert.Equal(UInt32.MaxValue, result);
31 }
32
33 [Fact]
34 public void CanReadUInt64Max()
35 {
36 var bytes = new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
37 var offset = 0u;
38
39 var result = BurnCommon.ReadUInt64(bytes, offset);
40
41 Assert.Equal(UInt64.MaxValue, result);
42 }
43 }
44}
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project Sdk="Microsoft.NET.Sdk">
5 <PropertyGroup>
6 <TargetFramework>netcoreapp3.1</TargetFramework>
7 <IsPackable>false</IsPackable>
8 <DebugType>embedded</DebugType>
9 </PropertyGroup>
10
11 <PropertyGroup>
12 <NoWarn>NU1701</NoWarn>
13 </PropertyGroup>
14
15 <ItemGroup>
16 <ProjectReference Include="..\..\WixToolset.Core.Burn\WixToolset.Core.Burn.csproj" />
17 </ItemGroup>
18
19 <ItemGroup>
20 <PackageReference Include="WixBuildTools.TestSupport" Version="4.0.*" />
21 </ItemGroup>
22
23 <ItemGroup>
24 <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.8.0" />
25 <PackageReference Include="xunit" Version="2.4.1" />
26 <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" PrivateAssets="All" />
27 </ItemGroup>
28</Project>
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System.IO;
6 using WixBuildTools.TestSupport;
7 using WixToolset.Core.TestPackage;
8 using Xunit;
9
10 public class ApprovedExeFixture
11 {
12 [Fact]
13 public void CanBuildWithApprovedExe()
14 {
15 var folder = TestData.Get(@"TestData");
16
17 using (var fs = new DisposableFileSystem())
18 {
19 var baseFolder = fs.GetFolder();
20 var intermediateFolder = Path.Combine(baseFolder, "obj");
21 var exePath = Path.Combine(baseFolder, @"bin\test.exe");
22
23 var result = WixRunner.Execute(new[]
24 {
25 "build",
26 Path.Combine(folder, "BundleWithApprovedExe", "Bundle.wxs"),
27 "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
28 "-bindpath", Path.Combine(folder, ".Data"),
29 "-intermediateFolder", intermediateFolder,
30 "-o", exePath,
31 });
32
33 Assert.NotEqual(0, result.ExitCode);
34 Assert.False(File.Exists(exePath));
35 }
36 }
37
38 [Fact]
39 public void CanBuildWithApprovedExe64()
40 {
41 var folder = TestData.Get(@"TestData");
42
43 using (var fs = new DisposableFileSystem())
44 {
45 var baseFolder = fs.GetFolder();
46 var intermediateFolder = Path.Combine(baseFolder, "obj");
47 var exePath = Path.Combine(baseFolder, @"bin\test.exe");
48
49 var result = WixRunner.Execute(new[]
50 {
51 "build",
52 Path.Combine(folder, "BundleWithApprovedExe", "Bundle64.wxs"),
53 "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
54 "-bindpath", Path.Combine(folder, ".Data"),
55 "-intermediateFolder", intermediateFolder,
56 "-o", exePath,
57 });
58
59 Assert.NotEqual(0, result.ExitCode);
60 Assert.False(File.Exists(exePath));
61 }
62 }
63 }
64}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System;
6 using System.IO;
7 using WixBuildTools.TestSupport;
8 using WixToolset.Core.TestPackage;
9 using WixToolset.Data;
10 using Xunit;
11
12 public class BadInputFixture
13 {
14 [Fact]
15 public void SwitchIsNotConsideredAnArgument()
16 {
17 var result = WixRunner.Execute(new[]
18 {
19 "build",
20 "-bindpath", "-thisisaswitchnotanarg",
21 });
22
23 Assert.Single(result.Messages, m => m.Id == (int)ErrorMessages.Ids.ExpectedArgument);
24 // TODO: when CantBuildSingleExeBundleWithInvalidArgument is fixed, uncomment:
25 //Assert.Equal((int)ErrorMessages.Ids.ExpectedArgument, result.ExitCode);
26 }
27
28 [Fact]
29 public void HandleInvalidIds()
30 {
31 var folder = TestData.Get(@"TestData\BadInput");
32
33 using (var fs = new DisposableFileSystem())
34 {
35 var baseFolder = fs.GetFolder();
36 var intermediateFolder = Path.Combine(baseFolder, "obj");
37 var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib");
38
39 var result = WixRunner.Execute(new[]
40 {
41 "build",
42 Path.Combine(folder, "InvalidIds.wxs"),
43 "-intermediateFolder", intermediateFolder,
44 "-o", wixlibPath,
45 });
46
47 Assert.Equal(330, result.ExitCode);
48 }
49 }
50
51 [Fact]
52 public void CantBuildSingleExeBundleWithInvalidArgument()
53 {
54 var folder = TestData.Get(@"TestData");
55
56 using (var fs = new DisposableFileSystem())
57 {
58 var baseFolder = fs.GetFolder();
59 var intermediateFolder = Path.Combine(baseFolder, "obj");
60 var exePath = Path.Combine(baseFolder, @"bin\test.exe");
61
62 var result = WixRunner.Execute(new[]
63 {
64 "build",
65 Path.Combine(folder, "SingleExeBundle", "SingleExePackageGroup.wxs"),
66 Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"),
67 "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
68 "-bindpath", Path.Combine(folder, ".Data"),
69 "-intermediateFolder", intermediateFolder,
70 "-o", exePath,
71 "-nonexistentswitch", "param",
72 });
73
74 Assert.NotEqual(0, result.ExitCode);
75 Assert.False(File.Exists(exePath));
76 }
77 }
78
79 [Fact]
80 public void RegistryKeyWithoutAttributesDoesntCrash()
81 {
82 var folder = TestData.Get(@"TestData\BadInput");
83
84 using (var fs = new DisposableFileSystem())
85 {
86 var baseFolder = fs.GetFolder();
87 var intermediateFolder = Path.Combine(baseFolder, "obj");
88 var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib");
89
90 var result = WixRunner.Execute(new[]
91 {
92 "build",
93 Path.Combine(folder, "RegistryKey.wxs"),
94 "-intermediateFolder", intermediateFolder,
95 "-o", wixlibPath,
96 });
97
98 Assert.InRange(result.ExitCode, 2, Int32.MaxValue);
99 }
100 }
101
102 [Fact]
103 public void BundleVariableWithBadTypeIsRejected()
104 {
105 var folder = TestData.Get(@"TestData\BadInput");
106
107 using (var fs = new DisposableFileSystem())
108 {
109 var baseFolder = fs.GetFolder();
110 var intermediateFolder = Path.Combine(baseFolder, "obj");
111 var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib");
112
113 var result = WixRunner.Execute(new[]
114 {
115 "build",
116 Path.Combine(folder, "BundleVariable.wxs"),
117 "-intermediateFolder", intermediateFolder,
118 "-o", wixlibPath,
119 });
120
121 Assert.Equal(21, result.ExitCode);
122 }
123 }
124
125 [Fact]
126 public void BundleVariableWithHiddenPersistedIsRejected()
127 {
128 var folder = TestData.Get(@"TestData\BadInput");
129
130 using (var fs = new DisposableFileSystem())
131 {
132 var baseFolder = fs.GetFolder();
133 var intermediateFolder = Path.Combine(baseFolder, "obj");
134 var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib");
135
136 var result = WixRunner.Execute(new[]
137 {
138 "build",
139 Path.Combine(folder, "HiddenPersistedBundleVariable.wxs"),
140 "-intermediateFolder", intermediateFolder,
141 "-o", wixlibPath,
142 });
143
144 Assert.Equal(193, result.ExitCode);
145 }
146 }
147 }
148}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System;
6 using System.IO;
7 using WixBuildTools.TestSupport;
8 using WixToolset.Core.TestPackage;
9 using Xunit;
10
11 public class BindVariablesFixture
12 {
13 [Fact]
14 public void CanBuildBundleWithPackageBindVariables()
15 {
16 var folder = TestData.Get(@"TestData");
17
18 using (var fs = new DisposableFileSystem())
19 {
20 var baseFolder = fs.GetFolder();
21 var intermediateFolder = Path.Combine(baseFolder, "obj");
22 var exePath = Path.Combine(baseFolder, @"bin\test.exe");
23
24 var result = WixRunner.Execute(new[]
25 {
26 "build",
27 Path.Combine(folder, "BundleBindVariables", "CacheIdFromPackageDescription.wxs"),
28 Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"),
29 "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
30 "-bindpath", Path.Combine(folder, ".Data"),
31 "-intermediateFolder", intermediateFolder,
32 "-o", exePath,
33 });
34
35 result.AssertSuccess();
36
37 Assert.True(File.Exists(exePath));
38 }
39 }
40
41 [Fact]
42 public void CanBuildWithDefaultValue()
43 {
44 var folder = TestData.Get(@"TestData", "BindVariables");
45
46 using (var fs = new DisposableFileSystem())
47 {
48 var baseFolder = fs.GetFolder();
49 var intermediateFolder = Path.Combine(baseFolder, "obj");
50 var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib");
51
52 var result = WixRunner.Execute(new[]
53 {
54 "build",
55 Path.Combine(folder, "DefaultedVariable.wxs"),
56 "-bf",
57 "-intermediateFolder", intermediateFolder,
58 "-bindpath", folder,
59 "-o", wixlibPath,
60 });
61
62 result.AssertSuccess();
63 }
64 }
65
66 [Fact]
67 public void CannotBuildWixlibWithBinariesFromMissingNamedBindPaths()
68 {
69 var folder = TestData.Get(@"TestData", "WixlibWithBinaries");
70
71 using (var fs = new DisposableFileSystem())
72 {
73 var baseFolder = fs.GetFolder();
74 var intermediateFolder = Path.Combine(baseFolder, "obj");
75 var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib");
76
77 var result = WixRunner.Execute(new[]
78 {
79 "build",
80 Path.Combine(folder, "PackageComponents.wxs"),
81 "-bf",
82 "-bindpath", Path.Combine(folder, "data"),
83 // Use names that aren't excluded in default .gitignores.
84 "-bindpath", $"AlphaBits={Path.Combine(folder, "data", "alpha")}",
85 "-bindpath", $"PowerBits={Path.Combine(folder, "data", "powerpc")}",
86 "-bindpath", $"{Path.Combine(folder, "data", "alpha")}",
87 "-bindpath", $"{Path.Combine(folder, "data", "powerpc")}",
88 "-intermediateFolder", intermediateFolder,
89 "-o", wixlibPath,
90 });
91
92 Assert.Equal(103, result.ExitCode);
93 }
94 }
95 }
96}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System.IO;
6 using System.Linq;
7 using WixBuildTools.TestSupport;
8 using WixToolset.Core.TestPackage;
9 using WixToolset.Data;
10 using WixToolset.Data.Symbols;
11 using Xunit;
12
13 public class BootstrapperApplicationFixture
14 {
15 [Fact]
16 public void CanSetBootstrapperApplicationDllDpiAwareness()
17 {
18 var folder = TestData.Get(@"TestData\BootstrapperApplication");
19
20 using (var fs = new DisposableFileSystem())
21 {
22 var baseFolder = fs.GetFolder();
23 var intermediateFolder = Path.Combine(baseFolder, "obj");
24 var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib");
25
26 var result = WixRunner.Execute(new[]
27 {
28 "build",
29 Path.Combine(folder, "DpiAwareness.wxs"),
30 "-intermediateFolder", intermediateFolder,
31 "-o", wixlibPath,
32 });
33
34 result.AssertSuccess();
35
36 var intermediate = Intermediate.Load(wixlibPath);
37 var allSymbols = intermediate.Sections.SelectMany(s => s.Symbols);
38 var baDllSymbol = allSymbols.OfType<WixBootstrapperApplicationDllSymbol>()
39 .SingleOrDefault();
40 Assert.NotNull(baDllSymbol);
41
42 Assert.Equal(WixBootstrapperApplicationDpiAwarenessType.GdiScaled, baDllSymbol.DpiAwareness);
43 }
44 }
45 }
46}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System.IO;
6 using System.Linq;
7 using WixBuildTools.TestSupport;
8 using WixToolset.Core;
9 using WixToolset.Core.TestPackage;
10 using WixToolset.Data;
11 using WixToolset.Extensibility.Services;
12 using Xunit;
13
14 public class BundleExtractionFixture
15 {
16 [Fact]
17 public void CanExtractBundleWithDetachedContainer()
18 {
19 var folder = TestData.Get(@"TestData");
20
21 using (var fs = new DisposableFileSystem())
22 {
23 var baseFolder = fs.GetFolder();
24 var intermediateFolder = Path.Combine(baseFolder, "obj");
25 var exePath = Path.Combine(baseFolder, @"bin\test.exe");
26 var pdbPath = Path.Combine(baseFolder, @"bin\test.wixpdb");
27 var extractFolderPath = Path.Combine(baseFolder, "extract");
28 var baFolderPath = Path.Combine(extractFolderPath, "UX");
29 var attachedContainerFolderPath = Path.Combine(extractFolderPath, "AttachedContainer");
30
31 // TODO: use WixRunner.Execute(string[]) to always go through the command line.
32 var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider();
33 var result = WixRunner.Execute(new[]
34 {
35 "build",
36 Path.Combine(folder, "BundleWithDetachedContainer", "Bundle.wxs"),
37 Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"),
38 Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"),
39 "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
40 "-bindpath", Path.Combine(folder, ".Data"),
41 "-intermediateFolder", intermediateFolder,
42 "-o", exePath,
43 }, serviceProvider, out var messages).Result;
44
45 WixRunnerResult.AssertSuccess(result, messages);
46 Assert.Empty(messages.Where(m => m.Level == MessageLevel.Warning));
47
48 Assert.True(File.Exists(exePath));
49
50 var unbinder = serviceProvider.GetService<IUnbinder>();
51 unbinder.Unbind(exePath, OutputType.Bundle, extractFolderPath);
52
53 Assert.True(File.Exists(Path.Combine(baFolderPath, "manifest.xml")));
54 Assert.False(Directory.Exists(attachedContainerFolderPath));
55 }
56 }
57 }
58}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 using System.Text;
10 using System.Xml;
11 using Example.Extension;
12 using WixBuildTools.TestSupport;
13 using WixToolset.Core;
14 using WixToolset.Core.Burn;
15 using WixToolset.Core.TestPackage;
16 using WixToolset.Data;
17 using WixToolset.Data.Burn;
18 using WixToolset.Data.Symbols;
19 using WixToolset.Dtf.Resources;
20 using Xunit;
21
22 public class BundleFixture
23 {
24 [Fact]
25 public void CanBuildMultiFileBundle()
26 {
27 var folder = TestData.Get(@"TestData\SimpleBundle");
28
29 using (var fs = new DisposableFileSystem())
30 {
31 var baseFolder = fs.GetFolder();
32 var intermediateFolder = Path.Combine(baseFolder, "obj");
33
34 var result = WixRunner.Execute(new[]
35 {
36 "build",
37 Path.Combine(folder, "MultiFileBootstrapperApplication.wxs"),
38 Path.Combine(folder, "MultiFileBundle.wxs"),
39 "-loc", Path.Combine(folder, "Bundle.en-us.wxl"),
40 "-bindpath", Path.Combine(folder, "data"),
41 "-intermediateFolder", intermediateFolder,
42 "-o", Path.Combine(baseFolder, @"bin\test.exe")
43 });
44
45 result.AssertSuccess();
46
47 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.exe")));
48 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb")));
49 }
50 }
51
52 [Fact]
53 public void CanBuildSimpleBundle()
54 {
55 var folder = TestData.Get(@"TestData\SimpleBundle");
56
57 using (var fs = new DisposableFileSystem())
58 {
59 var baseFolder = fs.GetFolder();
60 var intermediateFolder = Path.Combine(baseFolder, "obj");
61 var exePath = Path.Combine(baseFolder, @"bin\test.exe");
62 var pdbPath = Path.Combine(baseFolder, @"bin\test.wixpdb");
63 var baFolderPath = Path.Combine(baseFolder, "ba");
64 var extractFolderPath = Path.Combine(baseFolder, "extract");
65
66 var result = WixRunner.Execute(new[]
67 {
68 "build",
69 Path.Combine(folder, "Bundle.wxs"),
70 "-loc", Path.Combine(folder, "Bundle.en-us.wxl"),
71 "-bindpath", Path.Combine(folder, "data"),
72 "-intermediateFolder", intermediateFolder,
73 "-o", exePath,
74 });
75
76 result.AssertSuccess();
77 Assert.Empty(result.Messages.Where(m => m.Level == MessageLevel.Warning));
78
79 Assert.True(File.Exists(exePath));
80 Assert.True(File.Exists(pdbPath));
81
82 using (var wixOutput = WixOutput.Read(pdbPath))
83 {
84
85 var intermediate = Intermediate.Load(wixOutput);
86 var section = intermediate.Sections.Single();
87
88 var bundleSymbol = section.Symbols.OfType<WixBundleSymbol>().Single();
89 Assert.Equal("1.0.0.0", bundleSymbol.Version);
90
91 var previousVersion = bundleSymbol.Fields[(int)WixBundleSymbolFields.Version].PreviousValue;
92 Assert.Equal("!(bind.packageVersion.test.msi)", previousVersion.AsString());
93
94 var msiSymbol = section.Symbols.OfType<WixBundlePackageSymbol>().Single();
95 Assert.Equal("test.msi", msiSymbol.Id.Id);
96
97 var extractResult = BundleExtractor.ExtractBAContainer(null, exePath, baFolderPath, extractFolderPath);
98 extractResult.AssertSuccess();
99
100 var burnManifestData = wixOutput.GetData(BurnConstants.BurnManifestWixOutputStreamName);
101 var extractedBurnManifestData = File.ReadAllText(Path.Combine(baFolderPath, "manifest.xml"), Encoding.UTF8);
102 Assert.Equal(extractedBurnManifestData, burnManifestData);
103
104 var baManifestData = wixOutput.GetData(BurnConstants.BootstrapperApplicationDataWixOutputStreamName);
105 var extractedBaManifestData = File.ReadAllText(Path.Combine(baFolderPath, "BootstrapperApplicationData.xml"), Encoding.UTF8);
106 Assert.Equal(extractedBaManifestData, baManifestData);
107
108 var bextManifestData = wixOutput.GetData(BurnConstants.BundleExtensionDataWixOutputStreamName);
109 var extractedBextManifestData = File.ReadAllText(Path.Combine(baFolderPath, "BundleExtensionData.xml"), Encoding.UTF8);
110 Assert.Equal(extractedBextManifestData, bextManifestData);
111
112 var logElements = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Log");
113 var logElement = (XmlNode)Assert.Single(logElements);
114 Assert.Equal("<Log PathVariable='WixBundleLog' Prefix='~TestBundle' Extension='log' />", logElement.GetTestXml());
115
116 var registrationElements = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Registration");
117 var registrationElement = (XmlNode)Assert.Single(registrationElements);
118 Assert.Equal($"<Registration Id='{bundleSymbol.BundleId}' ExecutableName='test.exe' PerMachine='yes' Tag='' Version='1.0.0.0' ProviderKey='{bundleSymbol.BundleId}'>" +
119 "<Arp Register='yes' DisplayName='~TestBundle' DisplayVersion='1.0.0.0' Publisher='Example Corporation' />" +
120 "</Registration>", registrationElement.GetTestXml());
121
122 var msiPayloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload[@Id='test.msi']");
123 var msiPayload = (XmlNode)Assert.Single(msiPayloads);
124 Assert.Equal("<Payload Id='test.msi' FilePath='test.msi' FileSize='*' Hash='*' Packaging='embedded' SourcePath='a0' Container='WixAttachedContainer' />",
125 msiPayload.GetTestXml(new Dictionary<string, List<string>>() { { "Payload", new List<string> { "FileSize", "Hash" } } }));
126 }
127
128 var manifestResource = new Resource(ResourceType.Manifest, "#1", 1033);
129 manifestResource.Load(exePath);
130 var actualManifestData = Encoding.UTF8.GetString(manifestResource.Data);
131 Assert.Equal("<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
132 "<assembly manifestVersion=\"1.0\" xmlns=\"urn:schemas-microsoft-com:asm.v1\">" +
133 "<assemblyIdentity name=\"test.exe\" version=\"1.0.0.0\" processorArchitecture=\"x86\" type=\"win32\" />" +
134 "<description>~TestBundle</description>" +
135 "<dependency><dependentAssembly><assemblyIdentity name=\"Microsoft.Windows.Common-Controls\" version=\"6.0.0.0\" processorArchitecture=\"x86\" publicKeyToken=\"6595b64144ccf1df\" language=\"*\" type=\"win32\" /></dependentAssembly></dependency>" +
136 "<compatibility xmlns=\"urn:schemas-microsoft-com:compatibility.v1\"><application><supportedOS Id=\"{e2011457-1546-43c5-a5fe-008deee3d3f0}\" /><supportedOS Id=\"{35138b9a-5d96-4fbd-8e2d-a2440225f93a}\" /><supportedOS Id=\"{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}\" /><supportedOS Id=\"{1f676c76-80e1-4239-95bb-83d0f6d0da78}\" /><supportedOS Id=\"{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}\" /></application></compatibility>" +
137 "<trustInfo xmlns=\"urn:schemas-microsoft-com:asm.v3\"><security><requestedPrivileges><requestedExecutionLevel level=\"asInvoker\" uiAccess=\"false\" /></requestedPrivileges></security></trustInfo>" +
138 "<application xmlns=\"urn:schemas-microsoft-com:asm.v3\"><windowsSettings><dpiAware xmlns=\"http://schemas.microsoft.com/SMI/2005/WindowsSettings\">true/pm</dpiAware><dpiAwareness xmlns=\"http://schemas.microsoft.com/SMI/2016/WindowsSettings\">PerMonitorV2, PerMonitor</dpiAwareness></windowsSettings></application>" +
139 "</assembly>", actualManifestData);
140 }
141 }
142
143 [Fact]
144 public void CanBuildX64Bundle()
145 {
146 var folder = TestData.Get(@"TestData\SimpleBundle");
147
148 using (var fs = new DisposableFileSystem())
149 {
150 var baseFolder = fs.GetFolder();
151 var intermediateFolder = Path.Combine(baseFolder, "obj");
152 var exePath = Path.Combine(baseFolder, @"bin\test.exe");
153 var pdbPath = Path.Combine(baseFolder, @"bin\test.wixpdb");
154 var baFolderPath = Path.Combine(baseFolder, "ba");
155 var extractFolderPath = Path.Combine(baseFolder, "extract");
156
157 var result = WixRunner.Execute(false, new[] // TODO: go back to elevating warnings as errors.
158 {
159 "build",
160 "-arch", "x64",
161 Path.Combine(folder, "Bundle.wxs"),
162 "-loc", Path.Combine(folder, "Bundle.en-us.wxl"),
163 "-bindpath", Path.Combine(folder, "data"),
164 "-intermediateFolder", intermediateFolder,
165 "-o", exePath,
166 });
167
168 result.AssertSuccess();
169 var warning = Assert.Single(result.Messages.Where(m => m.Level == MessageLevel.Warning));
170 Assert.Equal((int)WarningMessages.Ids.ExperimentalBundlePlatform, warning.Id);
171
172 Assert.True(File.Exists(exePath));
173 Assert.True(File.Exists(pdbPath));
174
175 var manifestResource = new Resource(ResourceType.Manifest, "#1", 1033);
176 manifestResource.Load(exePath);
177 var actualManifestData = Encoding.UTF8.GetString(manifestResource.Data);
178 Assert.Equal("<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
179 "<assembly manifestVersion=\"1.0\" xmlns=\"urn:schemas-microsoft-com:asm.v1\">" +
180 "<assemblyIdentity name=\"test.exe\" version=\"1.0.0.0\" processorArchitecture=\"amd64\" type=\"win32\" />" +
181 "<description>~TestBundle</description>" +
182 "<dependency><dependentAssembly><assemblyIdentity name=\"Microsoft.Windows.Common-Controls\" version=\"6.0.0.0\" processorArchitecture=\"amd64\" publicKeyToken=\"6595b64144ccf1df\" language=\"*\" type=\"win32\" /></dependentAssembly></dependency>" +
183 "<compatibility xmlns=\"urn:schemas-microsoft-com:compatibility.v1\"><application><supportedOS Id=\"{e2011457-1546-43c5-a5fe-008deee3d3f0}\" /><supportedOS Id=\"{35138b9a-5d96-4fbd-8e2d-a2440225f93a}\" /><supportedOS Id=\"{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}\" /><supportedOS Id=\"{1f676c76-80e1-4239-95bb-83d0f6d0da78}\" /><supportedOS Id=\"{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}\" /></application></compatibility>" +
184 "<trustInfo xmlns=\"urn:schemas-microsoft-com:asm.v3\"><security><requestedPrivileges><requestedExecutionLevel level=\"asInvoker\" uiAccess=\"false\" /></requestedPrivileges></security></trustInfo>" +
185 "<application xmlns=\"urn:schemas-microsoft-com:asm.v3\"><windowsSettings><dpiAware xmlns=\"http://schemas.microsoft.com/SMI/2005/WindowsSettings\">true/pm</dpiAware><dpiAwareness xmlns=\"http://schemas.microsoft.com/SMI/2016/WindowsSettings\">PerMonitorV2, PerMonitor</dpiAwareness></windowsSettings></application>" +
186 "</assembly>", actualManifestData);
187 }
188 }
189
190 [Fact]
191 public void CanBuildSimpleBundleUsingExtensionBA()
192 {
193 var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath);
194 var folder = TestData.Get(@"TestData\SimpleBundle");
195
196 using (var fs = new DisposableFileSystem())
197 {
198 var baseFolder = fs.GetFolder();
199 var intermediateFolder = Path.Combine(baseFolder, "obj");
200
201 var result = WixRunner.Execute(new[]
202 {
203 "build",
204 Path.Combine(folder, "MultiFileBundle.wxs"),
205 "-loc", Path.Combine(folder, "Bundle.en-us.wxl"),
206 "-ext", extensionPath,
207 "-bindpath", Path.Combine(folder, "data"),
208 "-intermediateFolder", intermediateFolder,
209 "-o", Path.Combine(baseFolder, @"bin\test.exe")
210 });
211
212 result.AssertSuccess();
213
214 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.exe")));
215 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb")));
216 }
217 }
218
219 [Fact]
220 public void CanBuildSingleExeBundle()
221 {
222 var folder = TestData.Get(@"TestData");
223
224 using (var fs = new DisposableFileSystem())
225 {
226 var baseFolder = fs.GetFolder();
227 var intermediateFolder = Path.Combine(baseFolder, "obj");
228 var exePath = Path.Combine(baseFolder, @"bin\test.exe");
229
230 var result = WixRunner.Execute(new[]
231 {
232 "build",
233 Path.Combine(folder, "SingleExeBundle", "SingleExePackageGroup.wxs"),
234 Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"),
235 "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
236 "-bindpath", Path.Combine(folder, ".Data"),
237 "-intermediateFolder", intermediateFolder,
238 "-o", exePath,
239 });
240
241 result.AssertSuccess();
242
243 Assert.True(File.Exists(exePath));
244 }
245 }
246
247 [Fact]
248 public void CanBuildSingleExeRemotePayloadBundle()
249 {
250 var folder = TestData.Get(@"TestData");
251
252 using (var fs = new DisposableFileSystem())
253 {
254 var baseFolder = fs.GetFolder();
255 var intermediateFolder = Path.Combine(baseFolder, "obj");
256 var exePath = Path.Combine(baseFolder, @"bin\test.exe");
257 var pdbPath = Path.Combine(baseFolder, @"bin\test.wixpdb");
258
259 var result = WixRunner.Execute(new[]
260 {
261 "build",
262 Path.Combine(folder, "SingleExeBundle", "SingleExeRemotePayload.wxs"),
263 Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"),
264 "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
265 "-intermediateFolder", intermediateFolder,
266 "-o", exePath,
267 });
268
269 result.AssertSuccess();
270
271 Assert.True(File.Exists(exePath));
272 Assert.True(File.Exists(pdbPath));
273
274 using (var wixOutput = WixOutput.Read(pdbPath))
275 {
276 var intermediate = Intermediate.Load(wixOutput);
277 var section = intermediate.Sections.Single();
278
279 var payloadSymbol = section.Symbols.OfType<WixBundlePayloadSymbol>().Where(x => x.Id.Id == "NetFx462Web").Single();
280 Assert.Equal(Int64.MaxValue, payloadSymbol.FileSize);
281 }
282 }
283 }
284
285 [Fact]
286 public void CantBuildWithDuplicateCacheIds()
287 {
288 var folder = TestData.Get(@"TestData");
289
290 using (var fs = new DisposableFileSystem())
291 {
292 var baseFolder = fs.GetFolder();
293 var intermediateFolder = Path.Combine(baseFolder, "obj");
294 var exePath = Path.Combine(baseFolder, @"bin\test.exe");
295
296 var result = WixRunner.Execute(new[]
297 {
298 "build",
299 Path.Combine(folder, "BadInput", "DuplicateCacheIds.wxs"),
300 Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"),
301 "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
302 "-bindpath", Path.Combine(folder, ".Data"),
303 "-intermediateFolder", intermediateFolder,
304 "-o", exePath,
305 });
306
307 Assert.Equal(8001, result.ExitCode);
308 }
309 }
310
311 [Fact]
312 public void CantBuildWithDuplicatePayloadNames()
313 {
314 var folder = TestData.Get(@"TestData");
315
316 using (var fs = new DisposableFileSystem())
317 {
318 var baseFolder = fs.GetFolder();
319 var intermediateFolder = Path.Combine(baseFolder, "obj");
320 var exePath = Path.Combine(baseFolder, @"bin\test.exe");
321
322 var result = WixRunner.Execute(new[]
323 {
324 "build",
325 Path.Combine(folder, "BadInput", "DuplicatePayloadNames.wxs"),
326 Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"),
327 "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
328 "-bindpath", Path.Combine(folder, ".Data"),
329 "-intermediateFolder", intermediateFolder,
330 "-o", exePath,
331 });
332
333 var attachedContainerWarnings = result.Messages.Where(m => m.Id == (int)BurnBackendWarnings.Ids.AttachedContainerPayloadCollision)
334 .Select(m => m.ToString())
335 .ToArray();
336 WixAssert.CompareLineByLine(new string[]
337 {
338 "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.",
339 }, attachedContainerWarnings);
340
341 var baContainerErrors = result.Messages.Where(m => m.Id == (int)BurnBackendErrors.Ids.BAContainerPayloadCollision)
342 .Select(m => m.ToString())
343 .ToArray();
344 WixAssert.CompareLineByLine(new string[]
345 {
346 "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.",
347 "The Payload 'uxTxMXPVMXwQrPTMIGa5WGt93w0Ns' has a duplicate Name 'BootstrapperApplicationData.xml' in the BA container. When extracting the container at runtime, the file will get overwritten.",
348 "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.",
349 }, baContainerErrors);
350
351 var externalErrors = result.Messages.Where(m => m.Id == (int)BurnBackendErrors.Ids.ExternalPayloadCollision)
352 .Select(m => m.ToString())
353 .ToArray();
354 WixAssert.CompareLineByLine(new string[]
355 {
356 "The external Payload 'HiddenPersistedBundleVariable.wxs' has a duplicate Name 'PayloadCollision'. When building the bundle or laying out the bundle, the file will get overwritten.",
357 "The external Container 'MsiPackagesContainer' has a duplicate Name 'ContainerCollision'. When building the bundle or laying out the bundle, the file will get overwritten.",
358 }, externalErrors);
359
360 var packageCacheErrors = result.Messages.Where(m => m.Id == (int)BurnBackendErrors.Ids.PackageCachePayloadCollision)
361 .Select(m => m.ToString())
362 .ToArray();
363 WixAssert.CompareLineByLine(new string[]
364 {
365 "The Payload 'test.msi' has a duplicate Name 'test.msi' in package 'test.msi'. When caching the package, the file will get overwritten.",
366 }, packageCacheErrors);
367
368 Assert.Equal(14, result.Messages.Length);
369 }
370 }
371
372 [Fact]
373 public void CantBuildWithOrphanPayload()
374 {
375 var folder = TestData.Get(@"TestData");
376
377 using (var fs = new DisposableFileSystem())
378 {
379 var baseFolder = fs.GetFolder();
380 var intermediateFolder = Path.Combine(baseFolder, "obj");
381 var exePath = Path.Combine(baseFolder, @"bin\test.exe");
382
383 var result = WixRunner.Execute(new[]
384 {
385 "build",
386 Path.Combine(folder, "BadInput", "OrphanPayload.wxs"),
387 Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"),
388 Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"),
389 "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
390 "-bindpath", Path.Combine(folder, ".Data"),
391 "-intermediateFolder", intermediateFolder,
392 "-o", exePath,
393 });
394
395 Assert.Equal((int)LinkerErrors.Ids.OrphanedPayload, result.ExitCode);
396 }
397 }
398
399 [Fact]
400 public void CantBuildWithPackageInMultipleContainers()
401 {
402 var folder = TestData.Get(@"TestData");
403
404 using (var fs = new DisposableFileSystem())
405 {
406 var baseFolder = fs.GetFolder();
407 var intermediateFolder = Path.Combine(baseFolder, "obj");
408 var exePath = Path.Combine(baseFolder, @"bin\test.exe");
409
410 var result = WixRunner.Execute(new[]
411 {
412 "build",
413 Path.Combine(folder, "BadInput", "PackageInMultipleContainers.wxs"),
414 Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"),
415 Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"),
416 "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
417 "-bindpath", Path.Combine(folder, ".Data"),
418 "-intermediateFolder", intermediateFolder,
419 "-o", exePath,
420 });
421
422 Assert.Equal((int)LinkerErrors.Ids.PackageInMultipleContainers, result.ExitCode);
423 }
424 }
425
426 [Fact]
427 public void CantBuildWithUnscheduledPackage()
428 {
429 var folder = TestData.Get(@"TestData");
430
431 using (var fs = new DisposableFileSystem())
432 {
433 var baseFolder = fs.GetFolder();
434 var intermediateFolder = Path.Combine(baseFolder, "obj");
435 var exePath = Path.Combine(baseFolder, @"bin\test.exe");
436
437 var result = WixRunner.Execute(new[]
438 {
439 "build",
440 Path.Combine(folder, "BadInput", "UnscheduledPackage.wxs"),
441 Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"),
442 "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
443 "-bindpath", Path.Combine(folder, ".Data"),
444 "-intermediateFolder", intermediateFolder,
445 "-o", exePath,
446 });
447
448 Assert.Equal((int)LinkerErrors.Ids.UnscheduledChainPackage, result.ExitCode);
449 }
450 }
451
452 [Fact]
453 public void CantBuildWithUnscheduledRollbackBoundary()
454 {
455 var folder = TestData.Get(@"TestData");
456
457 using (var fs = new DisposableFileSystem())
458 {
459 var baseFolder = fs.GetFolder();
460 var intermediateFolder = Path.Combine(baseFolder, "obj");
461 var exePath = Path.Combine(baseFolder, @"bin\test.exe");
462
463 var result = WixRunner.Execute(new[]
464 {
465 "build",
466 Path.Combine(folder, "BadInput", "UnscheduledRollbackBoundary.wxs"),
467 Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"),
468 "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
469 "-bindpath", Path.Combine(folder, ".Data"),
470 "-intermediateFolder", intermediateFolder,
471 "-o", exePath,
472 });
473
474 Assert.Equal((int)LinkerErrors.Ids.UnscheduledRollbackBoundary, result.ExitCode);
475 }
476 }
477 }
478}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using Example.Extension;
9 using WixBuildTools.TestSupport;
10 using WixToolset.Core.TestPackage;
11 using Xunit;
12
13 public class BundleManifestFixture
14 {
15 [Fact]
16 public void PopulatesBAManifestWithBootstrapperApplicationBundleCustomData()
17 {
18 var folder = TestData.Get(@"TestData");
19
20 using (var fs = new DisposableFileSystem())
21 {
22 var baseFolder = fs.GetFolder();
23 var intermediateFolder = Path.Combine(baseFolder, "obj");
24 var bundlePath = Path.Combine(baseFolder, @"bin\test.exe");
25 var baFolderPath = Path.Combine(baseFolder, "ba");
26 var extractFolderPath = Path.Combine(baseFolder, "extract");
27
28 var result = WixRunner.Execute(new[]
29 {
30 "build",
31 Path.Combine(folder, "BundleCustomTable", "BundleCustomTable.wxs"),
32 Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"),
33 Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"),
34 "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
35 "-intermediateFolder", intermediateFolder,
36 "-o", bundlePath
37 });
38
39 result.AssertSuccess();
40
41 Assert.True(File.Exists(bundlePath));
42
43 var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath);
44 extractResult.AssertSuccess();
45
46 var customElements = extractResult.SelectBADataNodes("/ba:BootstrapperApplicationData/ba:BundleCustomTableBA");
47 Assert.Equal(3, customElements.Count);
48 Assert.Equal("<BundleCustomTableBA Id='one' Column2='two' />", customElements[0].GetTestXml());
49 Assert.Equal("<BundleCustomTableBA Id='&gt;' Column2='&lt;' />", customElements[1].GetTestXml());
50 Assert.Equal("<BundleCustomTableBA Id='1' Column2='2' />", customElements[2].GetTestXml());
51 }
52 }
53
54 [Fact]
55 public void PopulatesBAManifestWithPackageInformation()
56 {
57 var folder = TestData.Get(@"TestData");
58
59 using (var fs = new DisposableFileSystem())
60 {
61 var baseFolder = fs.GetFolder();
62 var intermediateFolder = Path.Combine(baseFolder, "obj");
63 var bundlePath = Path.Combine(baseFolder, @"bin\test.exe");
64 var baFolderPath = Path.Combine(baseFolder, "ba");
65 var extractFolderPath = Path.Combine(baseFolder, "extract");
66
67 var result = WixRunner.Execute(false, new[]
68 {
69 "build",
70 Path.Combine(folder, "CustomPackageDescription", "CustomPackageDescription.wxs"),
71 Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"),
72 "-bindpath", Path.Combine(folder, ".Data"),
73 "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
74 "-intermediateFolder", intermediateFolder,
75 "-o", bundlePath
76 });
77
78 result.AssertSuccess();
79
80 Assert.True(File.Exists(bundlePath));
81
82 var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath);
83 extractResult.AssertSuccess();
84
85 var packageElements = extractResult.SelectBADataNodes("/ba:BootstrapperApplicationData/ba:WixPackageProperties");
86 var ignoreAttributesByElementName = new Dictionary<string, List<string>>
87 {
88 { "WixPackageProperties", new List<string> { "DownloadSize", "PackageSize", "InstalledSize", "Version" } },
89 };
90 Assert.Equal(3, packageElements.Count);
91 Assert.Equal("<WixPackageProperties Package='burn.exe' Vital='yes' DisplayName='Windows Installer XML Toolset' Description='WiX Toolset Bootstrapper' DownloadSize='*' PackageSize='*' InstalledSize='*' PackageType='Exe' Permanent='yes' LogPathVariable='WixBundleLog_burn.exe' RollbackLogPathVariable='WixBundleRollbackLog_burn.exe' Compressed='yes' Version='*' Cache='keep' />", packageElements[0].GetTestXml(ignoreAttributesByElementName));
92 Assert.Equal("<WixPackageProperties Package='RemotePayloadExe' Vital='yes' DisplayName='Override RemotePayload display name' Description='Override RemotePayload description' DownloadSize='1' PackageSize='1' InstalledSize='1' PackageType='Exe' Permanent='yes' LogPathVariable='WixBundleLog_RemotePayloadExe' RollbackLogPathVariable='WixBundleRollbackLog_RemotePayloadExe' Compressed='no' Version='1.0.0.0' Cache='keep' />", packageElements[1].GetTestXml());
93 Assert.Equal("<WixPackageProperties Package='calc.exe' Vital='yes' DisplayName='Override harvested display name' Description='Override harvested description' DownloadSize='*' PackageSize='*' InstalledSize='*' PackageType='Exe' Permanent='yes' LogPathVariable='WixBundleLog_calc.exe' RollbackLogPathVariable='WixBundleRollbackLog_calc.exe' Compressed='yes' Version='*' Cache='keep' />", packageElements[2].GetTestXml(ignoreAttributesByElementName));
94 }
95 }
96
97 [Fact]
98 public void PopulatesBAManifestWithPayloadInformation()
99 {
100 var folder = TestData.Get(@"TestData");
101
102 using (var fs = new DisposableFileSystem())
103 {
104 var baseFolder = fs.GetFolder();
105 var intermediateFolder = Path.Combine(baseFolder, "obj");
106 var bundlePath = Path.Combine(baseFolder, @"bin\test.exe");
107 var baFolderPath = Path.Combine(baseFolder, "ba");
108 var extractFolderPath = Path.Combine(baseFolder, "extract");
109
110 var result = WixRunner.Execute(false, new[]
111 {
112 "build",
113 Path.Combine(folder, "SharedPayloadsBetweenPackages", "SharedPayloadsBetweenPackages.wxs"),
114 Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"),
115 "-bindpath", Path.Combine(folder, ".Data"),
116 "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
117 "-intermediateFolder", intermediateFolder,
118 "-o", bundlePath
119 });
120
121 result.AssertSuccess();
122
123 Assert.True(File.Exists(bundlePath));
124
125 var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath);
126 extractResult.AssertSuccess();
127
128 var payloadElements = extractResult.SelectBADataNodes("/ba:BootstrapperApplicationData/ba:WixPayloadProperties");
129 var ignoreAttributesByElementName = new Dictionary<string, List<string>>
130 {
131 { "WixPayloadProperties", new List<string> { "Size" } },
132 };
133 Assert.Equal(4, payloadElements.Count);
134 Assert.Equal("<WixPayloadProperties Package='credwiz.exe' Payload='SourceFilePayload' Container='WixAttachedContainer' Name='SharedPayloadsBetweenPackages.wxs' Size='*' />", payloadElements[0].GetTestXml(ignoreAttributesByElementName));
135 Assert.Equal("<WixPayloadProperties Package='credwiz.exe' Payload='credwiz.exe' Container='WixAttachedContainer' Name='credwiz.exe' Size='*' />", payloadElements[1].GetTestXml(ignoreAttributesByElementName));
136 Assert.Equal("<WixPayloadProperties Package='cscript.exe' Payload='SourceFilePayload' Container='WixAttachedContainer' Name='SharedPayloadsBetweenPackages.wxs' Size='*' />", payloadElements[2].GetTestXml(ignoreAttributesByElementName));
137 Assert.Equal("<WixPayloadProperties Package='cscript.exe' Payload='cscript.exe' Container='WixAttachedContainer' Name='cscript.exe' Size='*' />", payloadElements[3].GetTestXml(ignoreAttributesByElementName));
138 }
139 }
140
141 [Fact]
142 public void PopulatesBEManifestWithBundleExtensionBundleCustomData()
143 {
144 var folder = TestData.Get(@"TestData");
145
146 using (var fs = new DisposableFileSystem())
147 {
148 var baseFolder = fs.GetFolder();
149 var intermediateFolder = Path.Combine(baseFolder, "obj");
150 var bundlePath = Path.Combine(baseFolder, @"bin\test.exe");
151 var baFolderPath = Path.Combine(baseFolder, "ba");
152 var extractFolderPath = Path.Combine(baseFolder, "extract");
153
154 var result = WixRunner.Execute(new[]
155 {
156 "build",
157 Path.Combine(folder, "BundleCustomTable", "BundleCustomTable.wxs"),
158 Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"),
159 Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"),
160 "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
161 "-intermediateFolder", intermediateFolder,
162 "-o", bundlePath
163 });
164
165 result.AssertSuccess();
166
167 Assert.True(File.Exists(bundlePath));
168
169 var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath);
170 extractResult.AssertSuccess();
171
172 var customElements = extractResult.SelectBundleExtensionDataNodes("/be:BundleExtensionData/be:BundleExtension[@Id='CustomTableExtension']/be:BundleCustomTableBE");
173 Assert.Equal(3, customElements.Count);
174 Assert.Equal("<BundleCustomTableBE Id='one' Column2='two' />", customElements[0].GetTestXml());
175 Assert.Equal("<BundleCustomTableBE Id='&gt;' Column2='&lt;' />", customElements[1].GetTestXml());
176 Assert.Equal("<BundleCustomTableBE Id='1' Column2='2' />", customElements[2].GetTestXml());
177 }
178 }
179
180 [Fact]
181 public void PopulatesManifestWithBundleExtension()
182 {
183 var folder = TestData.Get(@"TestData");
184
185 using (var fs = new DisposableFileSystem())
186 {
187 var baseFolder = fs.GetFolder();
188 var intermediateFolder = Path.Combine(baseFolder, "obj");
189 var bundlePath = Path.Combine(baseFolder, @"bin\test.exe");
190 var baFolderPath = Path.Combine(baseFolder, "ba");
191 var extractFolderPath = Path.Combine(baseFolder, "extract");
192
193 var result = WixRunner.Execute(new[]
194 {
195 "build",
196 Path.Combine(folder, "BundleExtension", "BundleExtension.wxs"),
197 Path.Combine(folder, "BundleExtension", "SimpleBundleExtension.wxs"),
198 Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"),
199 Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"),
200 "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
201 "-intermediateFolder", intermediateFolder,
202 "-o", bundlePath
203 });
204
205 result.AssertSuccess();
206
207 Assert.True(File.Exists(bundlePath));
208
209 var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath);
210 extractResult.AssertSuccess();
211
212 var bundleExtensions = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:BundleExtension");
213 Assert.Equal(1, bundleExtensions.Count);
214 Assert.Equal("<BundleExtension Id='ExampleBext' EntryPayloadId='ExampleBext' />", bundleExtensions[0].GetTestXml());
215
216 var bundleExtensionPayloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:UX/burn:Payload[@Id='ExampleBext']");
217 Assert.Equal(1, bundleExtensionPayloads.Count);
218 var ignored = new Dictionary<string, List<string>>
219 {
220 { "Payload", new List<string> { "FileSize", "Hash", "SourcePath" } },
221 };
222 Assert.Equal("<Payload Id='ExampleBext' FilePath='fakebext.dll' FileSize='*' Hash='*' Packaging='embedded' SourcePath='*' />", bundleExtensionPayloads[0].GetTestXml(ignored));
223 }
224 }
225
226 [Fact]
227 public void PopulatesManifestWithBundleExtensionSearches()
228 {
229 var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath);
230 var folder = TestData.Get(@"TestData");
231
232 using (var fs = new DisposableFileSystem())
233 {
234 var baseFolder = fs.GetFolder();
235 var intermediateFolder = Path.Combine(baseFolder, "obj");
236 var bundlePath = Path.Combine(baseFolder, @"bin\test.exe");
237 var baFolderPath = Path.Combine(baseFolder, "ba");
238 var extractFolderPath = Path.Combine(baseFolder, "extract");
239
240 var result = WixRunner.Execute(new[]
241 {
242 "build",
243 Path.Combine(folder, "BundleExtension", "BundleExtensionSearches.wxs"),
244 Path.Combine(folder, "BundleExtension", "BundleWithSearches.wxs"),
245 Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"),
246 Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"),
247 "-ext", extensionPath,
248 "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
249 "-intermediateFolder", intermediateFolder,
250 "-o", bundlePath
251 });
252
253 result.AssertSuccess();
254
255 Assert.True(File.Exists(bundlePath));
256
257 var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath);
258 extractResult.AssertSuccess();
259
260 var bundleExtensions = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:BundleExtension");
261 Assert.Equal(1, bundleExtensions.Count);
262 Assert.Equal("<BundleExtension Id='ExampleBundleExtension' EntryPayloadId='ExampleBundleExtension' />", bundleExtensions[0].GetTestXml());
263
264 var extensionSearches = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:ExtensionSearch");
265 Assert.Equal(2, extensionSearches.Count);
266 Assert.Equal("<ExtensionSearch Id='ExampleSearchBar' Variable='SearchBar' Condition='WixBundleInstalled' ExtensionId='ExampleBundleExtension' />", extensionSearches[0].GetTestXml());
267 Assert.Equal("<ExtensionSearch Id='ExampleSearchFoo' Variable='SearchFoo' ExtensionId='ExampleBundleExtension' />", extensionSearches[1].GetTestXml());
268
269 var bundleExtensionDatas = extractResult.SelectBundleExtensionDataNodes("/be:BundleExtensionData/be:BundleExtension[@Id='ExampleBundleExtension']");
270 Assert.Equal(1, bundleExtensionDatas.Count);
271 Assert.Equal("<BundleExtension Id='ExampleBundleExtension'>" +
272 "<ExampleSearch Id='ExampleSearchBar' SearchFor='Bar' />" +
273 "<ExampleSearch Id='ExampleSearchFoo' SearchFor='Foo' />" +
274 "</BundleExtension>", bundleExtensionDatas[0].GetTestXml());
275
276 var exampleSearches = extractResult.SelectBundleExtensionDataNodes("/be:BundleExtensionData/be:BundleExtension[@Id='ExampleBundleExtension']/be:ExampleSearch");
277 Assert.Equal(2, exampleSearches.Count);
278 }
279 }
280
281 [Fact]
282 public void PopulatesManifestWithExePackages()
283 {
284 var folder = TestData.Get(@"TestData");
285
286 using (var fs = new DisposableFileSystem())
287 {
288 var baseFolder = fs.GetFolder();
289 var intermediateFolder = Path.Combine(baseFolder, "obj");
290 var bundlePath = Path.Combine(baseFolder, @"bin\test.exe");
291 var baFolderPath = Path.Combine(baseFolder, "ba");
292 var extractFolderPath = Path.Combine(baseFolder, "extract");
293
294 var result = WixRunner.Execute(new[]
295 {
296 "build",
297 Path.Combine(folder, "SharedPayloadsBetweenPackages", "SharedPayloadsBetweenPackages.wxs"),
298 Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"),
299 "-bindpath", Path.Combine(folder, ".Data"),
300 "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
301 "-intermediateFolder", intermediateFolder,
302 "-o", bundlePath
303 });
304
305 result.AssertSuccess();
306
307 Assert.True(File.Exists(bundlePath));
308
309 var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath);
310 extractResult.AssertSuccess();
311
312 var exePackageElements = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Chain/burn:ExePackage");
313 var ignoreAttributesByElementName = new Dictionary<string, List<string>>
314 {
315 { "ExePackage", new List<string> { "CacheId", "InstallSize", "Size" } },
316 };
317 Assert.Equal(2, exePackageElements.Count);
318 Assert.Equal("<ExePackage Id='credwiz.exe' Cache='keep' CacheId='*' InstallSize='*' Size='*' PerMachine='yes' Permanent='yes' Vital='yes' RollbackBoundaryForward='WixDefaultBoundary' LogPathVariable='WixBundleLog_credwiz.exe' RollbackLogPathVariable='WixBundleRollbackLog_credwiz.exe' DetectCondition='none' InstallArguments='' UninstallArguments='' RepairArguments='' Repairable='no'><PayloadRef Id='credwiz.exe' /><PayloadRef Id='SourceFilePayload' /></ExePackage>", exePackageElements[0].GetTestXml(ignoreAttributesByElementName));
319 Assert.Equal("<ExePackage Id='cscript.exe' Cache='keep' CacheId='*' InstallSize='*' Size='*' PerMachine='yes' Permanent='yes' Vital='yes' RollbackBoundaryBackward='WixDefaultBoundary' LogPathVariable='WixBundleLog_cscript.exe' RollbackLogPathVariable='WixBundleRollbackLog_cscript.exe' DetectCondition='none' InstallArguments='' UninstallArguments='' RepairArguments='' Repairable='no'><PayloadRef Id='cscript.exe' /><PayloadRef Id='SourceFilePayload' /></ExePackage>", exePackageElements[1].GetTestXml(ignoreAttributesByElementName));
320 }
321 }
322
323 [Fact]
324 public void PopulatesManifestWithSetVariables()
325 {
326 var folder = TestData.Get(@"TestData");
327
328 using (var fs = new DisposableFileSystem())
329 {
330 var baseFolder = fs.GetFolder();
331 var intermediateFolder = Path.Combine(baseFolder, "obj");
332 var bundlePath = Path.Combine(baseFolder, @"bin\test.exe");
333 var baFolderPath = Path.Combine(baseFolder, "ba");
334 var extractFolderPath = Path.Combine(baseFolder, "extract");
335
336 var result = WixRunner.Execute(new[]
337 {
338 "build",
339 Path.Combine(folder, "SetVariable", "Simple.wxs"),
340 Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"),
341 Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"),
342 "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
343 "-intermediateFolder", intermediateFolder,
344 "-o", bundlePath
345 });
346
347 result.AssertSuccess();
348
349 Assert.True(File.Exists(bundlePath));
350
351 var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath);
352 extractResult.AssertSuccess();
353
354 var setVariables = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:SetVariable");
355 Assert.Equal(6, setVariables.Count);
356 Assert.Equal("<SetVariable Id='SetCoercedNumber' Variable='CoercedNumber' Value='2' Type='numeric' />", setVariables[0].GetTestXml());
357 Assert.Equal("<SetVariable Id='SetCoercedString' Variable='CoercedString' Value='Bar' Type='string' />", setVariables[1].GetTestXml());
358 Assert.Equal("<SetVariable Id='SetCoercedVersion' Variable='CoercedVersion' Value='v2.0' Type='version' />", setVariables[2].GetTestXml());
359 Assert.Equal("<SetVariable Id='SetNeedsFormatting' Variable='NeedsFormatting' Value='[One] [Two] [Three]' Type='string' />", setVariables[3].GetTestXml());
360 Assert.Equal("<SetVariable Id='SetVersionString' Variable='VersionString' Value='v1.0' Type='string' />", setVariables[4].GetTestXml());
361 Assert.Equal("<SetVariable Id='SetUnset' Variable='Unset' Condition='VersionString = v2.0' />", setVariables[5].GetTestXml());
362 }
363 }
364 }
365}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System;
6 using System.IO;
7 using System.Linq;
8 using WixBuildTools.TestSupport;
9 using WixToolset.Core.TestPackage;
10 using Xunit;
11
12 public class CabFixture
13 {
14 [Fact]
15 public void CabinetFilesSequencedCorrectly()
16 {
17 var folder = TestData.Get(@"TestData\MultiFileCompressed");
18
19 using (var fs = new DisposableFileSystem())
20 {
21 var baseFolder = fs.GetFolder();
22 var intermediateFolder = Path.Combine(baseFolder, "obj");
23 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
24 var cabPath = Path.Combine(baseFolder, @"bin\cab1.cab");
25
26 var result = WixRunner.Execute(new[]
27 {
28 "build",
29 Path.Combine(folder, "Package.wxs"),
30 Path.Combine(folder, "PackageComponents.wxs"),
31 "-d", "MediaTemplateCompressionLevel",
32 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
33 "-bindpath", Path.Combine(folder, "data"),
34 "-intermediateFolder", intermediateFolder,
35 "-o", msiPath
36 });
37
38 result.AssertSuccess();
39 Assert.True(File.Exists(cabPath));
40
41 var fileTable = Query.QueryDatabase(msiPath, new[] { "File" });
42 var fileRows = fileTable.Select(r => new FileRow(r)).OrderBy(f => f.Sequence).ToList();
43
44 Assert.Equal(new[] { 1, 2 }, fileRows.Select(f => f.Sequence).ToArray());
45 Assert.Equal(new[] { "Notepad.exe", "test.txt" }, fileRows.Select(f => f.Name).ToArray());
46
47 var files = Query.GetCabinetFiles(cabPath);
48 Assert.Equal(fileRows.Select(f => f.Id).ToArray(), files.Select(f => f.Name).ToArray());
49 }
50 }
51
52 [Fact(Skip = "Sequence number of file from merge module is 0 but should be 1.")]
53 public void CabinetFilesSequencedCorrectlyUsingMergeModule()
54 {
55 var folder = TestData.Get(@"TestData\SimpleMerge");
56
57 using (var fs = new DisposableFileSystem())
58 {
59 var baseFolder = fs.GetFolder();
60 var intermediateFolder = Path.Combine(baseFolder, "obj");
61 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
62 var cabPath = Path.Combine(baseFolder, @"bin\cab1.cab");
63
64 var result = WixRunner.Execute(new[]
65 {
66 "build",
67 Path.Combine(folder, "Package.wxs"),
68 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
69 "-bindpath", Path.Combine(folder, ".data"),
70 "-intermediateFolder", intermediateFolder,
71 "-o", msiPath
72 });
73
74 result.AssertSuccess();
75 Assert.True(File.Exists(cabPath));
76
77 var fileTable = Query.QueryDatabase(msiPath, new[] { "File" });
78 var fileRows = fileTable.Select(r => new FileRow(r)).OrderBy(f => f.Sequence).ToList();
79
80 Assert.Equal(new[] { 1 }, fileRows.Select(f => f.Sequence).ToArray());
81 Assert.Equal(new[] { "test.txt" }, fileRows.Select(f => f.Name).ToArray());
82
83 var files = Query.GetCabinetFiles(cabPath);
84 Assert.Equal(fileRows.Select(f => f.Id).ToArray(), files.Select(f => f.Name).ToArray());
85 }
86 }
87
88 private class FileRow
89 {
90 public FileRow(string row)
91 {
92 row = row.Substring("File:".Length);
93
94 var split = row.Split('\t');
95 this.Id = split[0];
96 this.Name = split[2];
97 this.Sequence = Convert.ToInt32(split[7]);
98 }
99
100 public string Id { get; set; }
101
102 public string Name { get; set; }
103
104 public int Sequence { get; set; }
105 }
106 }
107}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System;
6 using System.IO;
7 using System.Linq;
8 using WixBuildTools.TestSupport;
9 using WixToolset.Core.TestPackage;
10 using WixToolset.Data;
11 using Xunit;
12
13 public class ComponentFixture
14 {
15 [Fact]
16 public void CanDetectDuplicateComponentGuids()
17 {
18 var folder = TestData.Get(@"TestData");
19
20 using (var fs = new DisposableFileSystem())
21 {
22 var baseFolder = fs.GetFolder();
23 var intermediateFolder = Path.Combine(baseFolder, "obj");
24 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
25
26 var result = WixRunner.Execute(new[]
27 {
28 "build",
29 Path.Combine(folder, "Component", "GuidCollision.wxs"),
30 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
31 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
32 "-intermediateFolder", intermediateFolder,
33 "-o", msiPath
34 });
35
36 var errors = result.Messages.Where(m => m.Level == MessageLevel.Error);
37 Array.Equals(new[]
38 {
39 369,
40 369
41 }, errors.Select(e => e.Id).ToArray());
42 }
43 }
44 }
45}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 using System.Xml;
10 using WixBuildTools.TestSupport;
11 using WixToolset.Core;
12 using WixToolset.Core.Burn;
13 using WixToolset.Core.TestPackage;
14 using Xunit;
15
16 public class ContainerFixture
17 {
18 [Fact(Skip = "Test demonstrates failure")]
19 public void CanBuildWithCustomAttachedContainer()
20 {
21 var folder = TestData.Get(@"TestData");
22
23 using (var fs = new DisposableFileSystem())
24 {
25 var baseFolder = fs.GetFolder();
26 var intermediateFolder = Path.Combine(baseFolder, "obj");
27 var binFolder = Path.Combine(baseFolder, "bin");
28 var bundlePath = Path.Combine(binFolder, "test.exe");
29 var baFolderPath = Path.Combine(baseFolder, "ba");
30 var extractFolderPath = Path.Combine(baseFolder, "extract");
31
32 this.BuildMsis(folder, intermediateFolder, binFolder, buildToSubfolder: true);
33
34 var result = WixRunner.Execute(new[]
35 {
36 "build",
37 Path.Combine(folder, "Container", "HarvestIntoAttachedContainer.wxs"),
38 Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"),
39 "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
40 "-bindpath", binFolder,
41 "-intermediateFolder", intermediateFolder,
42 "-o", bundlePath
43 });
44
45 result.AssertSuccess();
46
47 Assert.True(File.Exists(bundlePath));
48
49 var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath);
50 extractResult.AssertSuccess();
51
52 var payloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload");
53 Assert.Equal(4, payloads.Count);
54 var ignoreAttributes = new Dictionary<string, List<string>> { { "Payload", new List<string> { "FileSize", "Hash" } } };
55 Assert.Equal(@"<Payload Id='FirstX64' FilePath='FirstX64\FirstX64.msi' FileSize='*' Hash='*' DownloadUrl='http://example.com//FirstX64/FirstX64/FirstX64.msi' Packaging='embedded' SourcePath='a0' Container='BundlePackages' />", payloads[0].GetTestXml(ignoreAttributes));
56 Assert.Equal(@"<Payload Id='FirstX86.msi' FilePath='FirstX86\FirstX86.msi' FileSize='*' Hash='*' DownloadUrl='http://example.com//FirstX86.msi/FirstX86/FirstX86.msi' Packaging='embedded' SourcePath='a1' Container='BundlePackages' />", payloads[1].GetTestXml(ignoreAttributes));
57 Assert.Equal(@"<Payload Id='fk1m38Cf9RZ2Bx_ipinRY6BftelU' FilePath='FirstX86\PFiles\MsiPackage\test.txt' FileSize='*' Hash='*' DownloadUrl='http://example.com/FirstX86/fk1m38Cf9RZ2Bx_ipinRY6BftelU/FirstX86/PFiles/MsiPackage/test.txt' Packaging='embedded' SourcePath='a2' Container='BundlePackages' />", payloads[2].GetTestXml(ignoreAttributes));
58 Assert.Equal(@"<Payload Id='ff2L_N_DLQ.nSUi.l8LxG14gd2V4' FilePath='FirstX64\PFiles\MsiPackage\test.txt' FileSize='*' Hash='*' DownloadUrl='http://example.com/FirstX64/ff2L_N_DLQ.nSUi.l8LxG14gd2V4/FirstX64/PFiles/MsiPackage/test.txt' Packaging='embedded' SourcePath='a3' Container='BundlePackages' />", payloads[3].GetTestXml(ignoreAttributes));
59 }
60 }
61
62 [Fact]
63 public void HarvestedPayloadsArePutInCorrectContainer()
64 {
65 var folder = TestData.Get(@"TestData");
66
67 using (var fs = new DisposableFileSystem())
68 {
69 var baseFolder = fs.GetFolder();
70 var intermediateFolder = Path.Combine(baseFolder, "obj");
71 var binFolder = Path.Combine(baseFolder, "bin");
72 var bundlePath = Path.Combine(binFolder, "test.exe");
73 var baFolderPath = Path.Combine(baseFolder, "ba");
74 var extractFolderPath = Path.Combine(baseFolder, "extract");
75
76 this.BuildMsis(folder, intermediateFolder, binFolder);
77
78 var result = WixRunner.Execute(new[]
79 {
80 "build",
81 Path.Combine(folder, "Container", "HarvestIntoDetachedContainer.wxs"),
82 Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"),
83 "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
84 "-bindpath", binFolder,
85 "-intermediateFolder", intermediateFolder,
86 "-o", bundlePath
87 });
88
89 result.AssertSuccess();
90
91 Assert.True(File.Exists(bundlePath));
92
93 var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath);
94 extractResult.AssertSuccess();
95
96 var payloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload");
97 Assert.Equal(4, payloads.Count);
98 var ignoreAttributes = new Dictionary<string, List<string>> { { "Payload", new List<string> { "FileSize", "Hash" } } };
99 Assert.Equal(@"<Payload Id='FirstX86.msi' FilePath='FirstX86.msi' FileSize='*' Hash='*' Packaging='embedded' SourcePath='a0' Container='WixAttachedContainer' />", payloads[0].GetTestXml(ignoreAttributes));
100 Assert.Equal(@"<Payload Id='FirstX64.msi' FilePath='FirstX64.msi' FileSize='*' Hash='*' Packaging='embedded' SourcePath='a1' Container='FirstX64' />", payloads[1].GetTestXml(ignoreAttributes));
101 Assert.Equal(@"<Payload Id='fk1m38Cf9RZ2Bx_ipinRY6BftelU' FilePath='PFiles\MsiPackage\test.txt' FileSize='*' Hash='*' Packaging='embedded' SourcePath='a2' Container='WixAttachedContainer' />", payloads[2].GetTestXml(ignoreAttributes));
102 Assert.Equal(@"<Payload Id='fC0n41rZK8oW3JK8LzHu6AT3CjdQ' FilePath='PFiles\MsiPackage\test.txt' FileSize='*' Hash='*' Packaging='embedded' SourcePath='a3' Container='FirstX64' />", payloads[3].GetTestXml(ignoreAttributes));
103 }
104 }
105
106 [Fact]
107 public void HarvestedPayloadsArePutInCorrectPackage()
108 {
109 var folder = TestData.Get(@"TestData");
110
111 using (var fs = new DisposableFileSystem())
112 {
113 var baseFolder = fs.GetFolder();
114 var intermediateFolder = Path.Combine(baseFolder, "obj");
115 var binFolder = Path.Combine(baseFolder, "bin");
116 var bundlePath = Path.Combine(binFolder, "test.exe");
117 var baFolderPath = Path.Combine(baseFolder, "ba");
118 var extractFolderPath = Path.Combine(baseFolder, "extract");
119
120 this.BuildMsis(folder, intermediateFolder, binFolder);
121
122 var result = WixRunner.Execute(new[]
123 {
124 "build",
125 Path.Combine(folder, "Container", "HarvestIntoDetachedContainer.wxs"),
126 Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"),
127 "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
128 "-bindpath", binFolder,
129 "-intermediateFolder", intermediateFolder,
130 "-o", bundlePath
131 });
132
133 result.AssertSuccess();
134
135 Assert.True(File.Exists(bundlePath));
136
137 var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath);
138 extractResult.AssertSuccess();
139
140 var ignoreAttributes = new Dictionary<string, List<string>>
141 {
142 { "MsiPackage", new List<string> { "CacheId", "InstallSize", "Size", "ProductCode" } },
143 { "Provides", new List<string> { "Key" } },
144 };
145 var msiPackages = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Chain/burn:MsiPackage")
146 .Cast<XmlElement>()
147 .Select(e => e.GetTestXml(ignoreAttributes))
148 .ToArray();
149 WixAssert.CompareLineByLine(new string[]
150 {
151 "<MsiPackage Id='FirstX86.msi' Cache='keep' CacheId='*' InstallSize='*' Size='*' PerMachine='yes' Permanent='no' Vital='yes' RollbackBoundaryForward='WixDefaultBoundary' LogPathVariable='WixBundleLog_FirstX86.msi' RollbackLogPathVariable='WixBundleRollbackLog_FirstX86.msi' ProductCode='*' Language='1033' Version='1.0.0.0' UpgradeCode='{12E4699F-E774-4D05-8A01-5BDD41BBA127}'>" +
152 "<MsiProperty Id='ARPSYSTEMCOMPONENT' Value='1' />" +
153 "<Provides Key='*' Version='1.0.0.0' DisplayName='MsiPackage' />" +
154 "<RelatedPackage Id='{12E4699F-E774-4D05-8A01-5BDD41BBA127}' MaxVersion='1.0.0.0' MaxInclusive='no' OnlyDetect='no' LangInclusive='no'><Language Id='1033' /></RelatedPackage>" +
155 "<RelatedPackage Id='{12E4699F-E774-4D05-8A01-5BDD41BBA127}' MinVersion='1.0.0.0' MinInclusive='no' OnlyDetect='yes' LangInclusive='no'><Language Id='1033' /></RelatedPackage>" +
156 "<PayloadRef Id='FirstX86.msi' />" +
157 "<PayloadRef Id='fk1m38Cf9RZ2Bx_ipinRY6BftelU' />" +
158 "</MsiPackage>",
159 "<MsiPackage Id='FirstX64.msi' Cache='keep' CacheId='*' InstallSize='*' Size='*' PerMachine='yes' Permanent='no' Vital='yes' RollbackBoundaryBackward='WixDefaultBoundary' LogPathVariable='WixBundleLog_FirstX64.msi' RollbackLogPathVariable='WixBundleRollbackLog_FirstX64.msi' ProductCode='*' Language='1033' Version='1.0.0.0' UpgradeCode='{12E4699F-E774-4D05-8A01-5BDD41BBA127}'>" +
160 "<MsiProperty Id='ARPSYSTEMCOMPONENT' Value='1' />" +
161 "<Provides Key='*' Version='1.0.0.0' DisplayName='MsiPackage' />" +
162 "<RelatedPackage Id='{12E4699F-E774-4D05-8A01-5BDD41BBA127}' MaxVersion='1.0.0.0' MaxInclusive='no' OnlyDetect='no' LangInclusive='no'><Language Id='1033' /></RelatedPackage>" +
163 "<RelatedPackage Id='{12E4699F-E774-4D05-8A01-5BDD41BBA127}' MinVersion='1.0.0.0' MinInclusive='no' OnlyDetect='yes' LangInclusive='no'><Language Id='1033' /></RelatedPackage>" +
164 "<PayloadRef Id='FirstX64.msi' />" +
165 "<PayloadRef Id='fC0n41rZK8oW3JK8LzHu6AT3CjdQ' />" +
166 "</MsiPackage>",
167 }, msiPackages);
168 }
169 }
170
171 [Fact]
172 public void LayoutPayloadIsPutInContainer()
173 {
174 var folder = TestData.Get(@"TestData");
175
176 using (var fs = new DisposableFileSystem())
177 {
178 var baseFolder = fs.GetFolder();
179 var intermediateFolder = Path.Combine(baseFolder, "obj");
180 var binFolder = Path.Combine(baseFolder, "bin");
181 var bundlePath = Path.Combine(binFolder, "test.exe");
182 var baFolderPath = Path.Combine(baseFolder, "ba");
183 var extractFolderPath = Path.Combine(baseFolder, "extract");
184
185 this.BuildMsis(folder, intermediateFolder, binFolder);
186
187 var result = WixRunner.Execute(false, new[]
188 {
189 "build",
190 Path.Combine(folder, "Container", "LayoutPayloadInContainer.wxs"),
191 "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
192 "-bindpath", binFolder,
193 "-intermediateFolder", intermediateFolder,
194 "-o", bundlePath
195 });
196
197 WixAssert.CompareLineByLine(new string[]
198 {
199 "The layout-only Payload 'SharedPayload' is being added to Container 'FirstX64'. It will not be extracted during layout.",
200 }, result.Messages.Select(m => m.ToString()).ToArray());
201 result.AssertSuccess();
202
203 Assert.True(File.Exists(bundlePath));
204
205 var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath);
206 extractResult.AssertSuccess();
207
208 var ignoreAttributes = new Dictionary<string, List<string>> { { "Payload", new List<string> { "FileSize", "Hash" } } };
209 var payloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload[@Id='SharedPayload']")
210 .Cast<XmlElement>()
211 .Select(e => e.GetTestXml(ignoreAttributes))
212 .ToArray();
213 WixAssert.CompareLineByLine(new string[]
214 {
215 "<Payload Id='SharedPayload' FilePath='LayoutPayloadInContainer.wxs' FileSize='*' Hash='*' LayoutOnly='yes' Packaging='embedded' SourcePath='a1' Container='FirstX64' />",
216 }, payloads);
217 }
218 }
219
220 [Fact]
221 public void MultipleAttachedContainersAreNotCurrentlySupported()
222 {
223 var folder = TestData.Get(@"TestData");
224
225 using (var fs = new DisposableFileSystem())
226 {
227 var baseFolder = fs.GetFolder();
228 var intermediateFolder = Path.Combine(baseFolder, "obj");
229 var binFolder = Path.Combine(baseFolder, "bin");
230 var bundlePath = Path.Combine(binFolder, "test.exe");
231 var baFolderPath = Path.Combine(baseFolder, "ba");
232 var extractFolderPath = Path.Combine(baseFolder, "extract");
233
234 this.BuildMsis(folder, intermediateFolder, binFolder);
235
236 var result = WixRunner.Execute(new[]
237 {
238 "build",
239 Path.Combine(folder, "Container", "MultipleAttachedContainers.wxs"),
240 Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"),
241 "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
242 "-bindpath", binFolder,
243 "-intermediateFolder", intermediateFolder,
244 "-o", bundlePath
245 });
246
247 Assert.Equal((int)BurnBackendErrors.Ids.MultipleAttachedContainersUnsupported, result.ExitCode);
248 }
249 }
250
251 [Fact]
252 public void PayloadIsNotPutInMultipleContainers()
253 {
254 var folder = TestData.Get(@"TestData");
255
256 using (var fs = new DisposableFileSystem())
257 {
258 var baseFolder = fs.GetFolder();
259 var intermediateFolder = Path.Combine(baseFolder, "obj");
260 var binFolder = Path.Combine(baseFolder, "bin");
261 var bundlePath = Path.Combine(binFolder, "test.exe");
262 var baFolderPath = Path.Combine(baseFolder, "ba");
263 var extractFolderPath = Path.Combine(baseFolder, "extract");
264
265 this.BuildMsis(folder, intermediateFolder, binFolder);
266
267 var result = WixRunner.Execute(false, new[]
268 {
269 "build",
270 Path.Combine(folder, "Container", "PayloadInMultipleContainers.wxs"),
271 Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"),
272 "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
273 "-bindpath", binFolder,
274 "-intermediateFolder", intermediateFolder,
275 "-o", bundlePath
276 });
277
278 WixAssert.CompareLineByLine(new string[]
279 {
280 "The Payload 'SharedPayload' can't be added to Container 'FirstX64' because it was already added to Container 'FirstX86'.",
281 }, result.Messages.Select(m => m.ToString()).ToArray());
282 result.AssertSuccess();
283
284 Assert.True(File.Exists(bundlePath));
285
286 var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath);
287 extractResult.AssertSuccess();
288
289 var ignoreAttributes = new Dictionary<string, List<string>> { { "Payload", new List<string> { "FileSize", "Hash" } } };
290 var payloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload[@Id='SharedPayload']")
291 .Cast<XmlElement>()
292 .Select(e => e.GetTestXml(ignoreAttributes))
293 .ToArray();
294 WixAssert.CompareLineByLine(new string[]
295 {
296 "<Payload Id='SharedPayload' FilePath='PayloadInMultipleContainers.wxs' FileSize='*' Hash='*' Packaging='embedded' SourcePath='a2' Container='FirstX86' />",
297 }, payloads);
298 }
299 }
300
301 [Fact]
302 public void PopulatesBAManifestWithLayoutOnlyPayloads()
303 {
304 var folder = TestData.Get(@"TestData");
305
306 using (var fs = new DisposableFileSystem())
307 {
308 var baseFolder = fs.GetFolder();
309 var intermediateFolder = Path.Combine(baseFolder, "obj");
310 var binFolder = Path.Combine(baseFolder, "bin");
311 var bundlePath = Path.Combine(binFolder, "test.exe");
312 var baFolderPath = Path.Combine(baseFolder, "ba");
313 var extractFolderPath = Path.Combine(baseFolder, "extract");
314
315 this.BuildMsis(folder, intermediateFolder, binFolder);
316
317 var result = WixRunner.Execute(false, new[]
318 {
319 "build",
320 Path.Combine(folder, "Container", "LayoutPayloadInContainer.wxs"),
321 "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
322 "-bindpath", binFolder,
323 "-intermediateFolder", intermediateFolder,
324 "-o", bundlePath
325 });
326
327 WixAssert.CompareLineByLine(new string[]
328 {
329 "The layout-only Payload 'SharedPayload' is being added to Container 'FirstX64'. It will not be extracted during layout.",
330 }, result.Messages.Select(m => m.ToString()).ToArray());
331 result.AssertSuccess();
332
333 Assert.True(File.Exists(bundlePath));
334
335 var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath);
336 extractResult.AssertSuccess();
337
338 var ignoreAttributesByElementName = new Dictionary<string, List<string>>
339 {
340 { "WixPayloadProperties", new List<string> { "Size" } },
341 };
342 var payloads = extractResult.SelectBADataNodes("/ba:BootstrapperApplicationData/ba:WixPayloadProperties")
343 .Cast<XmlElement>()
344 .Select(e => e.GetTestXml(ignoreAttributesByElementName))
345 .ToArray();
346 WixAssert.CompareLineByLine(new string[]
347 {
348 "<WixPayloadProperties Package='FirstX64.msi' Payload='FirstX64.msi' Container='FirstX64' Name='FirstX64.msi' Size='*' />",
349 "<WixPayloadProperties Package='FirstX64.msi' Payload='SharedPayload' Container='FirstX64' Name='LayoutPayloadInContainer.wxs' Size='*' />",
350 "<WixPayloadProperties Package='FirstX64.msi' Payload='fC0n41rZK8oW3JK8LzHu6AT3CjdQ' Container='FirstX64' Name='PFiles\\MsiPackage\\test.txt' Size='*' />",
351 "<WixPayloadProperties Payload='SharedPayload' Container='FirstX64' Name='LayoutPayloadInContainer.wxs' Size='*' />",
352 }, payloads);
353 }
354 }
355
356 private void BuildMsis(string folder, string intermediateFolder, string binFolder, bool buildToSubfolder = false)
357 {
358 var result = WixRunner.Execute(new[]
359 {
360 "build",
361 Path.Combine(folder, "MsiTransaction", "FirstX86.wxs"),
362 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
363 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
364 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
365 "-intermediateFolder", intermediateFolder,
366 "-o", Path.Combine(binFolder, buildToSubfolder ? "FirstX86" : ".", "FirstX86.msi"),
367 });
368
369 result.AssertSuccess();
370
371 result = WixRunner.Execute(new[]
372 {
373 "build",
374 Path.Combine(folder, "MsiTransaction", "FirstX64.wxs"),
375 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
376 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
377 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
378 "-intermediateFolder", intermediateFolder,
379 "-o", Path.Combine(binFolder, buildToSubfolder ? "FirstX64" : ".", "FirstX64.msi"),
380 });
381
382 result.AssertSuccess();
383 }
384 }
385}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System.IO;
6 using System.Linq;
7 using WixBuildTools.TestSupport;
8 using WixToolset.Core.TestPackage;
9 using WixToolset.Data;
10 using WixToolset.Data.Symbols;
11 using Xunit;
12
13 public class CopyFileFixture
14 {
15 [Fact]
16 public void CanBuildCopyFile()
17 {
18 var folder = TestData.Get(@"TestData");
19
20 using (var fs = new DisposableFileSystem())
21 {
22 var baseFolder = fs.GetFolder();
23 var intermediateFolder = Path.Combine(baseFolder, "obj");
24 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
25
26 var result = WixRunner.Execute(new[]
27 {
28 "build",
29 Path.Combine(folder, "CopyFile", "CopyFile.wxs"),
30 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
31 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
32 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
33 "-intermediateFolder", intermediateFolder,
34 "-o", msiPath
35 });
36
37 result.AssertSuccess();
38
39 var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb"));
40 var section = intermediate.Sections.Single();
41 var copyFileSymbol = section.Symbols.OfType<MoveFileSymbol>().Single();
42 Assert.Equal("MoveText", copyFileSymbol.Id.Id);
43 Assert.True(copyFileSymbol.Delete);
44 Assert.Equal("OtherFolder", copyFileSymbol.DestFolder);
45 }
46 }
47 }
48}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System.IO;
6 using System.Linq;
7 using WixBuildTools.TestSupport;
8 using WixToolset.Core.TestPackage;
9 using Xunit;
10
11 public class CustomActionFixture
12 {
13 [Fact]
14 public void CanDetectCustomActionCycle()
15 {
16 var folder = TestData.Get(@"TestData");
17
18 using (var fs = new DisposableFileSystem())
19 {
20 var baseFolder = fs.GetFolder();
21 var intermediateFolder = Path.Combine(baseFolder, "obj");
22 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
23
24 var result = WixRunner.Execute(new[]
25 {
26 "build",
27 Path.Combine(folder, "CustomAction", "CustomActionCycle.wxs"),
28 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
29 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
30 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
31 "-intermediateFolder", intermediateFolder,
32 "-o", msiPath
33 });
34
35 Assert.Equal(176, result.ExitCode);
36 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());
37 }
38 }
39
40 [Fact]
41 public void CanDetectCustomActionCycleWithTail()
42 {
43 var folder = TestData.Get(@"TestData");
44
45 using (var fs = new DisposableFileSystem())
46 {
47 var baseFolder = fs.GetFolder();
48 var intermediateFolder = Path.Combine(baseFolder, "obj");
49 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
50
51 var result = WixRunner.Execute(new[]
52 {
53 "build",
54 Path.Combine(folder, "CustomAction", "CustomActionCycleWithTail.wxs"),
55 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
56 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
57 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
58 "-intermediateFolder", intermediateFolder,
59 "-o", msiPath
60 });
61
62 Assert.Equal(176, result.ExitCode);
63 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());
64 }
65 }
66
67 [Fact]
68 public void PopulatesCustomActionTable()
69 {
70 var folder = TestData.Get(@"TestData");
71
72 using (var fs = new DisposableFileSystem())
73 {
74 var baseFolder = fs.GetFolder();
75 var intermediateFolder = Path.Combine(baseFolder, "obj");
76 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
77
78 var result = WixRunner.Execute(new[]
79 {
80 "build",
81 Path.Combine(folder, "CustomAction", "UnscheduledCustomAction.wxs"),
82 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
83 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
84 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
85 "-intermediateFolder", intermediateFolder,
86 "-o", msiPath
87 });
88
89 result.AssertSuccess();
90
91 Assert.True(File.Exists(msiPath));
92 var results = Query.QueryDatabase(msiPath, new[] {
93 "ActionText",
94 "AdminExecuteSequence",
95 "AdminUISequence",
96 "AdvtExecuteSequence",
97 "Binary",
98 "CustomAction",
99 "InstallExecuteSequence",
100 "InstallUISequence",
101 "Property",
102 }).Where(x => !x.StartsWith("Property:") || x.StartsWith("Property:MsiHiddenProperties\t")).ToArray();
103 Assert.Equal(new[]
104 {
105 "ActionText:CustomAction2\tProgess2Text\t",
106 "AdminExecuteSequence:CostFinalize\t\t1000",
107 "AdminExecuteSequence:CostInitialize\t\t800",
108 "AdminExecuteSequence:CustomAction2\t\t801",
109 "AdminExecuteSequence:FileCost\t\t900",
110 "AdminExecuteSequence:InstallAdminPackage\t\t3900",
111 "AdminExecuteSequence:InstallFiles\t\t4000",
112 "AdminExecuteSequence:InstallFinalize\t\t6600",
113 "AdminExecuteSequence:InstallInitialize\t\t1500",
114 "AdminExecuteSequence:InstallValidate\t\t1400",
115 "AdminUISequence:CostFinalize\t\t1000",
116 "AdminUISequence:CostInitialize\t\t800",
117 "AdminUISequence:CustomAction2\t\t801",
118 "AdminUISequence:ExecuteAction\t\t1300",
119 "AdminUISequence:FileCost\t\t900",
120 "AdvtExecuteSequence:CostFinalize\t\t1000",
121 "AdvtExecuteSequence:CostInitialize\t\t800",
122 "AdvtExecuteSequence:CustomAction2\t\t801",
123 "AdvtExecuteSequence:InstallFinalize\t\t6600",
124 "AdvtExecuteSequence:InstallInitialize\t\t1500",
125 "AdvtExecuteSequence:InstallValidate\t\t1400",
126 "AdvtExecuteSequence:PublishFeatures\t\t6300",
127 "AdvtExecuteSequence:PublishProduct\t\t6400",
128 "Binary:Binary1\t[Binary data]",
129 "CustomAction:CustomAction1\t1\tBinary1\tInvalidEntryPoint\t",
130 "CustomAction:CustomAction2\t51\tTestAdvtExecuteSequenceProperty\t1\t",
131 "CustomAction:CustomActionWithHiddenTarget\t9217\tBinary1\tInvalidEntryPoint\t",
132 "CustomAction:DiscardOptimismAllBeingsWhoProceed\t19\t\tAbandon hope all ye who enter here.\t",
133 "InstallExecuteSequence:CostFinalize\t\t1000",
134 "InstallExecuteSequence:CostInitialize\t\t800",
135 "InstallExecuteSequence:CreateFolders\t\t3700",
136 "InstallExecuteSequence:CustomAction2\t\t801",
137 "InstallExecuteSequence:FileCost\t\t900",
138 "InstallExecuteSequence:FindRelatedProducts\t\t25",
139 "InstallExecuteSequence:InstallFiles\t\t4000",
140 "InstallExecuteSequence:InstallFinalize\t\t6600",
141 "InstallExecuteSequence:InstallInitialize\t\t1500",
142 "InstallExecuteSequence:InstallValidate\t\t1400",
143 "InstallExecuteSequence:LaunchConditions\t\t100",
144 "InstallExecuteSequence:MigrateFeatureStates\t\t1200",
145 "InstallExecuteSequence:ProcessComponents\t\t1600",
146 "InstallExecuteSequence:PublishFeatures\t\t6300",
147 "InstallExecuteSequence:PublishProduct\t\t6400",
148 "InstallExecuteSequence:RegisterProduct\t\t6100",
149 "InstallExecuteSequence:RegisterUser\t\t6000",
150 "InstallExecuteSequence:RemoveExistingProducts\t\t1401",
151 "InstallExecuteSequence:RemoveFiles\t\t3500",
152 "InstallExecuteSequence:RemoveFolders\t\t3600",
153 "InstallExecuteSequence:UnpublishFeatures\t\t1800",
154 "InstallExecuteSequence:ValidateProductID\t\t700",
155 "InstallUISequence:CostFinalize\t\t1000",
156 "InstallUISequence:CostInitialize\t\t800",
157 "InstallUISequence:CustomAction2\t\t801",
158 "InstallUISequence:ExecuteAction\t\t1300",
159 "InstallUISequence:FileCost\t\t900",
160 "InstallUISequence:FindRelatedProducts\t\t25",
161 "InstallUISequence:LaunchConditions\t\t100",
162 "InstallUISequence:MigrateFeatureStates\t\t1200",
163 "InstallUISequence:ValidateProductID\t\t700",
164 "Property:MsiHiddenProperties\tCustomActionWithHiddenTarget",
165 }, results);
166 }
167 }
168 }
169}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System.IO;
6 using System.Xml.Linq;
7 using WixBuildTools.TestSupport;
8 using WixToolset.Core.TestPackage;
9 using Xunit;
10
11 public class CustomTableFixture
12 {
13 [Fact]
14 public void PopulatesCustomTable1()
15 {
16 var folder = TestData.Get(@"TestData");
17
18 using (var fs = new DisposableFileSystem())
19 {
20 var baseFolder = fs.GetFolder();
21 var intermediateFolder = Path.Combine(baseFolder, "obj");
22 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
23
24 var result = WixRunner.Execute(new[]
25 {
26 "build",
27 Path.Combine(folder, "CustomTable", "CustomTable.wxs"),
28 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
29 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
30 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
31 "-intermediateFolder", intermediateFolder,
32 "-o", msiPath
33 });
34
35 result.AssertSuccess();
36
37 Assert.True(File.Exists(msiPath));
38 var results = Query.QueryDatabase(msiPath, new[] { "CustomTable1" });
39 Assert.Equal(new[]
40 {
41 "CustomTable1:Row1\ttest.txt",
42 "CustomTable1:Row2\ttest.txt",
43 }, results);
44 }
45 }
46
47 [Fact]
48 public void PopulatesCustomTableWithLocalization()
49 {
50 var folder = TestData.Get(@"TestData");
51
52 using (var fs = new DisposableFileSystem())
53 {
54 var baseFolder = fs.GetFolder();
55 var intermediateFolder = Path.Combine(baseFolder, "obj");
56 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
57
58 var result = WixRunner.Execute(new[]
59 {
60 "build",
61 Path.Combine(folder, "CustomTable", "LocalizedCustomTable.wxs"),
62 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
63 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
64 "-loc", Path.Combine(folder, "CustomTable", "LocalizedCustomTable.en-us.wxl"),
65 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
66 "-intermediateFolder", intermediateFolder,
67 "-o", msiPath
68 });
69
70 result.AssertSuccess();
71
72 Assert.True(File.Exists(msiPath));
73 var results = Query.QueryDatabase(msiPath, new[] { "CustomTableLocalized" });
74 Assert.Equal(new[]
75 {
76 "CustomTableLocalized:Row1\tThis is row one",
77 "CustomTableLocalized:Row2\tThis is row two",
78 }, results);
79 }
80 }
81
82 [Fact]
83 public void PopulatesCustomTableWithFilePath()
84 {
85 var folder = TestData.Get(@"TestData");
86
87 using (var fs = new DisposableFileSystem())
88 {
89 var baseFolder = fs.GetFolder();
90 var intermediateFolder = Path.Combine(baseFolder, "obj");
91 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
92
93 var result = WixRunner.Execute(new[]
94 {
95 "build",
96 Path.Combine(folder, "CustomTable", "CustomTableWithFile.wxs"),
97 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
98 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
99 "-bindpath", Path.Combine(folder, "CustomTable", "data"),
100 "-intermediateFolder", intermediateFolder,
101 "-o", msiPath
102 });
103
104 result.AssertSuccess();
105
106 Assert.True(File.Exists(msiPath));
107 var results = Query.QueryDatabase(msiPath, new[] { "CustomTableWithFile" });
108 Assert.Equal(new[]
109 {
110 "CustomTableWithFile:Row1\t[Binary data]",
111 "CustomTableWithFile:Row2\t[Binary data]",
112 }, results);
113 }
114 }
115
116 [Fact]
117 public void PopulatesCustomTableWithFilePathSerialized()
118 {
119 var folder = TestData.Get(@"TestData");
120
121 using (var fs = new DisposableFileSystem())
122 {
123 var baseFolder = fs.GetFolder();
124 var intermediateFolder = Path.Combine(baseFolder, "obj");
125 var wixlibPath = Path.Combine(baseFolder, @"bin\test.wixlib");
126 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
127
128 var result = WixRunner.Execute(new[]
129 {
130 "build",
131 Path.Combine(folder, "CustomTable", "CustomTableWithFile.wxs"),
132 "-bindpath", Path.Combine(folder, "CustomTable", "data"),
133 "-intermediateFolder", intermediateFolder,
134 "-o", wixlibPath
135 });
136
137 result.AssertSuccess();
138
139 result = WixRunner.Execute(new[]
140 {
141 "build",
142 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
143 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
144 "-lib", wixlibPath,
145 "-bindpath", Path.Combine(folder, "CustomTable", "data"),
146 "-intermediateFolder", intermediateFolder,
147 "-o", msiPath
148 });
149
150 result.AssertSuccess();
151
152 Assert.True(File.Exists(msiPath));
153 var results = Query.QueryDatabase(msiPath, new[] { "CustomTableWithFile" });
154 Assert.Equal(new[]
155 {
156 "CustomTableWithFile:Row1\t[Binary data]",
157 "CustomTableWithFile:Row2\t[Binary data]",
158 }, results);
159 }
160 }
161
162 [Fact]
163 public void UnrealCustomTableIsNotPresentInMsi()
164 {
165 var folder = TestData.Get(@"TestData");
166
167 using (var fs = new DisposableFileSystem())
168 {
169 var baseFolder = fs.GetFolder();
170 var intermediateFolder = Path.Combine(baseFolder, "obj");
171 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
172
173 var result = WixRunner.Execute(new[]
174 {
175 "build",
176 Path.Combine(folder, "CustomTable", "CustomTable.wxs"),
177 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
178 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
179 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
180 "-intermediateFolder", intermediateFolder,
181 "-o", msiPath
182 });
183
184 result.AssertSuccess();
185
186 Assert.True(File.Exists(msiPath));
187 var results = Query.QueryDatabase(msiPath, new[] { "CustomTable2" });
188 Assert.Empty(results);
189 }
190 }
191
192 [Fact]
193 public void CanCompileAndDecompile()
194 {
195 var folder = TestData.Get(@"TestData");
196 var expectedFile = Path.Combine(folder, "CustomTable", "CustomTable-Expected.wxs");
197
198 using (var fs = new DisposableFileSystem())
199 {
200 var baseFolder = fs.GetFolder();
201 var intermediateFolder = Path.Combine(baseFolder, "obj");
202 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
203 var decompiledWxsPath = Path.Combine(baseFolder, @"decompiled.wxs");
204
205 var result = WixRunner.Execute(new[]
206 {
207 "build",
208 "-d", "ProductCode=83f9c623-26fe-42ab-951e-170022117f54",
209 Path.Combine(folder, "CustomTable", "CustomTable.wxs"),
210 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
211 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
212 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
213 "-intermediateFolder", intermediateFolder,
214 "-o", msiPath
215 });
216
217 result.AssertSuccess();
218 Assert.True(File.Exists(msiPath));
219
220 result = WixRunner.Execute(new[]
221 {
222 "decompile", msiPath,
223 "-sw1060",
224 "-intermediateFolder", intermediateFolder,
225 "-o", decompiledWxsPath
226 });
227
228 result.AssertSuccess();
229
230 WixAssert.CompareXml(expectedFile, decompiledWxsPath);
231 }
232 }
233 }
234}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System.IO;
6 using WixBuildTools.TestSupport;
7 using WixToolset.Core.TestPackage;
8 using Xunit;
9
10 public class DecompileFixture
11 {
12 private static void DecompileAndCompare(string sourceFolder, string msiName, string expectedWxsName)
13 {
14 var folder = TestData.Get(sourceFolder);
15
16 using (var fs = new DisposableFileSystem())
17 {
18 var intermediateFolder = fs.GetFolder();
19 var outputPath = Path.Combine(intermediateFolder, @"Actual.wxs");
20
21 var result = WixRunner.Execute(new[]
22 {
23 "decompile",
24 Path.Combine(folder, msiName),
25 "-intermediateFolder", intermediateFolder,
26 "-o", outputPath
27 });
28
29 result.AssertSuccess();
30
31 WixAssert.CompareXml(Path.Combine(folder, expectedWxsName), outputPath);
32 }
33 }
34
35 [Fact]
36 public void CanDecompileSingleFileCompressed()
37 {
38 DecompileAndCompare(@"TestData\DecompileSingleFileCompressed", "example.msi", "Expected.wxs");
39 }
40
41 [Fact]
42 public void CanDecompile64BitSingleFileCompressed()
43 {
44 DecompileAndCompare(@"TestData\DecompileSingleFileCompressed64", "example.msi", "Expected.wxs");
45 }
46
47 [Fact]
48 public void CanDecompileNestedDirSearchUnderRegSearch()
49 {
50 DecompileAndCompare(@"TestData\AppSearch", "NestedDirSearchUnderRegSearch.msi", "DecompiledNestedDirSearchUnderRegSearch.wxs");
51 }
52
53 [Fact]
54 public void CanDecompileOldClassTableDefinition()
55 {
56 // The input MSI was not created using standard methods, it is an example of a real world database that needs to be decompiled.
57 // The Class/@Feature_ column has length of 32, the File/@Attributes has length of 2,
58 // and numerous foreign key relationships are missing.
59 DecompileAndCompare(@"TestData\Class", "OldClassTableDef.msi", "DecompiledOldClassTableDef.wxs");
60 }
61
62 [Fact]
63 public void CanDecompileSequenceTables()
64 {
65 DecompileAndCompare(@"TestData\SequenceTables", "SequenceTables.msi", "DecompiledSequenceTables.wxs");
66 }
67
68 [Fact]
69 public void CanDecompileShortcuts()
70 {
71 DecompileAndCompare(@"TestData\Shortcut", "shortcuts.msi", "DecompiledShortcuts.wxs");
72 }
73
74 [Fact]
75 public void CanDecompileNullComponent()
76 {
77 DecompileAndCompare(@"TestData\DecompileNullComponent", "example.msi", "Expected.wxs");
78 }
79
80 [Fact]
81 public void CanDecompileMergeModuleWithTargetDirComponent()
82 {
83 DecompileAndCompare(@"TestData\DecompileTargetDirMergeModule", "MergeModule1.msm", "Expected.wxs");
84 }
85 }
86}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System.Collections.Generic;
6 using System.IO;
7 using System.Linq;
8 using System.Xml;
9 using WixBuildTools.TestSupport;
10 using WixToolset.Core.TestPackage;
11 using Xunit;
12
13 public class DependencyExtensionFixture
14 {
15 [Fact]
16 public void CanBuildBundleUsingExePackageWithProvides()
17 {
18 var folder = TestData.Get(@"TestData");
19
20 using (var fs = new DisposableFileSystem())
21 {
22 var baseFolder = fs.GetFolder();
23 var intermediateFolder = Path.Combine(baseFolder, "obj");
24 var binFolder = Path.Combine(baseFolder, "bin");
25 var bundlePath = Path.Combine(binFolder, "test.exe");
26 var baFolderPath = Path.Combine(baseFolder, "ba");
27 var extractFolderPath = Path.Combine(baseFolder, "extract");
28
29 var result = WixRunner.Execute(new[]
30 {
31 "build",
32 Path.Combine(folder, "Dependency", "ExePackageProvidesBundle.wxs"),
33 Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"),
34 "-bindpath", Path.Combine(folder, ".Data"),
35 "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
36 "-intermediateFolder", intermediateFolder,
37 "-o", bundlePath,
38 });
39
40 result.AssertSuccess();
41
42 Assert.True(File.Exists(bundlePath));
43
44 var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath);
45 extractResult.AssertSuccess();
46
47 var provides = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Chain/burn:ExePackage/burn:Provides")
48 .Cast<XmlElement>()
49 .Select(e => e.GetTestXml())
50 .ToArray();
51 WixAssert.CompareLineByLine(new string[]
52 {
53 "<Provides Key='DependencyTests_ExeA,v1.0' Version='1.0.0.0' DisplayName='Windows Installer XML Toolset' />",
54 }, provides);
55 }
56 }
57
58 [Fact]
59 public void CanBuildBundleUsingMsiWithProvides()
60 {
61 var folder = TestData.Get(@"TestData");
62
63 using (var fs = new DisposableFileSystem())
64 {
65 var baseFolder = fs.GetFolder();
66 var intermediateFolder = Path.Combine(baseFolder, "obj");
67 var binFolder = Path.Combine(baseFolder, "bin");
68 var bundlePath = Path.Combine(binFolder, "test.exe");
69 var baFolderPath = Path.Combine(baseFolder, "ba");
70 var extractFolderPath = Path.Combine(baseFolder, "extract");
71
72 var result = WixRunner.Execute(new[]
73 {
74 "build",
75 Path.Combine(folder, "UsingProvides", "Package.wxs"),
76 Path.Combine(folder, "UsingProvides", "PackageComponents.wxs"),
77 "-loc", Path.Combine(folder, "UsingProvides", "Package.en-us.wxl"),
78 "-bindpath", Path.Combine(folder, "UsingProvides"),
79 "-intermediateFolder", intermediateFolder,
80 "-o", Path.Combine(binFolder, "UsingProvides.msi"),
81 });
82
83 result.AssertSuccess();
84
85 result = WixRunner.Execute(new[]
86 {
87 "build",
88 Path.Combine(folder, "Dependency", "UsingProvidesBundle.wxs"),
89 Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"),
90 "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
91 "-bindpath", binFolder,
92 "-intermediateFolder", intermediateFolder,
93 "-o", bundlePath,
94 });
95
96 result.AssertSuccess();
97
98 Assert.True(File.Exists(bundlePath));
99
100 var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath);
101 extractResult.AssertSuccess();
102
103 var provides = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Chain/burn:MsiPackage/burn:Provides")
104 .Cast<XmlElement>()
105 .Select(e => e.GetTestXml())
106 .ToArray();
107 WixAssert.CompareLineByLine(new string[]
108 {
109 "<Provides Key='UsingProvides' Version='1.0.0.0' DisplayName='MsiPackage' Imported='yes' />",
110 "<Provides Key='{A81D50F9-B696-4F3D-ABE0-E64D61590E5F}' Version='1.0.0.0' DisplayName='MsiPackage' />",
111 }, provides);
112 }
113 }
114
115 [Fact]
116 public void CanBuildBundleWithCustomProviderKey()
117 {
118 var folder = TestData.Get(@"TestData");
119
120 using (var fs = new DisposableFileSystem())
121 {
122 var baseFolder = fs.GetFolder();
123 var intermediateFolder = Path.Combine(baseFolder, "obj");
124 var binFolder = Path.Combine(baseFolder, "bin");
125 var bundlePath = Path.Combine(binFolder, "test.exe");
126 var baFolderPath = Path.Combine(baseFolder, "ba");
127 var extractFolderPath = Path.Combine(baseFolder, "extract");
128
129 var result = WixRunner.Execute(new[]
130 {
131 "build",
132 Path.Combine(folder, "Dependency", "CustomProviderKeyBundle.wxs"),
133 Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"),
134 "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
135 "-intermediateFolder", intermediateFolder,
136 "-o", bundlePath,
137 });
138
139 result.AssertSuccess();
140
141 Assert.True(File.Exists(bundlePath));
142
143 var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath);
144 extractResult.AssertSuccess();
145
146 var ignoreAttributesByElementName = new Dictionary<string, List<string>>
147 {
148 { "Registration", new List<string> { "Id" } },
149 };
150 var registration = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Registration")
151 .Cast<XmlElement>()
152 .Select(e => e.GetTestXml(ignoreAttributesByElementName))
153 .ToArray();
154 WixAssert.CompareLineByLine(new string[]
155 {
156 "<Registration Id='*' ExecutableName='test.exe' PerMachine='yes' Tag='' Version='1.0.0.0' ProviderKey='MyProviderKey,v1.0'><Arp Register='yes' DisplayName='BurnBundle' DisplayVersion='1.0.0.0' Publisher='Example Corporation' /></Registration>",
157 }, registration);
158 }
159 }
160
161 [Fact]
162 public void CanBuildPackageUsingProvides()
163 {
164 var folder = TestData.Get(@"TestData\UsingProvides");
165 var build = new Builder(folder, null, new[] { folder });
166
167 var results = build.BuildAndQuery(Build, "WixDependencyProvider");
168 Assert.Equal(new[]
169 {
170 "WixDependencyProvider:dep74OfIcniaqxA7EprRGBw4Oyy3r8\tfilF5_pLhBuF5b4N9XEo52g_hUM5Lo\tUsingProvides\t\t\t",
171 }, results);
172 }
173
174 private static void Build(string[] args)
175 {
176 var result = WixRunner.Execute(args)
177 .AssertSuccess();
178 }
179 }
180}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System;
6 using System.IO;
7 using System.Linq;
8 using WixBuildTools.TestSupport;
9 using WixToolset.Core.TestPackage;
10 using WixToolset.Data;
11 using WixToolset.Data.WindowsInstaller;
12 using Xunit;
13
14 public class DirectoryFixture
15 {
16 [Fact]
17 public void CanGet32bitProgramFiles6432Folder()
18 {
19 var folder = TestData.Get(@"TestData");
20
21 using (var fs = new DisposableFileSystem())
22 {
23 var baseFolder = fs.GetFolder();
24 var intermediateFolder = Path.Combine(baseFolder, "obj");
25 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
26
27 var result = WixRunner.Execute(new[]
28 {
29 "build",
30 Path.Combine(folder, "Directory", "Empty.wxs"),
31 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
32 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
33 "-intermediateFolder", intermediateFolder,
34 "-o", msiPath
35 });
36
37 result.AssertSuccess();
38
39 var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb"));
40 var section = intermediate.Sections.Single();
41
42 var dirSymbols = section.Symbols.OfType<WixToolset.Data.Symbols.DirectorySymbol>().ToList();
43 Assert.Equal(new[]
44 {
45 "INSTALLFOLDER:ProgramFiles6432Folder:MsiPackage",
46 "ProgramFiles6432Folder:ProgramFilesFolder:.",
47 "ProgramFilesFolder:TARGETDIR:PFiles",
48 "TARGETDIR::SourceDir"
49 }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => d.Id.Id + ":" + d.ParentDirectoryRef + ":" + d.Name).ToArray());
50 }
51 }
52
53 [Fact]
54 public void CanGet64bitProgramFiles6432Folder()
55 {
56 var folder = TestData.Get(@"TestData");
57
58 using (var fs = new DisposableFileSystem())
59 {
60 var baseFolder = fs.GetFolder();
61 var intermediateFolder = Path.Combine(baseFolder, "obj");
62 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
63
64 var result = WixRunner.Execute(new[]
65 {
66 "build",
67 "-arch", "x64",
68 Path.Combine(folder, "Directory", "Empty.wxs"),
69 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
70 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
71 "-intermediateFolder", intermediateFolder,
72 "-o", msiPath
73 });
74
75 result.AssertSuccess();
76
77 var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb"));
78 var section = intermediate.Sections.Single();
79
80 var dirSymbols = section.Symbols.OfType<WixToolset.Data.Symbols.DirectorySymbol>().ToList();
81 Assert.Equal(new[]
82 {
83 "INSTALLFOLDER:ProgramFiles6432Folder:MsiPackage",
84 "ProgramFiles6432Folder:ProgramFiles64Folder:.",
85 "ProgramFiles64Folder:TARGETDIR:PFiles64",
86 "TARGETDIR::SourceDir"
87 }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => d.Id.Id + ":" + d.ParentDirectoryRef + ":" + d.Name).ToArray());
88 }
89 }
90
91 [Fact]
92 public void CanGetDefaultName()
93 {
94 var folder = TestData.Get(@"TestData");
95
96 using (var fs = new DisposableFileSystem())
97 {
98 var baseFolder = fs.GetFolder();
99 var intermediateFolder = Path.Combine(baseFolder, "obj");
100 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
101
102 var result = WixRunner.Execute(new[]
103 {
104 "build",
105 Path.Combine(folder, "Directory", "DefaultName.wxs"),
106 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
107 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
108 "-intermediateFolder", intermediateFolder,
109 "-o", msiPath
110 });
111
112 result.AssertSuccess();
113
114 var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb"));
115 var section = intermediate.Sections.Single();
116
117 var dirSymbols = section.Symbols.OfType<WixToolset.Data.Symbols.DirectorySymbol>().ToList();
118 WixAssert.CompareLineByLine(new[]
119 {
120 "BinFolder\tCompanyFolder\t.",
121 "CompanyFolder\tProgramFilesFolder\tExample Corporation",
122 "ProgramFilesFolder\tTARGETDIR\tPFiles",
123 "TARGETDIR\t\tSourceDir"
124 }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => String.Join('\t', d.Id.Id, d.ParentDirectoryRef, d.Name)).ToArray());
125
126 var data = WindowsInstallerData.Load(Path.Combine(baseFolder, @"bin\test.wixpdb"));
127 var directoryRows = data.Tables["Directory"].Rows;
128 WixAssert.CompareLineByLine(new[]
129 {
130 "BinFolder\tCompanyFolder\t.",
131 "CompanyFolder\tProgramFilesFolder\tu7-b4gch|Example Corporation",
132 "ProgramFilesFolder\tTARGETDIR\tPFiles",
133 "TARGETDIR\t\tSourceDir"
134 }, directoryRows.Select(r => String.Join('\t', r.FieldAsString(0), r.FieldAsString(1), r.FieldAsString(2))).ToArray());
135 }
136 }
137
138 [Fact]
139 public void CanGetDuplicateDir()
140 {
141 var folder = TestData.Get(@"TestData");
142
143 using (var fs = new DisposableFileSystem())
144 {
145 var baseFolder = fs.GetFolder();
146 var intermediateFolder = Path.Combine(baseFolder, "obj");
147 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
148
149 var result = WixRunner.Execute(new[]
150 {
151 "build",
152 "-arch", "x64",
153 Path.Combine(folder, "DuplicateDir", "DuplicateDir.wxs"),
154 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
155 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
156 "-intermediateFolder", intermediateFolder,
157 "-o", msiPath
158 });
159
160 result.AssertSuccess();
161
162 var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb"));
163 var section = intermediate.Sections.Single();
164
165 var dirSymbols = section.Symbols.OfType<WixToolset.Data.Symbols.DirectorySymbol>().ToList();
166 Assert.Equal(new[]
167 {
168 "dZsSsu81KcG46xXTwc4mTSZO5Zx4:INSTALLFOLDER:dupe",
169 "INSTALLFOLDER:ProgramFiles6432Folder:MsiPackage",
170 "ProgramFiles6432Folder:ProgramFiles64Folder:.",
171 "ProgramFiles64Folder:TARGETDIR:PFiles64",
172 "TARGETDIR::SourceDir"
173 }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => d.Id.Id + ":" + d.ParentDirectoryRef + ":" + d.Name).ToArray());
174 }
175 }
176
177 [Fact]
178 public void CanGetWithMultiNestedSubdirectory()
179 {
180 var folder = TestData.Get(@"TestData");
181
182 using (var fs = new DisposableFileSystem())
183 {
184 var baseFolder = fs.GetFolder();
185 var intermediateFolder = Path.Combine(baseFolder, "obj");
186 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
187
188 var result = WixRunner.Execute(new[]
189 {
190 "build",
191 "-arch", "x64",
192 Path.Combine(folder, "Directory", "Nested.wxs"),
193 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
194 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
195 "-intermediateFolder", intermediateFolder,
196 "-o", msiPath
197 });
198
199 result.AssertSuccess();
200
201 var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb"));
202 var section = intermediate.Sections.Single();
203
204 var dirSymbols = section.Symbols.OfType<WixToolset.Data.Symbols.DirectorySymbol>().ToList();
205 Assert.Equal(new[]
206 {
207 "BinFolder:ProgramFilesFolder:Example Corporation\\Test Product\\bin",
208 "ProgramFilesFolder:TARGETDIR:PFiles",
209 "TARGETDIR::SourceDir"
210 }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => d.Id.Id + ":" + d.ParentDirectoryRef + ":" + d.Name).ToArray());
211
212 var data = WindowsInstallerData.Load(Path.Combine(baseFolder, @"bin\test.wixpdb"));
213 var directoryRows = data.Tables["Directory"].Rows;
214 Assert.Equal(new[]
215 {
216 "d4EceYatXTyy8HXPt5B6DT9Rj.wE:ProgramFilesFolder:u7-b4gch|Example Corporation",
217 "dSJ1pgiASlW7kJTu0wqsGBklJsS0:d4EceYatXTyy8HXPt5B6DT9Rj.wE:vjj-gxay|Test Product",
218 "BinFolder:dSJ1pgiASlW7kJTu0wqsGBklJsS0:bin",
219 "ProgramFilesFolder:TARGETDIR:PFiles",
220 "TARGETDIR::SourceDir"
221 }, directoryRows.Select(r => r.FieldAsString(0) + ":" + r.FieldAsString(1) + ":" + r.FieldAsString(2)).ToArray());
222 }
223 }
224
225 [Fact]
226 public void CanGetDuplicateTargetSourceName()
227 {
228 var folder = TestData.Get(@"TestData");
229
230 using (var fs = new DisposableFileSystem())
231 {
232 var baseFolder = fs.GetFolder();
233 var intermediateFolder = Path.Combine(baseFolder, "obj");
234 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
235
236 var result = WixRunner.Execute(new[]
237 {
238 "build",
239 "-arch", "x64",
240 Path.Combine(folder, "Directory", "DuplicateTargetSourceName.wxs"),
241 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
242 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
243 "-intermediateFolder", intermediateFolder,
244 "-o", msiPath
245 });
246
247 result.AssertSuccess();
248
249 var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb"));
250 var section = intermediate.Sections.Single();
251
252 var dirSymbols = section.Symbols.OfType<WixToolset.Data.Symbols.DirectorySymbol>().ToList();
253 Assert.Equal(new[]
254 {
255 "BinFolder\tProgramFilesFolder\tbin",
256 "ProgramFilesFolder\tTARGETDIR\tPFiles",
257 "TARGETDIR\t\tSourceDir"
258 }, dirSymbols.OrderBy(d => d.Id.Id).Select(d => String.Join('\t', d.Id.Id, d.ParentDirectoryRef, d.Name)).ToArray());
259
260 var data = WindowsInstallerData.Load(Path.Combine(baseFolder, @"bin\test.wixpdb"));
261 var directoryRows = data.Tables["Directory"].Rows;
262 Assert.Equal(new[]
263 {
264 "BinFolder\tProgramFilesFolder\tbin",
265 "ProgramFilesFolder\tTARGETDIR\tPFiles",
266 "TARGETDIR\t\tSourceDir"
267 }, directoryRows.Select(r => String.Join('\t', r.FieldAsString(0), r.FieldAsString(1), r.FieldAsString(2))).ToArray());
268 }
269 }
270 }
271}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System.IO;
6 using WixBuildTools.TestSupport;
7 using WixToolset.Core.TestPackage;
8 using Xunit;
9
10 public class ExePackageFixture
11 {
12 [Fact]
13 public void ErrorWhenMissingDetectCondition()
14 {
15 var folder = TestData.Get(@"TestData", "ExePackage");
16
17 using (var fs = new DisposableFileSystem())
18 {
19 var baseFolder = fs.GetFolder();
20
21 var result = WixRunner.Execute(new[]
22 {
23 "build",
24 Path.Combine(folder, "MissingDetectCondition.wxs"),
25 "-o", Path.Combine(baseFolder, "test.wixlib")
26 });
27
28 Assert.Equal(1153, result.ExitCode);
29 }
30 }
31
32 [Fact]
33 public void ErrorWhenRequireDetectCondition()
34 {
35 var folder = TestData.Get(@"TestData", "ExePackage");
36
37 using (var fs = new DisposableFileSystem())
38 {
39 var baseFolder = fs.GetFolder();
40
41 var result = WixRunner.Execute(new[]
42 {
43 "build",
44 Path.Combine(folder, "RequireDetectCondition.wxs"),
45 "-o", Path.Combine(baseFolder, "test.wixlib")
46 });
47
48 Assert.Equal(401, result.ExitCode);
49 }
50 }
51 }
52}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System;
6 using System.IO;
7 using System.Linq;
8 using Example.Extension;
9 using WixBuildTools.TestSupport;
10 using WixToolset.Core.TestPackage;
11 using WixToolset.Data;
12 using WixToolset.Data.Symbols;
13 using Xunit;
14
15 public class ExtensionFixture
16 {
17 [Fact]
18 public void CanBuildAndQuery()
19 {
20 var folder = TestData.Get(@"TestData\ExampleExtension");
21 var build = new Builder(folder, typeof(ExampleExtensionFactory), new[] { Path.Combine(folder, "data") });
22
23 var results = build.BuildAndQuery(Build, "Wix4Example");
24 Assert.Equal(new[]
25 {
26 "Wix4Example:Foo\tBar"
27 }, results);
28 }
29
30 [Fact]
31 public void CanBuildWithExampleExtension()
32 {
33 var folder = TestData.Get(@"TestData\ExampleExtension");
34 var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath);
35
36 using (var fs = new DisposableFileSystem())
37 {
38 var intermediateFolder = fs.GetFolder();
39
40 var result = WixRunner.Execute(new[]
41 {
42 "build",
43 Path.Combine(folder, "Package.wxs"),
44 Path.Combine(folder, "PackageComponents.wxs"),
45 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
46 "-ext", extensionPath,
47 "-bindpath", Path.Combine(folder, "data"),
48 "-intermediateFolder", intermediateFolder,
49 "-o", Path.Combine(intermediateFolder, @"bin\extest.msi")
50 });
51
52 result.AssertSuccess();
53
54 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\extest.msi")));
55 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\extest.wixpdb")));
56 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\PFiles\MsiPackage\example.txt")));
57
58 var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\extest.wixpdb"));
59 var section = intermediate.Sections.Single();
60
61 var fileSymbol = section.Symbols.OfType<FileSymbol>().Single();
62 Assert.Equal(Path.Combine(folder, @"data\example.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path);
63 Assert.Equal(@"example.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path);
64
65 var example = section.Symbols.Where(t => t.Definition.Type == SymbolDefinitionType.MustBeFromAnExtension).Single();
66 Assert.Equal("Foo", example.Id?.Id);
67 Assert.Equal("Bar", example[0].AsString());
68 }
69 }
70
71 [Fact]
72 public void CanParseCommandLineWithExtension()
73 {
74 var folder = TestData.Get(@"TestData\ExampleExtension");
75 var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath);
76
77 using (var fs = new DisposableFileSystem())
78 {
79 var intermediateFolder = fs.GetFolder();
80
81 var result = WixRunner.Execute(new[]
82 {
83 "build",
84 Path.Combine(folder, "Package.wxs"),
85 Path.Combine(folder, "PackageComponents.wxs"),
86 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
87 "-ext", extensionPath,
88 "-bindpath", Path.Combine(folder, "data"),
89 "-intermediateFolder", intermediateFolder,
90 "-example", "test",
91 "-o", Path.Combine(intermediateFolder, @"bin\extest.msi")
92 });
93
94 result.AssertSuccess();
95
96 var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\extest.wixpdb"));
97 var section = intermediate.Sections.Single();
98
99 var property = section.Symbols.OfType<PropertySymbol>().Where(p => p.Id.Id == "ExampleProperty").Single();
100 Assert.Equal("ExampleProperty", property.Id.Id);
101 Assert.Equal("test", property.Value);
102 }
103 }
104
105 [Fact]
106 public void CannotBuildWithMissingExtension()
107 {
108 var folder = TestData.Get(@"TestData\ExampleExtension");
109
110 using (var fs = new DisposableFileSystem())
111 {
112 var intermediateFolder = fs.GetFolder();
113
114 var exception = Assert.Throws<WixException>(() =>
115 WixRunner.Execute(new[]
116 {
117 "build",
118 Path.Combine(folder, "Package.wxs"),
119 "-ext", "ExampleExtension.DoesNotExist"
120 }));
121
122 Assert.StartsWith("The extension 'ExampleExtension.DoesNotExist' could not be found. Checked paths: ", exception.Message);
123 }
124 }
125
126 [Fact]
127 public void CannotBuildWithMissingVersionedExtension()
128 {
129 var folder = TestData.Get(@"TestData\ExampleExtension");
130
131 using (var fs = new DisposableFileSystem())
132 {
133 var intermediateFolder = fs.GetFolder();
134
135 var exception = Assert.Throws<WixException>(() =>
136 WixRunner.Execute(new[]
137 {
138 "build",
139 Path.Combine(folder, "Package.wxs"),
140 "-ext", "ExampleExtension.DoesNotExist/1.0.0"
141 }));
142
143 Assert.StartsWith("The extension 'ExampleExtension.DoesNotExist/1.0.0' could not be found. Checked paths: ", exception.Message);
144 }
145 }
146
147 private static void Build(string[] args)
148 {
149 var result = WixRunner.Execute(args)
150 .AssertSuccess();
151 }
152 }
153}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System.IO;
6 using System.Linq;
7 using WixBuildTools.TestSupport;
8 using WixToolset.Core.TestPackage;
9 using WixToolset.Data;
10 using WixToolset.Data.Symbols;
11 using WixToolset.Data.WindowsInstaller;
12 using Xunit;
13
14 public class LanguageFixture
15 {
16 [Fact]
17 public void CanBuildWithDefaultProductLanguage()
18 {
19 var folder = TestData.Get(@"TestData", "Language");
20
21 using (var fs = new DisposableFileSystem())
22 {
23 var baseFolder = fs.GetFolder();
24
25 var result = WixRunner.Execute(new[]
26 {
27 "build",
28 Path.Combine(folder, "Package.wxs"),
29 "-loc", Path.Combine(folder, "Package.wxl"),
30 "-bindpath", Path.Combine(folder, "data"),
31 "-intermediateFolder", Path.Combine(baseFolder, "obj"),
32 "-o", Path.Combine(baseFolder, @"bin\test.msi")
33 });
34
35 result.AssertSuccess();
36
37 var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb"));
38 var section = intermediate.Sections.Single();
39
40 var directorySymbols = section.Symbols.OfType<DirectorySymbol>();
41 WixAssert.CompareLineByLine(new[]
42 {
43 "INSTALLFOLDER:Example Corporation\\MsiPackage",
44 "ProgramFilesFolder:PFiles",
45 "TARGETDIR:SourceDir"
46 }, directorySymbols.OrderBy(s => s.Id.Id).Select(s => s.Id.Id + ":" + s.Name).ToArray());
47
48 var propertySymbol = section.Symbols.OfType<PropertySymbol>().Single(p => p.Id.Id == "ProductLanguage");
49 Assert.Equal("0", propertySymbol.Value);
50
51 var summaryPlatform = section.Symbols.OfType<SummaryInformationSymbol>().Single(s => s.PropertyId == SummaryInformationType.PlatformAndLanguage);
52 Assert.Equal("Intel;0", summaryPlatform.Value);
53
54 var summaryCodepage = section.Symbols.OfType<SummaryInformationSymbol>().Single(s => s.PropertyId == SummaryInformationType.Codepage);
55 Assert.Equal("1252", summaryCodepage.Value);
56
57 var data = WindowsInstallerData.Load(Path.Combine(baseFolder, @"bin\test.wixpdb"));
58 var directoryRows = data.Tables["Directory"].Rows;
59 WixAssert.CompareLineByLine(new[]
60 {
61 "d4EceYatXTyy8HXPt5B6DT9Rj.wE:u7-b4gch|Example Corporation",
62 "INSTALLFOLDER:oekcr5lq|MsiPackage",
63 "ProgramFilesFolder:PFiles",
64 "TARGETDIR:SourceDir"
65 }, directoryRows.Select(r => r.FieldAsString(0) + ":" + r.FieldAsString(2)).ToArray());
66 }
67 }
68
69 [Fact]
70 public void CanBuildEnuWxl()
71 {
72 var folder = TestData.Get(@"TestData", "Language");
73
74 using (var fs = new DisposableFileSystem())
75 {
76 var baseFolder = fs.GetFolder();
77
78 var result = WixRunner.Execute(new[]
79 {
80 "build",
81 Path.Combine(folder, "Package.wxs"),
82 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
83 "-bindpath", Path.Combine(folder, "data"),
84 "-intermediateFolder", Path.Combine(baseFolder, "obj"),
85 "-o", Path.Combine(baseFolder, @"bin\test.msi")
86 });
87
88 result.AssertSuccess();
89
90 var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb"));
91 var section = intermediate.Sections.Single();
92
93 var propertySymbol = section.Symbols.OfType<PropertySymbol>().Single(p => p.Id.Id == "ProductLanguage");
94 Assert.Equal("1033", propertySymbol.Value);
95
96 var summaryPlatform = section.Symbols.OfType<SummaryInformationSymbol>().Single(s => s.PropertyId == SummaryInformationType.PlatformAndLanguage);
97 Assert.Equal("Intel;1033", summaryPlatform.Value);
98
99 var summaryCodepage = section.Symbols.OfType<SummaryInformationSymbol>().Single(s => s.PropertyId == SummaryInformationType.Codepage);
100 Assert.Equal("1252", summaryCodepage.Value);
101 }
102 }
103
104 [Fact]
105 public void CanBuildJpnWxl()
106 {
107 var folder = TestData.Get(@"TestData", "Language");
108
109 using (var fs = new DisposableFileSystem())
110 {
111 var baseFolder = fs.GetFolder();
112
113 var result = WixRunner.Execute(new[]
114 {
115 "build",
116 Path.Combine(folder, "Package.wxs"),
117 "-loc", Path.Combine(folder, "Package.ja-jp.wxl"),
118 "-bindpath", Path.Combine(folder, "data"),
119 "-intermediateFolder", Path.Combine(baseFolder, "obj"),
120 "-o", Path.Combine(baseFolder, @"bin\test.msi")
121 });
122
123 result.AssertSuccess();
124
125 var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb"));
126 var section = intermediate.Sections.Single();
127
128 var propertySymbol = section.Symbols.OfType<PropertySymbol>().Single(p => p.Id.Id == "ProductLanguage");
129 Assert.Equal("1041", propertySymbol.Value);
130
131 var summaryPlatform = section.Symbols.OfType<SummaryInformationSymbol>().Single(s => s.PropertyId == SummaryInformationType.PlatformAndLanguage);
132 Assert.Equal("Intel;1041", summaryPlatform.Value);
133
134 var summaryCodepage = section.Symbols.OfType<SummaryInformationSymbol>().Single(s => s.PropertyId == SummaryInformationType.Codepage);
135 Assert.Equal("932", summaryCodepage.Value);
136 }
137 }
138
139 [Fact]
140 public void CanBuildJpnWxlWithEnuSummaryInfo()
141 {
142 var folder = TestData.Get(@"TestData", "Language");
143
144 using (var fs = new DisposableFileSystem())
145 {
146 var baseFolder = fs.GetFolder();
147
148 var result = WixRunner.Execute(new[]
149 {
150 "build",
151 Path.Combine(folder, "Package.wxs"),
152 "-loc", Path.Combine(folder, "PackageWithEnSummaryInfo.ja-jp.wxl"),
153 "-bindpath", Path.Combine(folder, "data"),
154 "-intermediateFolder", Path.Combine(baseFolder, "obj"),
155 "-o", Path.Combine(baseFolder, @"bin\test.msi")
156 });
157
158 result.AssertSuccess();
159
160 var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb"));
161 var section = intermediate.Sections.Single();
162
163 var propertySymbol = section.Symbols.OfType<PropertySymbol>().Single(p => p.Id.Id == "ProductLanguage");
164 Assert.Equal("1041", propertySymbol.Value);
165
166 var summaryPlatform = section.Symbols.OfType<SummaryInformationSymbol>().Single(s => s.PropertyId == SummaryInformationType.PlatformAndLanguage);
167 Assert.Equal("Intel;1041", summaryPlatform.Value);
168
169 var summaryCodepage = section.Symbols.OfType<SummaryInformationSymbol>().Single(s => s.PropertyId == SummaryInformationType.Codepage);
170 Assert.Equal("1252", summaryCodepage.Value);
171 }
172 }
173 }
174}
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 @@
1
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4namespace WixToolsetTest.CoreIntegration
5{
6 using System;
7 using System.IO;
8 using System.Linq;
9 using WixBuildTools.TestSupport;
10 using WixToolset.Core;
11 using WixToolset.Core.TestPackage;
12 using WixToolset.Data;
13 using WixToolset.Data.Symbols;
14 using WixToolset.Extensibility.Data;
15 using WixToolset.Extensibility.Services;
16 using Xunit;
17
18 public class LinkerFixture
19 {
20 [Fact]
21 public void MustCompileBeforeLinking()
22 {
23 var intermediate1 = new Intermediate("TestIntermediate1", new[] { new IntermediateSection("test1", SectionType.Product) }, null);
24 var intermediate2 = new Intermediate("TestIntermediate2", new[] { new IntermediateSection("test2", SectionType.Fragment) }, null);
25 var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider();
26
27 var listener = new TestMessageListener();
28 var messaging = serviceProvider.GetService<IMessaging>();
29 messaging.SetListener(listener);
30
31 var creator = serviceProvider.GetService<ISymbolDefinitionCreator>();
32 var context = serviceProvider.GetService<ILinkContext>();
33 context.Extensions = Array.Empty<WixToolset.Extensibility.ILinkerExtension>();
34 context.ExtensionData = Array.Empty<WixToolset.Extensibility.IExtensionData>();
35 context.Intermediates = new[] { intermediate1, intermediate2 };
36 context.SymbolDefinitionCreator = creator;
37
38 var linker = serviceProvider.GetService<ILinker>();
39 linker.Link(context);
40
41 Assert.Equal((int)ErrorMessages.Ids.IntermediatesMustBeCompiled, messaging.LastErrorNumber);
42 Assert.Single(listener.Messages);
43 Assert.EndsWith("TestIntermediate1, TestIntermediate2", listener.Messages[0].ToString());
44 }
45
46 [Fact]
47 public void CanBuildWithOverridableActions()
48 {
49 var folder = TestData.Get(@"TestData\OverridableActions");
50
51 using (var fs = new DisposableFileSystem())
52 {
53 var baseFolder = fs.GetFolder();
54 var intermediateFolder = Path.Combine(baseFolder, "obj");
55
56 var result = WixRunner.Execute(new[]
57 {
58 "build",
59 "-sw1008", // this is expected for this test
60 Path.Combine(folder, "Package.wxs"),
61 Path.Combine(folder, "PackageComponents.wxs"),
62 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
63 "-bindpath", Path.Combine(folder, "data"),
64 "-intermediateFolder", intermediateFolder,
65 "-o", Path.Combine(baseFolder, @"bin\test.msi")
66 });
67
68 result.AssertSuccess();
69
70 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi")));
71 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb")));
72 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\MsiPackage\test.txt")));
73
74 var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb"));
75 var section = intermediate.Sections.Single();
76
77 var actions = section.Symbols.OfType<WixActionSymbol>().Where(wat => wat.Action.StartsWith("Set")).ToList();
78 Assert.Equal(2, actions.Count);
79 //Assert.Equal(Path.Combine(folder, @"data\test.txt"), wixFile[WixFileSymbolFields.Source].AsPath().Path);
80 //Assert.Equal(@"test.txt", wixFile[WixFileSymbolFields.Source].PreviousValue.AsPath().Path);
81 }
82 }
83
84 [Fact]
85 public void MissingEntrySectionDetectedProduct()
86 {
87 var folder = TestData.Get(@"TestData\OverridableActions");
88
89 using (var fs = new DisposableFileSystem())
90 {
91 var baseFolder = fs.GetFolder();
92 var intermediateFolder = Path.Combine(baseFolder, "obj");
93
94 try
95 {
96 WixRunner.Execute(new[]
97 {
98 "build",
99 Path.Combine(folder, "PackageComponents.wxs"),
100 "-intermediateFolder", intermediateFolder,
101 "-o", Path.Combine(baseFolder, @"bin\test.msi")
102 });
103 }
104 catch (WixException we)
105 {
106 Assert.Equal("Could not find entry section in provided list of intermediates. Expected section of type 'Product'.", we.Message);
107 return;
108 }
109
110 Assert.True(false, "Expected WixException for missing entry section but expectations were not met.");
111 }
112 }
113
114 [Fact]
115 public void MissingEntrySectionDetectedWixipl()
116 {
117 var folder = TestData.Get(@"TestData\OverridableActions");
118
119 using (var fs = new DisposableFileSystem())
120 {
121 var baseFolder = fs.GetFolder();
122 var intermediateFolder = Path.Combine(baseFolder, "obj");
123
124 try
125 {
126 WixRunner.Execute(new[]
127 {
128 "build",
129 Path.Combine(folder, "PackageComponents.wxs"),
130 "-intermediateFolder", intermediateFolder,
131 "-o", Path.Combine(baseFolder, @"bin\test.wixipl")
132 });
133 }
134 catch (WixException we)
135 {
136 Assert.Equal("Could not find entry section in provided list of intermediates. Supported entry section types are: Product, Bundle, Patch, PatchCreation, Module.", we.Message);
137 return;
138 }
139
140 Assert.True(false, "Expected WixException for missing entry section but expectations were not met.");
141 }
142 }
143
144 [Fact]
145 public void MissingEntrySectionDetectedUnknown()
146 {
147 var folder = TestData.Get(@"TestData\OverridableActions");
148
149 using (var fs = new DisposableFileSystem())
150 {
151 var baseFolder = fs.GetFolder();
152 var intermediateFolder = Path.Combine(baseFolder, "obj");
153
154 try
155 {
156 WixRunner.Execute(new[]
157 {
158 "build",
159 Path.Combine(folder, "PackageComponents.wxs"),
160 "-intermediateFolder", intermediateFolder,
161 "-o", Path.Combine(baseFolder, @"bin\test.bob")
162 });
163 }
164 catch (WixException we)
165 {
166 Assert.Equal("Could not find entry section in provided list of intermediates. Supported entry section types are: Product, Bundle, Patch, PatchCreation, Module.", we.Message);
167 return;
168 }
169
170 Assert.True(false, "Expected WixException for missing entry section but expectations were not met.");
171 }
172 }
173 }
174}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System.IO;
6 using System.Linq;
7 using WixBuildTools.TestSupport;
8 using WixToolset.Core.TestPackage;
9 using WixToolset.Data;
10 using Xunit;
11
12 public class MediaFixture
13 {
14 [Fact]
15 public void CanBuildMultiMedia()
16 {
17 var folder = TestData.Get(@"TestData");
18
19 using (var fs = new DisposableFileSystem())
20 {
21 var baseFolder = fs.GetFolder();
22 var intermediateFolder = Path.Combine(baseFolder, "obj");
23 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
24
25 var result = WixRunner.Execute(new[]
26 {
27 "build",
28 Path.Combine(folder, "Media", "MultiMedia.wxs"),
29 "-bindpath", Path.Combine(folder, "Media", "data"),
30 "-intermediateFolder", intermediateFolder,
31 "-o", msiPath
32 });
33
34 result.AssertSuccess();
35
36 var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb"));
37 var section = intermediate.Sections.Single();
38
39 var mediaSymbols = section.Symbols.OfType<WixToolset.Data.Symbols.MediaSymbol>().OrderBy(m => m.DiskId).ToList();
40 var fileSymbols = section.Symbols.OfType<WixToolset.Data.Symbols.FileSymbol>().OrderBy(f => f.Sequence).ToList();
41 Assert.Equal(1, mediaSymbols[0].DiskId);
42 Assert.Equal(2, mediaSymbols[0].LastSequence);
43 Assert.Equal(2, mediaSymbols[1].DiskId);
44 Assert.Equal(4, mediaSymbols[1].LastSequence);
45 Assert.Equal(new[]
46 {
47 "a1.txt",
48 "a2.txt",
49 "b1.txt",
50 "b2.txt",
51 }, fileSymbols.Select(f => f.Name).ToArray());
52 Assert.Equal(new[]
53 {
54 1,
55 2,
56 3,
57 4,
58 }, fileSymbols.Select(f => f.Sequence).ToArray());
59 }
60 }
61 }
62}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System;
6 using System.IO;
7 using System.Linq;
8 using WixBuildTools.TestSupport;
9 using WixToolset.Core.TestPackage;
10 using WixToolset.Data;
11 using WixToolset.Data.Symbols;
12 using WixToolset.Data.WindowsInstaller;
13 using Xunit;
14
15 public class ModuleFixture
16 {
17 [Fact]
18 public void CanBuildSimpleModule()
19 {
20 var folder = TestData.Get(@"TestData\SimpleModule");
21
22 using (var fs = new DisposableFileSystem())
23 {
24 var intermediateFolder = fs.GetFolder();
25
26 var result = WixRunner.Execute(new[]
27 {
28 "build",
29 Path.Combine(folder, "Module.wxs"),
30 "-loc", Path.Combine(folder, "Module.en-us.wxl"),
31 "-bindpath", Path.Combine(folder, "data"),
32 "-intermediateFolder", intermediateFolder,
33 "-o", Path.Combine(intermediateFolder, @"bin\test.msm")
34 });
35
36 result.AssertSuccess();
37
38 var msmPath = Path.Combine(intermediateFolder, @"bin\test.msm");
39 Assert.True(File.Exists(msmPath));
40 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb")));
41
42 var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb"));
43 var section = intermediate.Sections.Single();
44
45 var dirSymbols = section.Symbols.OfType<DirectorySymbol>().OrderBy(d => d.Id.Id).ToList();
46 WixAssert.CompareLineByLine(new[]
47 {
48 "MergeRedirectFolder\tTARGETDIR\t.",
49 "NotTheMergeRedirectFolder\tTARGETDIR\t.",
50 "TARGETDIR\t\tSourceDir"
51 }, dirSymbols.Select(d => String.Join("\t", d.Id.Id, d.ParentDirectoryRef, d.Name)).ToArray());
52
53 var fileSymbols = section.Symbols.OfType<FileSymbol>().OrderBy(d => d.Id.Id).ToList();
54 WixAssert.CompareLineByLine(new[]
55 {
56 $"File1\t{Path.Combine(folder, @"data\test.txt")}\ttest.txt",
57 $"File2\t{Path.Combine(folder, @"data\test.txt")}\ttest.txt",
58 }, fileSymbols.Select(fileSymbol => String.Join("\t", fileSymbol.Id.Id, fileSymbol[FileSymbolFields.Source].AsPath().Path, fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path)).ToArray());
59
60 var data = WindowsInstallerData.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb"));
61 var fileRows = data.Tables["File"].Rows;
62 Assert.Equal(new[]
63 {
64 "File1.243FB739_4D05_472F_9CFB_EF6B1017B6DE",
65 "File2.243FB739_4D05_472F_9CFB_EF6B1017B6DE",
66 }, fileRows.Select(r => r.FieldAsString(0)).ToArray());
67
68 var cabPath = Path.Combine(intermediateFolder, "msm-test.cab");
69 Query.ExtractStream(msmPath, "MergeModule.CABinet", cabPath);
70 var files = Query.GetCabinetFiles(cabPath);
71 Assert.Equal(new[]
72 {
73 "File1.243FB739_4D05_472F_9CFB_EF6B1017B6DE",
74 "File2.243FB739_4D05_472F_9CFB_EF6B1017B6DE",
75 }, files.Select(f => Path.Combine(f.Path, f.Name)).ToArray());
76 }
77 }
78
79 [Fact]
80 public void CanSuppressModularization()
81 {
82 var folder = TestData.Get(@"TestData\SuppressModularization");
83
84 using (var fs = new DisposableFileSystem())
85 {
86 var intermediateFolder = fs.GetFolder();
87
88 var result = WixRunner.Execute(new[]
89 {
90 "build",
91 Path.Combine(folder, "Module.wxs"),
92 "-loc", Path.Combine(folder, "Module.en-us.wxl"),
93 "-bindpath", Path.Combine(folder, "data"),
94 "-intermediateFolder", intermediateFolder,
95 "-sw1079",
96 "-sw1086",
97 "-o", Path.Combine(intermediateFolder, @"bin\test.msm")
98 });
99
100 result.AssertSuccess();
101
102 var msmPath = Path.Combine(intermediateFolder, @"bin\test.msm");
103
104 var rows = Query.QueryDatabase(msmPath, new[] { "CustomAction", "Property" });
105 WixAssert.CompareLineByLine(new[]
106 {
107 "CustomAction:Test\t11265\tFakeCA.243FB739_4D05_472F_9CFB_EF6B1017B6DE\tTestEntry\t",
108 "Property:MsiHiddenProperties\tTest"
109 }, rows);
110 }
111 }
112 }
113}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System;
6 using System.IO;
7 using System.Linq;
8 using Example.Extension;
9 using WixBuildTools.TestSupport;
10 using WixToolset.Core.TestPackage;
11 using WixToolset.Data;
12 using WixToolset.Data.Symbols;
13 using WixToolset.Data.WindowsInstaller;
14 using Xunit;
15
16 public class MsiFixture
17 {
18 [Fact]
19 public void CanBuildSingleFile()
20 {
21 var folder = TestData.Get(@"TestData\SingleFile");
22
23 using (var fs = new DisposableFileSystem())
24 {
25 var baseFolder = fs.GetFolder();
26 var intermediateFolder = Path.Combine(baseFolder, "obj");
27
28 var result = WixRunner.Execute(new[]
29 {
30 "build",
31 Path.Combine(folder, "Package.wxs"),
32 Path.Combine(folder, "PackageComponents.wxs"),
33 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
34 "-bindpath", Path.Combine(folder, "data"),
35 "-intermediateFolder", intermediateFolder,
36 "-o", Path.Combine(baseFolder, @"bin\test.msi")
37 });
38
39 result.AssertSuccess();
40
41 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi")));
42 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb")));
43 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\MsiPackage\test.txt")));
44
45 var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb"));
46
47 Assert.False(intermediate.HasLevel(WixToolset.Data.IntermediateLevels.Compiled));
48 Assert.True(intermediate.HasLevel(WixToolset.Data.IntermediateLevels.Linked));
49 Assert.True(intermediate.HasLevel(WixToolset.Data.IntermediateLevels.Resolved));
50 Assert.True(intermediate.HasLevel(WixToolset.Data.WindowsInstaller.IntermediateLevels.FullyBound));
51
52 var section = intermediate.Sections.Single();
53
54 var fileSymbol = section.Symbols.OfType<FileSymbol>().First();
55 Assert.Equal(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path);
56 Assert.Equal(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path);
57 }
58 }
59
60 [Fact]
61 public void CanBuildSingleFileCompressed()
62 {
63 var folder = TestData.Get(@"TestData\SingleFileCompressed");
64
65 using (var fs = new DisposableFileSystem())
66 {
67 var intermediateFolder = fs.GetFolder();
68
69 var result = WixRunner.Execute(new[]
70 {
71 "build",
72 Path.Combine(folder, "Package.wxs"),
73 Path.Combine(folder, "PackageComponents.wxs"),
74 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
75 "-bindpath", Path.Combine(folder, "data"),
76 "-intermediateFolder", intermediateFolder,
77 "-o", Path.Combine(intermediateFolder, @"bin\test.msi")
78 });
79
80 result.AssertSuccess();
81
82 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi")));
83 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\example.cab")));
84 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb")));
85
86 var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb"));
87 var section = intermediate.Sections.Single();
88
89 var fileSymbol = section.Symbols.OfType<FileSymbol>().Single();
90 Assert.Equal(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path);
91 Assert.Equal(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path);
92 }
93 }
94
95 [Fact]
96 public void CanBuildSingleFileCompressedWithMediaTemplate()
97 {
98 var folder = TestData.Get(@"TestData\SingleFileCompressed");
99
100 using (var fs = new DisposableFileSystem())
101 {
102 var intermediateFolder = fs.GetFolder();
103
104 var result = WixRunner.Execute(new[]
105 {
106 "build",
107 Path.Combine(folder, "Package.wxs"),
108 Path.Combine(folder, "PackageComponents.wxs"),
109 "-d", "MediaTemplateCompressionLevel",
110 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
111 "-bindpath", Path.Combine(folder, "data"),
112 "-intermediateFolder", intermediateFolder,
113 "-o", Path.Combine(intermediateFolder, @"bin\test.msi")
114 });
115
116 result.AssertSuccess();
117
118 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi")));
119 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\cab1.cab")));
120 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb")));
121 }
122 }
123
124 [Fact]
125 public void CanBuildSingleFileCompressedWithMediaTemplateWithLowCompression()
126 {
127 var folder = TestData.Get(@"TestData\SingleFileCompressed");
128
129 using (var fs = new DisposableFileSystem())
130 {
131 var intermediateFolder = fs.GetFolder();
132
133 var result = WixRunner.Execute(new[]
134 {
135 "build",
136 Path.Combine(folder, "Package.wxs"),
137 Path.Combine(folder, "PackageComponents.wxs"),
138 "-d", "MediaTemplateCompressionLevel=low",
139 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
140 "-bindpath", Path.Combine(folder, "data"),
141 "-intermediateFolder", intermediateFolder,
142 "-o", Path.Combine(intermediateFolder, @"bin\test.msi")
143 });
144
145 result.AssertSuccess();
146
147 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi")));
148 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\low1.cab")));
149 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb")));
150 }
151 }
152
153 [Fact]
154 public void CanBuildMultipleFilesCompressed()
155 {
156 var folder = TestData.Get(@"TestData\MultiFileCompressed");
157
158 using (var fs = new DisposableFileSystem())
159 {
160 var intermediateFolder = fs.GetFolder();
161
162 var result = WixRunner.Execute(new[]
163 {
164 "build",
165 "-sw1079", // TODO: why does this test need to create a second cab which is empty?
166 Path.Combine(folder, "Package.wxs"),
167 Path.Combine(folder, "PackageComponents.wxs"),
168 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
169 "-bindpath", Path.Combine(folder, "data"),
170 "-intermediateFolder", intermediateFolder,
171 "-o", Path.Combine(intermediateFolder, @"bin\test.msi")
172 });
173
174 result.AssertSuccess();
175
176 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi")));
177 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\example1.cab")));
178 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\example2.cab")));
179 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb")));
180 }
181 }
182
183 [Fact]
184 public void CanFailBuildMissingFile()
185 {
186 var folder = TestData.Get(@"TestData\SingleFile");
187
188 using (var fs = new DisposableFileSystem())
189 {
190 var baseFolder = fs.GetFolder();
191 var intermediateFolder = Path.Combine(baseFolder, "obj");
192
193 var result = WixRunner.Execute(new[]
194 {
195 "build",
196 Path.Combine(folder, "Package.wxs"),
197 Path.Combine(folder, "PackageComponents.wxs"),
198 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
199 "-bindpath", Path.Combine(folder, "does-not-exist"),
200 "-bindpath", Path.Combine(folder, "also-does-not-exist"),
201 "-intermediateFolder", intermediateFolder,
202 "-o", Path.Combine(baseFolder, @"bin\test.msi")
203 }, out var messages);
204 Assert.Equal(103, result);
205
206 var error = messages.Single(m => m.Level == MessageLevel.Error);
207 var errorMessage = error.ToString();
208 var checkedPaths = errorMessage.Substring(errorMessage.IndexOf(':') + 1).Split(new[] { ',' }).Select(s => s.Trim()).ToArray();
209 Assert.Equal(new[]
210 {
211 "test.txt",
212 Path.Combine(folder, "does-not-exist", "test.txt"),
213 Path.Combine(folder, "also-does-not-exist", "test.txt"),
214 }, checkedPaths);
215 }
216 }
217
218 [Fact]
219 public void CanBuildWithErrorTable()
220 {
221 var folder = TestData.Get(@"TestData\ErrorsInUI");
222
223 using (var fs = new DisposableFileSystem())
224 {
225 var baseFolder = fs.GetFolder();
226 var intermediateFolder = Path.Combine(baseFolder, "obj");
227
228 var result = WixRunner.Execute(new[]
229 {
230 "build",
231 Path.Combine(folder, "Package.wxs"),
232 Path.Combine(folder, "PackageComponents.wxs"),
233 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
234 "-bindpath", Path.Combine(folder, "data"),
235 "-intermediateFolder", intermediateFolder,
236 "-o", Path.Combine(baseFolder, @"bin\test.msi")
237 });
238
239 result.AssertSuccess();
240
241 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi")));
242 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb")));
243 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\MsiPackage\test.txt")));
244
245 var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb"));
246 var section = intermediate.Sections.Single();
247
248 var errors = section.Symbols.OfType<ErrorSymbol>().ToDictionary(t => t.Id.Id);
249 Assert.Equal("Category 55 Emergency Doomsday Crisis", errors["1234"].Message.Trim());
250 Assert.Equal(" ", errors["5678"].Message);
251
252 var customAction1 = section.Symbols.OfType<CustomActionSymbol>().Where(t => t.Id.Id == "CanWeReferenceAnError_YesWeCan").Single();
253 Assert.Equal("1234", customAction1.Target);
254
255 var customAction2 = section.Symbols.OfType<CustomActionSymbol>().Where(t => t.Id.Id == "TextErrorsWorkOKToo").Single();
256 Assert.Equal("If you see this, something went wrong.", customAction2.Target);
257 }
258 }
259
260 [Fact]
261 public void CanLoadPdbGeneratedByBuild()
262 {
263 var folder = TestData.Get(@"TestData\MultiFileCompressed");
264
265 using (var fs = new DisposableFileSystem())
266 {
267 var intermediateFolder = fs.GetFolder();
268
269 var result = WixRunner.Execute(new[]
270 {
271 "build",
272 Path.Combine(folder, "Package.wxs"),
273 Path.Combine(folder, "PackageComponents.wxs"),
274 "-d", "MediaTemplateCompressionLevel",
275 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
276 "-bindpath", Path.Combine(folder, "data"),
277 "-intermediateFolder", intermediateFolder,
278 "-o", Path.Combine(intermediateFolder, @"bin\test.msi")
279 });
280
281 result.AssertSuccess();
282
283 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi")));
284 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\cab1.cab")));
285
286 var pdbPath = Path.Combine(intermediateFolder, @"bin\test.wixpdb");
287 Assert.True(File.Exists(pdbPath));
288
289 var output = WindowsInstallerData.Load(pdbPath, suppressVersionCheck: true);
290 Assert.NotNull(output);
291 }
292 }
293
294 [Fact]
295 public void CanLoadPdbGeneratedByBuildViaWixOutput()
296 {
297 var folder = TestData.Get(@"TestData\MultiFileCompressed");
298
299 using (var fs = new DisposableFileSystem())
300 {
301 var intermediateFolder = fs.GetFolder();
302
303 var result = WixRunner.Execute(new[]
304 {
305 "build",
306 Path.Combine(folder, "Package.wxs"),
307 Path.Combine(folder, "PackageComponents.wxs"),
308 "-d", "MediaTemplateCompressionLevel",
309 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
310 "-bindpath", Path.Combine(folder, "data"),
311 "-intermediateFolder", intermediateFolder,
312 "-o", Path.Combine(intermediateFolder, @"bin\test.msi")
313 });
314
315 result.AssertSuccess();
316
317 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi")));
318 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\cab1.cab")));
319
320 var pdbPath = Path.Combine(intermediateFolder, @"bin\test.wixpdb");
321 Assert.True(File.Exists(pdbPath));
322
323 var wixOutput = WixOutput.Read(pdbPath);
324 var output = WindowsInstallerData.Load(wixOutput, suppressVersionCheck: true);
325 Assert.NotNull(output);
326 }
327 }
328
329 [Fact]
330 public void CanBuildManualUpgrade()
331 {
332 var folder = TestData.Get(@"TestData\ManualUpgrade");
333
334 using (var fs = new DisposableFileSystem())
335 {
336 var intermediateFolder = fs.GetFolder();
337
338 var result = WixRunner.Execute(new[]
339 {
340 "build",
341 Path.Combine(folder, "Package.wxs"),
342 Path.Combine(folder, "PackageComponents.wxs"),
343 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
344 "-bindpath", Path.Combine(folder, "data"),
345 "-intermediateFolder", intermediateFolder,
346 "-o", Path.Combine(intermediateFolder, @"bin\test.msi")
347 }, out var messages);
348
349 Assert.Equal(0, result);
350
351 var pdbPath = Path.Combine(intermediateFolder, @"bin\test.wixpdb");
352 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.msi")));
353 Assert.True(File.Exists(pdbPath));
354 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\PFiles\MsiPackage\test.txt")));
355
356 var intermediate = Intermediate.Load(pdbPath);
357 var section = intermediate.Sections.Single();
358
359 var upgradeSymbol = section.Symbols.OfType<UpgradeSymbol>().Single();
360 Assert.False(upgradeSymbol.ExcludeLanguages);
361 Assert.True(upgradeSymbol.IgnoreRemoveFailures);
362 Assert.False(upgradeSymbol.VersionMaxInclusive);
363 Assert.True(upgradeSymbol.VersionMinInclusive);
364 Assert.Equal("13.0.0", upgradeSymbol.VersionMax);
365 Assert.Equal("12.0.0", upgradeSymbol.VersionMin);
366 Assert.False(upgradeSymbol.OnlyDetect);
367 Assert.Equal("BLAHBLAHBLAH", upgradeSymbol.ActionProperty);
368
369 var pdb = WindowsInstallerData.Load(pdbPath, suppressVersionCheck: false);
370 var secureProperties = pdb.Tables["Property"].Rows.Where(row => row.GetKey() == "SecureCustomProperties").Single();
371 Assert.Contains("BLAHBLAHBLAH", secureProperties.FieldAsString(1));
372 }
373 }
374
375 [Fact]
376 public void CanBuildWixipl()
377 {
378 var folder = TestData.Get(@"TestData\SingleFile");
379
380 using (var fs = new DisposableFileSystem())
381 {
382 var baseFolder = fs.GetFolder();
383 var intermediateFolder = Path.Combine(baseFolder, "obj");
384
385 var result = WixRunner.Execute(new[]
386 {
387 "build",
388 Path.Combine(folder, "Package.wxs"),
389 Path.Combine(folder, "PackageComponents.wxs"),
390 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
391 "-bindpath", Path.Combine(folder, "data"),
392 "-intermediateFolder", intermediateFolder,
393 "-o", Path.Combine(baseFolder, @"bin\test.wixipl")
394 }, out var messages);
395
396 Assert.Equal(0, result);
397
398 var builtFiles = Directory.GetFiles(Path.Combine(baseFolder, @"bin"));
399
400 Assert.Equal(new[]{
401 "test.wixipl"
402 }, builtFiles.Select(Path.GetFileName).ToArray());
403 }
404 }
405
406 [Fact]
407 public void CanBuildWixlib()
408 {
409 var folder = TestData.Get(@"TestData\SingleFile");
410
411 using (var fs = new DisposableFileSystem())
412 {
413 var baseFolder = fs.GetFolder();
414 var intermediateFolder = Path.Combine(baseFolder, "obj");
415
416 var result = WixRunner.Execute(new[]
417 {
418 "build",
419 Path.Combine(folder, "Package.wxs"),
420 Path.Combine(folder, "PackageComponents.wxs"),
421 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
422 "-bindpath", Path.Combine(folder, "data"),
423 "-intermediateFolder", intermediateFolder,
424 "-o", Path.Combine(baseFolder, @"bin\test.wixlib")
425 }, out var messages);
426
427 Assert.Equal(0, result);
428
429 var builtFiles = Directory.GetFiles(Path.Combine(baseFolder, @"bin"));
430
431 Assert.Equal(new[]{
432 "test.wixlib"
433 }, builtFiles.Select(Path.GetFileName).ToArray());
434 }
435 }
436
437 [Fact]
438 public void CanBuildBinaryWixlib()
439 {
440 var folder = TestData.Get(@"TestData\SingleFile");
441
442 using (var fs = new DisposableFileSystem())
443 {
444 var baseFolder = fs.GetFolder();
445 var intermediateFolder = Path.Combine(baseFolder, "obj");
446
447 var result = WixRunner.Execute(
448 "build",
449 Path.Combine(folder, "Package.wxs"),
450 Path.Combine(folder, "PackageComponents.wxs"),
451 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
452 "-bindpath", Path.Combine(folder, "data"),
453 "-intermediateFolder", intermediateFolder,
454 "-bindfiles",
455 "-o", Path.Combine(baseFolder, @"bin\test.wixlib"));
456
457 result.AssertSuccess();
458
459 using (var wixout = WixOutput.Read(Path.Combine(baseFolder, @"bin\test.wixlib")))
460 {
461 Assert.NotNull(wixout.GetDataStream("wix-ir.json"));
462
463 var text = wixout.GetData("wix-ir/test.txt");
464 Assert.Equal("This is test.txt.", text);
465 }
466 }
467 }
468
469 [Fact]
470 public void CanBuildBinaryWixlibWithCollidingFilenames()
471 {
472 var folder = TestData.Get(@"TestData\SameFileFolders");
473
474 using (var fs = new DisposableFileSystem())
475 {
476 var baseFolder = fs.GetFolder();
477 var intermediateFolder = Path.Combine(baseFolder, "obj");
478
479 var result = WixRunner.Execute(
480 "build",
481 Path.Combine(folder, "TestComponents.wxs"),
482 "-bindpath", Path.Combine(folder, "data"),
483 "-intermediateFolder", intermediateFolder,
484 "-bindfiles",
485 "-o", Path.Combine(baseFolder, @"bin\test.wixlib"));
486
487 result.AssertSuccess();
488
489 using (var wixout = WixOutput.Read(Path.Combine(baseFolder, @"bin\test.wixlib")))
490 {
491 Assert.NotNull(wixout.GetDataStream("wix-ir.json"));
492
493 var text = wixout.GetData("wix-ir/test.txt");
494 Assert.Equal(@"This is a\test.txt.", text);
495
496 var text2 = wixout.GetData("wix-ir/test.txt-1");
497 Assert.Equal(@"This is b\test.txt.", text2);
498
499 var text3 = wixout.GetData("wix-ir/test.txt-2");
500 Assert.Equal(@"This is c\test.txt.", text3);
501 }
502 }
503 }
504
505 [Fact]
506 public void CanBuildWithIncludePath()
507 {
508 var folder = TestData.Get(@"TestData\IncludePath");
509 var bindpath = Path.Combine(folder, "data");
510
511 using (var fs = new DisposableFileSystem())
512 {
513 var baseFolder = fs.GetFolder();
514 var intermediateFolder = Path.Combine(baseFolder, "obj");
515
516 var result = WixRunner.Execute(
517 "build",
518 Path.Combine(folder, "Package.wxs"),
519 Path.Combine(folder, "PackageComponents.wxs"),
520 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
521 "-bindpath", bindpath,
522 "-intermediateFolder", intermediateFolder,
523 "-o", Path.Combine(baseFolder, @"bin\test.msi"),
524 "-i", bindpath);
525
526 result.AssertSuccess();
527
528 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi")));
529 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb")));
530 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\MsiPackage\test.txt")));
531
532 var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb"));
533 var section = intermediate.Sections.Single();
534
535 var fileSymbol = section.Symbols.OfType<FileSymbol>().Single();
536 Assert.Equal(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path);
537 Assert.Equal(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path);
538 }
539 }
540
541 [Fact]
542 public void CanBuildWithAssembly()
543 {
544 var folder = TestData.Get(@"TestData\Assembly");
545
546 using (var fs = new DisposableFileSystem())
547 {
548 var baseFolder = fs.GetFolder();
549 var intermediateFolder = Path.Combine(baseFolder, "obj");
550
551 var result = WixRunner.Execute(new[]
552 {
553 "build",
554 Path.Combine(folder, "Package.wxs"),
555 Path.Combine(folder, "PackageComponents.wxs"),
556 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
557 "-bindpath", Path.Combine(folder, "data"),
558 "-intermediateFolder", intermediateFolder,
559 "-o", Path.Combine(baseFolder, @"bin\test.msi")
560 });
561
562 result.AssertSuccess();
563
564 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi")));
565 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb")));
566 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\AssemblyMsiPackage\candle.exe")));
567
568 var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb"));
569 var section = intermediate.Sections.Single();
570
571 var fileSymbol = section.Symbols.OfType<FileSymbol>().Single();
572 Assert.Equal(Path.Combine(folder, @"data\candle.exe"), fileSymbol[FileSymbolFields.Source].AsPath().Path);
573 Assert.Equal(@"candle.exe", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path);
574
575 var msiAssemblyNameSymbols = section.Symbols.OfType<MsiAssemblyNameSymbol>();
576 Assert.Equal(new[]
577 {
578 "culture",
579 "fileVersion",
580 "name",
581 "processorArchitecture",
582 "publicKeyToken",
583 "version"
584 }, msiAssemblyNameSymbols.OrderBy(a => a.Name).Select(a => a.Name).ToArray());
585
586 Assert.Equal(new[]
587 {
588 "neutral",
589 "3.11.11810.0",
590 "candle",
591 "x86",
592 "256B3414DFA97718",
593 "3.0.0.0"
594 }, msiAssemblyNameSymbols.OrderBy(a => a.Name).Select(a => a.Value).ToArray());
595 }
596 }
597
598 [Fact]
599 public void CanBuild64bit()
600 {
601 var folder = TestData.Get(@"TestData\SingleFile");
602
603 using (var fs = new DisposableFileSystem())
604 {
605 var baseFolder = fs.GetFolder();
606 var intermediateFolder = Path.Combine(baseFolder, "obj");
607
608 var result = WixRunner.Execute(new[]
609 {
610 "build",
611 Path.Combine(folder, "Package.wxs"),
612 Path.Combine(folder, "PackageComponents.wxs"),
613 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
614 "-bindpath", Path.Combine(folder, "data"),
615 "-intermediateFolder", intermediateFolder,
616 "-arch", "x64",
617 "-o", Path.Combine(baseFolder, @"bin\test.msi")
618 });
619
620 result.AssertSuccess();
621
622 var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb"));
623 var section = intermediate.Sections.Single();
624
625 var platformSummary = section.Symbols.OfType<SummaryInformationSymbol>().Single(s => s.PropertyId == SummaryInformationType.PlatformAndLanguage);
626 Assert.Equal("x64;1033", platformSummary.Value);
627 }
628 }
629
630 [Fact]
631 public void CanBuildSharedComponent()
632 {
633 var folder = TestData.Get(@"TestData\SingleFile");
634
635 using (var fs = new DisposableFileSystem())
636 {
637 var baseFolder = fs.GetFolder();
638 var intermediateFolder = Path.Combine(baseFolder, "obj");
639
640 var result = WixRunner.Execute(new[]
641 {
642 "build",
643 Path.Combine(folder, "Package.wxs"),
644 Path.Combine(folder, "PackageComponents.wxs"),
645 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
646 "-bindpath", Path.Combine(folder, "data"),
647 "-intermediateFolder", intermediateFolder,
648 "-arch", "x64",
649 "-o", Path.Combine(baseFolder, @"bin\test.msi")
650 });
651
652 result.AssertSuccess();
653
654 var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb"));
655 var section = intermediate.Sections.Single();
656
657 // Only one component is shared.
658 var sharedComponentSymbols = section.Symbols.OfType<ComponentSymbol>();
659 Assert.Equal(1, sharedComponentSymbols.Sum(t => t.Shared ? 1 : 0));
660
661 // And it is this one.
662 var sharedComponentSymbol = sharedComponentSymbols.Single(t => t.Id.Id == "Shared.dll");
663 Assert.True(sharedComponentSymbol.Shared);
664 }
665 }
666
667 [Fact]
668 public void CanBuildSetProperty()
669 {
670 var folder = TestData.Get(@"TestData\SetProperty");
671
672 using (var fs = new DisposableFileSystem())
673 {
674 var baseFolder = fs.GetFolder();
675 var intermediateFolder = Path.Combine(baseFolder, "obj");
676
677 var result = WixRunner.Execute(new[]
678 {
679 "build",
680 Path.Combine(folder, "Package.wxs"),
681 Path.Combine(folder, "PackageComponents.wxs"),
682 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
683 "-bindpath", Path.Combine(folder, "data"),
684 "-intermediateFolder", intermediateFolder,
685 "-o", Path.Combine(baseFolder, @"bin\test.msi")
686 });
687
688 result.AssertSuccess();
689
690 var output = WindowsInstallerData.Load(Path.Combine(baseFolder, @"bin\test.wixpdb"), false);
691 var caRows = output.Tables["CustomAction"].Rows.Single();
692 Assert.Equal("SetINSTALLLOCATION", caRows.FieldAsString(0));
693 Assert.Equal("51", caRows.FieldAsString(1));
694 Assert.Equal("INSTALLLOCATION", caRows.FieldAsString(2));
695 Assert.Equal("[INSTALLFOLDER]", caRows.FieldAsString(3));
696 }
697 }
698
699 [Fact]
700 public void CanBuildVersionIndependentProgId()
701 {
702 var folder = TestData.Get(@"TestData\ProgId");
703
704 using (var fs = new DisposableFileSystem())
705 {
706 var baseFolder = fs.GetFolder();
707 var intermediateFolder = Path.Combine(baseFolder, "obj");
708
709 var result = WixRunner.Execute(new[]
710 {
711 "build",
712 Path.Combine(folder, "Package.wxs"),
713 Path.Combine(folder, "PackageComponents.wxs"),
714 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
715 "-bindpath", Path.Combine(folder, "data"),
716 "-intermediateFolder", intermediateFolder,
717 "-o", Path.Combine(baseFolder, @"bin\test.msi")
718 });
719
720 result.AssertSuccess();
721
722 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.msi")));
723 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb")));
724 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\PFiles\MsiPackage\Foo.exe")));
725
726 var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb"));
727 var section = intermediate.Sections.Single();
728
729 var progids = section.Symbols.OfType<ProgIdSymbol>().OrderBy(symbol => symbol.ProgId).ToList();
730 Assert.Equal(new[]
731 {
732 "Foo.File.hol",
733 "Foo.File.hol.15"
734 }, progids.Select(p => p.ProgId).ToArray());
735
736 Assert.Equal(new[]
737 {
738 "Foo.File.hol.15",
739 null
740 }, progids.Select(p => p.ParentProgIdRef).ToArray());
741 }
742 }
743
744 [Fact]
745 public void CanBuildInstanceTransform()
746 {
747 var folder = TestData.Get(@"TestData\InstanceTransform");
748
749 using (var fs = new DisposableFileSystem())
750 {
751 var intermediateFolder = fs.GetFolder();
752
753 var result = WixRunner.Execute(new[]
754 {
755 "build",
756 Path.Combine(folder, "Package.wxs"),
757 Path.Combine(folder, "PackageComponents.wxs"),
758 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
759 "-bindpath", Path.Combine(folder, "data"),
760 "-intermediateFolder", intermediateFolder,
761 "-o", Path.Combine(intermediateFolder, @"bin\test.msi")
762 });
763
764 result.AssertSuccess();
765
766 var output = WindowsInstallerData.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb"), false);
767 var substorage = output.SubStorages.Single();
768 Assert.Equal("I1", substorage.Name);
769
770 var data = substorage.Data;
771 Assert.Equal(new[]
772 {
773 "_SummaryInformation",
774 "Property",
775 "Upgrade"
776 }, data.Tables.Select(t => t.Name).ToArray());
777
778 Assert.Equal(new[]
779 {
780 "INSTANCEPROPERTY\tI1",
781 "ProductName\tMsiPackage (Instance 1)",
782 }, JoinRows(data.Tables["Property"]));
783
784 Assert.Equal(new[]
785 {
786 "{047730A5-30FE-4A62-A520-DA9381B8226A}\t\t1.0.0.0\t1033\t1\t\tWIX_UPGRADE_DETECTED",
787 "{047730A5-30FE-4A62-A520-DA9381B8226A}\t\t1.0.0.0\t1033\t1\t0\t0",
788 "{047730A5-30FE-4A62-A520-DA9381B8226A}\t1.0.0.0\t\t1033\t2\t\tWIX_DOWNGRADE_DETECTED",
789 "{047730A5-30FE-4A62-A520-DA9381B8226A}\t1.0.0.0\t\t1033\t2\t0\t0"
790 }, JoinRows(data.Tables["Upgrade"]));
791 }
792 }
793
794 [Fact(Skip = "Test demonstrates failure")]
795 public void FailsBuildAtLinkTimeForMissingEnsureTable()
796 {
797 var folder = TestData.Get(@"TestData");
798 var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath);
799
800 using (var fs = new DisposableFileSystem())
801 {
802 var baseFolder = fs.GetFolder();
803 var intermediateFolder = Path.Combine(baseFolder, "obj");
804 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
805
806 var result = WixRunner.Execute(new[]
807 {
808 "build",
809 Path.Combine(folder, "BadEnsureTable", "BadEnsureTable.wxs"),
810 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
811 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
812 "-ext", extensionPath,
813 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
814 "-intermediateFolder", intermediateFolder,
815 "-o", msiPath
816 });
817 Assert.Collection(result.Messages,
818 first =>
819 {
820 Assert.Equal(MessageLevel.Error, first.Level);
821 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());
822 });
823
824 Assert.False(File.Exists(msiPath));
825 }
826 }
827
828 private static string[] JoinRows(Table table)
829 {
830 return table.Rows.Select(r => JoinFields(r.Fields)).ToArray();
831
832 string JoinFields(Field[] fields)
833 {
834 return String.Join('\t', fields.Select(f => f.ToString()));
835 }
836 }
837 }
838}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System;
6 using System.IO;
7 using System.Linq;
8 using Example.Extension;
9 using WixBuildTools.TestSupport;
10 using WixToolset.Core.TestPackage;
11 using WixToolset.Data;
12 using WixToolset.Data.Symbols;
13 using WixToolset.Data.WindowsInstaller;
14 using Xunit;
15
16 public class MsiQueryFixture
17 {
18 [Fact]
19 public void PopulatesAppIdTableWhenAdvertised()
20 {
21 var folder = TestData.Get(@"TestData");
22
23 using (var fs = new DisposableFileSystem())
24 {
25 var baseFolder = fs.GetFolder();
26 var intermediateFolder = Path.Combine(baseFolder, "obj");
27 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
28
29 var result = WixRunner.Execute(new[]
30 {
31 "build",
32 Path.Combine(folder, "AppId", "Advertised.wxs"),
33 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
34 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
35 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
36 "-intermediateFolder", intermediateFolder,
37 "-o", msiPath
38 });
39
40 result.AssertSuccess();
41
42 Assert.True(File.Exists(msiPath));
43 var results = Query.QueryDatabase(msiPath, new[] { "AppId" });
44 WixAssert.CompareLineByLine(new[]
45 {
46 "AppId:{D6040299-B15C-4C94-AE26-0C9B60D14C35}\t\t\t\t\t\t",
47 }, results);
48 }
49 }
50
51 [Fact]
52 public void PopulatesAppSearchTablesFromComponentSearch()
53 {
54 var folder = TestData.Get(@"TestData");
55
56 using (var fs = new DisposableFileSystem())
57 {
58 var baseFolder = fs.GetFolder();
59 var intermediateFolder = Path.Combine(baseFolder, "obj");
60 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
61
62 var result = WixRunner.Execute(new[]
63 {
64 "build",
65 Path.Combine(folder, "AppSearch", "ComponentSearch.wxs"),
66 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
67 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
68 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
69 "-intermediateFolder", intermediateFolder,
70 "-o", msiPath
71 });
72
73 result.AssertSuccess();
74
75 Assert.True(File.Exists(msiPath));
76 var results = Query.QueryDatabase(msiPath, new[] { "AppSearch", "CompLocator" });
77 WixAssert.CompareLineByLine(new[]
78 {
79 "AppSearch:SAMPLECOMPFOUND\tSampleCompSearch",
80 "CompLocator:SampleCompSearch\t{4D9A0D20-D0CC-40DE-B580-EAD38B985217}\t1",
81 }, results);
82 }
83 }
84
85 [Fact]
86 public void PopulatesAppSearchTablesFromDirectorySearch()
87 {
88 var folder = TestData.Get(@"TestData");
89
90 using (var fs = new DisposableFileSystem())
91 {
92 var baseFolder = fs.GetFolder();
93 var intermediateFolder = Path.Combine(baseFolder, "obj");
94 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
95
96 var result = WixRunner.Execute(new[]
97 {
98 "build",
99 Path.Combine(folder, "AppSearch", "DirectorySearch.wxs"),
100 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
101 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
102 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
103 "-intermediateFolder", intermediateFolder,
104 "-o", msiPath
105 });
106
107 result.AssertSuccess();
108
109 Assert.True(File.Exists(msiPath));
110 var results = Query.QueryDatabase(msiPath, new[] { "AppSearch", "DrLocator" });
111 WixAssert.CompareLineByLine(new[]
112 {
113 "AppSearch:SAMPLEDIRFOUND\tSampleDirSearch",
114 "DrLocator:SampleDirSearch\t\tC:\\SampleDir\t",
115 }, results);
116 }
117 }
118
119 [Fact]
120 public void PopulatesAppSearchTablesFromFileSearch()
121 {
122 var folder = TestData.Get(@"TestData");
123
124 using (var fs = new DisposableFileSystem())
125 {
126 var baseFolder = fs.GetFolder();
127 var intermediateFolder = Path.Combine(baseFolder, "obj");
128 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
129
130 var result = WixRunner.Execute(new[]
131 {
132 "build",
133 Path.Combine(folder, "AppSearch", "FileSearch.wxs"),
134 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
135 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
136 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
137 "-intermediateFolder", intermediateFolder,
138 "-o", msiPath
139 });
140
141 result.AssertSuccess();
142
143 Assert.True(File.Exists(msiPath));
144 var results = Query.QueryDatabase(msiPath, new[] { "AppSearch", "DrLocator", "IniLocator" });
145 WixAssert.CompareLineByLine(new[]
146 {
147 "AppSearch:SAMPLEFILEFOUND\tSampleFileSearch",
148 "DrLocator:SampleFileSearch\tSampleIniFileSearch\t\t",
149 "IniLocator:SampleFileSearch\tsample.fil\tMySection\tMyKey\t\t1",
150 }, results);
151 }
152 }
153
154 [Fact]
155 public void PopulatesAppSearchTablesFromRegistrySearch()
156 {
157 var folder = TestData.Get(@"TestData");
158
159 using (var fs = new DisposableFileSystem())
160 {
161 var baseFolder = fs.GetFolder();
162 var intermediateFolder = Path.Combine(baseFolder, "obj");
163 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
164
165 var result = WixRunner.Execute(new[]
166 {
167 "build",
168 Path.Combine(folder, "AppSearch", "RegistrySearch.wxs"),
169 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
170 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
171 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
172 "-intermediateFolder", intermediateFolder,
173 "-o", msiPath
174 });
175
176 result.AssertSuccess();
177
178 Assert.True(File.Exists(msiPath));
179 var results = Query.QueryDatabase(msiPath, new[] { "AppSearch", "RegLocator" });
180 WixAssert.CompareLineByLine(new[]
181 {
182 "AppSearch:SAMPLEREGFOUND\tSampleRegSearch",
183 "RegLocator:SampleRegSearch\t2\tSampleReg\t\t2",
184 }, results);
185 }
186 }
187
188 [Fact]
189 public void PopulatesAppSearchTablesFromRegistrySearch64()
190 {
191 var folder = TestData.Get(@"TestData");
192
193 using (var fs = new DisposableFileSystem())
194 {
195 var baseFolder = fs.GetFolder();
196 var intermediateFolder = Path.Combine(baseFolder, "obj");
197 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
198
199 var result = WixRunner.Execute(new[]
200 {
201 "build",
202 Path.Combine(folder, "AppSearch", "RegistrySearch64.wxs"),
203 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
204 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
205 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
206 "-intermediateFolder", intermediateFolder,
207 "-o", msiPath
208 });
209
210 result.AssertSuccess();
211
212 Assert.True(File.Exists(msiPath));
213 var results = Query.QueryDatabase(msiPath, new[] { "AppSearch", "RegLocator" });
214 WixAssert.CompareLineByLine(new[]
215 {
216 "AppSearch:SAMPLEREGFOUND\tSampleRegSearch",
217 "RegLocator:SampleRegSearch\t2\tSampleReg\t\t18",
218 }, results);
219 }
220 }
221
222 [Fact]
223 public void PopulatesClassTablesWhenIconIndexIsZero()
224 {
225 var folder = TestData.Get(@"TestData");
226
227 using (var fs = new DisposableFileSystem())
228 {
229 var baseFolder = fs.GetFolder();
230 var intermediateFolder = Path.Combine(baseFolder, "obj");
231 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
232
233 var result = WixRunner.Execute(new[]
234 {
235 "build",
236 Path.Combine(folder, "Class", "IconIndex0.wxs"),
237 Path.Combine(folder, "Icon", "SampleIcon.wxs"),
238 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
239 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
240 "-bindpath", Path.Combine(folder, ".Data"),
241 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
242 "-intermediateFolder", intermediateFolder,
243 "-o", msiPath
244 });
245
246 result.AssertSuccess();
247
248 Assert.True(File.Exists(msiPath));
249 var results = Query.QueryDatabase(msiPath, new[] { "Class" });
250 WixAssert.CompareLineByLine(new[]
251 {
252 "Class:{3FAED4CC-C473-4B8A-BE8B-303871377A4A}\tLocalServer32\tClassComp\t\tFakeClass3FAE\t\t\tSampleIcon\t0\t\t\tProductFeature\t",
253 }, results);
254 }
255 }
256
257 [Fact]
258 public void PopulatesClassTablesWhenProgIdIsNestedUnderAdvertisedClass()
259 {
260 var folder = TestData.Get(@"TestData");
261
262 using (var fs = new DisposableFileSystem())
263 {
264 var baseFolder = fs.GetFolder();
265 var intermediateFolder = Path.Combine(baseFolder, "obj");
266 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
267
268 var result = WixRunner.Execute(new[]
269 {
270 "build",
271 Path.Combine(folder, "ProgId", "NestedUnderClass.wxs"),
272 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
273 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
274 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
275 "-intermediateFolder", intermediateFolder,
276 "-o", msiPath
277 });
278
279 result.AssertSuccess();
280
281 Assert.True(File.Exists(msiPath));
282 var results = Query.QueryDatabase(msiPath, new[] { "Class", "ProgId", "Registry" });
283 WixAssert.CompareLineByLine(new[]
284 {
285 "Class:{F12A6F69-117F-471F-AE73-F8E74218F498}\tLocalServer32\tProgIdComp\t73E7DF7E-EFAC-4E11-90E2-6EBAEB8DE58D\tFakeClassF12A\t\t\t\t\t\t\tProductFeature\t",
286 "ProgId:73E7DF7E-EFAC-4E11-90E2-6EBAEB8DE58D\t\t{F12A6F69-117F-471F-AE73-F8E74218F498}\tFakeClassF12A\t\t",
287 "Registry:regUIIK326nDZpkWHuexeF58EikQvA\t0\t73E7DF7E-EFAC-4E11-90E2-6EBAEB8DE58D\tNoOpen\tNoOpen73E7\tProgIdComp",
288 "Registry:regvrhMurMp98anbQJkpgA8yJCefdM\t0\tCLSID\\{F12A6F69-117F-471F-AE73-F8E74218F498}\\Version\t\t0.0.0.1\tProgIdComp",
289 "Registry:regY1F4E2lvu_Up6gV6c3jeN5ukn8s\t0\tCLSID\\{F12A6F69-117F-471F-AE73-F8E74218F498}\\LocalServer32\tThreadingModel\tApartment\tProgIdComp",
290 }, results);
291 }
292 }
293
294 [Fact]
295 public void PopulatesControlTables()
296 {
297 var folder = TestData.Get(@"TestData");
298
299 using (var fs = new DisposableFileSystem())
300 {
301 var baseFolder = fs.GetFolder();
302 var intermediateFolder = Path.Combine(baseFolder, "obj");
303 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
304
305 var result = WixRunner.Execute(new[]
306 {
307 "build",
308 Path.Combine(folder, "DialogsInInstallUISequence", "PackageComponents.wxs"),
309 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
310 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
311 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
312 "-intermediateFolder", intermediateFolder,
313 "-o", msiPath,
314 });
315
316 result.AssertSuccess();
317
318 Assert.True(File.Exists(msiPath));
319
320 var results = Query.QueryDatabase(msiPath, new[] { "CheckBox", "Control", "ControlCondition", "InstallUISequence" });
321 WixAssert.CompareLineByLine(new[]
322 {
323 "CheckBox:WIXUI_EXITDIALOGOPTIONALCHECKBOX\t1",
324 "Control:FirstDialog\tHeader\tText\t0\t13\t90\t13\t3\t\tFirstDialogHeader\tTitle\t",
325 "Control:FirstDialog\tTitle\tText\t0\t0\t90\t13\t3\t\tFirstDialogTitle\tHeader\t",
326 "Control:SecondDialog\tOptionalCheckBox\tCheckBox\t0\t13\t100\t40\t2\tWIXUI_EXITDIALOGOPTIONALCHECKBOX\t[WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT]\tTitle\tOptional checkbox|Check this box for fun",
327 "Control:SecondDialog\tTitle\tText\t0\t0\t90\t13\t3\t\tSecondDialogTitle\tOptionalCheckBox\t",
328 "ControlCondition:FirstDialog\tHeader\tDisable\tInstalled",
329 "ControlCondition:FirstDialog\tHeader\tHide\tInstalled",
330 "ControlCondition:SecondDialog\tOptionalCheckBox\tShow\tWIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT AND NOT Installed",
331 "InstallUISequence:CostFinalize\t\t1000",
332 "InstallUISequence:CostInitialize\t\t800",
333 "InstallUISequence:ExecuteAction\t\t1300",
334 "InstallUISequence:FileCost\t\t900",
335 "InstallUISequence:FindRelatedProducts\t\t25",
336 "InstallUISequence:FirstDialog\tInstalled AND PATCH\t1298",
337 "InstallUISequence:LaunchConditions\t\t100",
338 "InstallUISequence:MigrateFeatureStates\t\t1200",
339 "InstallUISequence:SecondDialog\tNOT Installed\t1299",
340 "InstallUISequence:ValidateProductID\t\t700",
341 }, results);
342 }
343 }
344
345 [Fact]
346 public void PopulatesCreateFolderTableForNullKeypathComponents()
347 {
348 var folder = TestData.Get(@"TestData\Components");
349
350 using (var fs = new DisposableFileSystem())
351 {
352 var baseFolder = fs.GetFolder();
353 var intermediateFolder = Path.Combine(baseFolder, "obj");
354 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
355
356 var result = WixRunner.Execute(new[]
357 {
358 "build",
359 Path.Combine(folder, "Package.wxs"),
360 Path.Combine(folder, "PackageComponents.wxs"),
361 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
362 "-bindpath", Path.Combine(folder, "data"),
363 "-intermediateFolder", intermediateFolder,
364 "-o", msiPath
365 });
366
367 result.AssertSuccess();
368
369 Assert.True(File.Exists(msiPath));
370 var results = Query.QueryDatabase(msiPath, new[] { "CreateFolder" });
371 WixAssert.CompareLineByLine(new[]
372 {
373 "CreateFolder:INSTALLFOLDER\tNullKeypathComponent",
374 }, results);
375 }
376 }
377
378 [Fact]
379 public void PopulatesDirectoryTableWithValidDefaultDir()
380 {
381 var folder = TestData.Get(@"TestData");
382
383 using (var fs = new DisposableFileSystem())
384 {
385 var baseFolder = fs.GetFolder();
386 var intermediateFolder = Path.Combine(baseFolder, "obj");
387 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
388
389 var result = WixRunner.Execute(new[]
390 {
391 "build",
392 "-sw1031", // this is expected for this test
393 Path.Combine(folder, "DefaultDir", "DefaultDir.wxs"),
394 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
395 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
396 "-intermediateFolder", intermediateFolder,
397 "-o", msiPath
398 });
399
400 result.AssertSuccess();
401
402 Assert.True(File.Exists(msiPath));
403 var results = Query.QueryDatabase(msiPath, new[] { "Directory" });
404 WixAssert.CompareLineByLine(new[]
405 {
406 "Directory:DUPLICATENAMEANDSHORTNAME\tINSTALLFOLDER\tduplicat",
407 "Directory:Folder1\tINSTALLFOLDER\tFolder.1",
408 "Directory:Folder12\tINSTALLFOLDER\tFolder.12",
409 "Directory:Folder123\tINSTALLFOLDER\tFolder.123",
410 "Directory:Folder1234\tINSTALLFOLDER\tyakwclwy|Folder.1234",
411 "Directory:INSTALLFOLDER\tProgramFiles6432Folder\t1egc1laj|MsiPackage",
412 "Directory:NAMEANDSHORTNAME\tINSTALLFOLDER\tSHORTNAM|NameAndShortName",
413 "Directory:NAMEANDSHORTSOURCENAME\tINSTALLFOLDER\tNAMEASSN|NameAndShortSourceName",
414 "Directory:NAMEWITHSHORTVALUE\tINSTALLFOLDER\tSHORTVAL",
415 "Directory:ProgramFiles6432Folder\tProgramFilesFolder\t.",
416 "Directory:ProgramFilesFolder\tTARGETDIR\tPFiles",
417 "Directory:SHORTNAMEANDLONGSOURCENAME\tINSTALLFOLDER\tSHNALSNM:6ukthv5q|ShortNameAndLongSourceName",
418 "Directory:SHORTNAMEONLY\tINSTALLFOLDER\tSHORTONL",
419 "Directory:SOURCENAME\tINSTALLFOLDER\ts2s5bq-i|NameAndSourceName:dhnqygng|SourceNameWithName",
420 "Directory:SOURCENAMESONLY\tINSTALLFOLDER\t.:SRCNAMON|SourceNameOnly",
421 "Directory:SOURCENAMEWITHSHORTVALUE\tINSTALLFOLDER\t.:SRTSRCVL",
422 "Directory:TARGETDIR\t\tSourceDir",
423 }, results);
424 }
425 }
426
427 [Fact]
428 public void PopulatesEnvironmentTable()
429 {
430 var folder = TestData.Get(@"TestData");
431
432 using (var fs = new DisposableFileSystem())
433 {
434 var baseFolder = fs.GetFolder();
435 var intermediateFolder = Path.Combine(baseFolder, "obj");
436 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
437
438 var result = WixRunner.Execute(new[]
439 {
440 "build",
441 Path.Combine(folder, "Environment", "Environment.wxs"),
442 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
443 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
444 "-intermediateFolder", intermediateFolder,
445 "-o", msiPath
446 });
447
448 result.AssertSuccess();
449
450 Assert.True(File.Exists(msiPath));
451 var results = Query.QueryDatabase(msiPath, new[] { "Environment" });
452 WixAssert.CompareLineByLine(new[]
453 {
454 "Environment:PATH\t=-*PATH\t[INSTALLFOLDER]; ;[~]\tWixEnvironmentTest",
455 "Environment:WixEnvironmentTest1\t=-WixEnvTest1\t\tWixEnvironmentTest",
456 "Environment:WixEnvironmentTest2\t+-WixEnvTest1\t\tWixEnvironmentTest",
457 "Environment:WixEnvironmentTest3\t!-WixEnvTest1\t\tWixEnvironmentTest",
458 "Environment:WixEnvironmentTest4\t=-*WIX\t[INSTALLFOLDER]\tWixEnvironmentTest",
459 }, results);
460 }
461 }
462
463 [Fact(Skip = "Test demonstrates failure")]
464 public void PopulatesExampleTableBecauseOfEnsureTable()
465 {
466 var folder = TestData.Get(@"TestData");
467 var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath);
468
469 using (var fs = new DisposableFileSystem())
470 {
471 var baseFolder = fs.GetFolder();
472 var intermediateFolder = Path.Combine(baseFolder, "obj");
473 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
474
475 var result = WixRunner.Execute(new[]
476 {
477 "build",
478 Path.Combine(folder, "EnsureTable", "EnsureTable.wxs"),
479 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
480 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
481 "-ext", extensionPath,
482 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
483 "-intermediateFolder", intermediateFolder,
484 "-o", msiPath
485 });
486
487 result.AssertSuccess();
488
489 Assert.True(File.Exists(msiPath));
490 var results = Query.QueryDatabaseByTable(msiPath, new[] { "Wix4Example" });
491 Assert.Empty(results["Wix4Example"]);
492 }
493 }
494
495 [Fact]
496 public void PopulatesFeatureTableWithParent()
497 {
498 var folder = TestData.Get(@"TestData");
499
500 using (var fs = new DisposableFileSystem())
501 {
502 var baseFolder = fs.GetFolder();
503 var intermediateFolder = Path.Combine(baseFolder, "obj");
504 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
505
506 var result = WixRunner.Execute(new[]
507 {
508 "build",
509 Path.Combine(folder, "FeatureGroup", "FeatureGroup.wxs"),
510 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
511 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
512 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
513 "-intermediateFolder", intermediateFolder,
514 "-o", msiPath
515 });
516
517 result.AssertSuccess();
518
519 Assert.True(File.Exists(msiPath));
520 var results = Query.QueryDatabase(msiPath, new[] { "Feature" });
521 WixAssert.CompareLineByLine(new[]
522 {
523 "Feature:ChildFeature\tParentFeature\tChildFeatureTitle\t\t2\t1\t\t0",
524 "Feature:ParentFeature\t\tParentFeatureTitle\t\t2\t1\t\t0",
525 "Feature:ProductFeature\t\tMsiPackageTitle\t\t2\t1\t\t0",
526 }, results);
527 }
528 }
529
530 [Fact]
531 public void PopulatesFontTableFromFontTitle()
532 {
533 var folder = TestData.Get(@"TestData");
534
535 using (var fs = new DisposableFileSystem())
536 {
537 var baseFolder = fs.GetFolder();
538 var intermediateFolder = Path.Combine(baseFolder, "obj");
539 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
540
541 var result = WixRunner.Execute(new[]
542 {
543 "build",
544 Path.Combine(folder, "Font", "FontTitle.wxs"),
545 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
546 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
547 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
548 "-intermediateFolder", intermediateFolder,
549 "-o", msiPath
550 });
551
552 result.AssertSuccess();
553
554 Assert.True(File.Exists(msiPath));
555 var results = Query.QueryDatabase(msiPath, new[] { "Font" });
556 WixAssert.CompareLineByLine(new[]
557 {
558 "Font:test.txt\tFakeFont",
559 }, results);
560 }
561 }
562
563 [Fact]
564 public void PopulatesFontTableFromTrueType()
565 {
566 var folder = TestData.Get(@"TestData");
567
568 using (var fs = new DisposableFileSystem())
569 {
570 var baseFolder = fs.GetFolder();
571 var intermediateFolder = Path.Combine(baseFolder, "obj");
572 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
573
574 var result = WixRunner.Execute(new[]
575 {
576 "build",
577 Path.Combine(folder, "Font", "TrueType.wxs"),
578 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
579 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
580 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
581 "-intermediateFolder", intermediateFolder,
582 "-o", msiPath
583 });
584
585 result.AssertSuccess();
586
587 Assert.True(File.Exists(msiPath));
588 var results = Query.QueryDatabase(msiPath, new[] { "Font" });
589 WixAssert.CompareLineByLine(new[]
590 {
591 "Font:TrueTypeFontFile\t",
592 }, results);
593 }
594 }
595
596 [Fact]
597 public void PopulatesInstallExecuteSequenceTable()
598 {
599 var folder = TestData.Get(@"TestData");
600
601 using (var fs = new DisposableFileSystem())
602 {
603 var baseFolder = fs.GetFolder();
604 var intermediateFolder = Path.Combine(baseFolder, "obj");
605 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
606
607 var result = WixRunner.Execute(new[]
608 {
609 "build",
610 Path.Combine(folder, "Upgrade", "DetectOnly.wxs"),
611 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
612 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
613 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
614 "-intermediateFolder", intermediateFolder,
615 "-o", msiPath
616 });
617
618 result.AssertSuccess();
619
620 Assert.True(File.Exists(msiPath));
621 var results = Query.QueryDatabase(msiPath, new[] { "InstallExecuteSequence" });
622 WixAssert.CompareLineByLine(new[]
623 {
624 "InstallExecuteSequence:CostFinalize\t\t1000",
625 "InstallExecuteSequence:CostInitialize\t\t800",
626 "InstallExecuteSequence:CreateFolders\t\t3700",
627 "InstallExecuteSequence:FileCost\t\t900",
628 "InstallExecuteSequence:FindRelatedProducts\t\t25",
629 "InstallExecuteSequence:InstallFiles\t\t4000",
630 "InstallExecuteSequence:InstallFinalize\t\t6600",
631 "InstallExecuteSequence:InstallInitialize\t\t1500",
632 "InstallExecuteSequence:InstallValidate\t\t1400",
633 "InstallExecuteSequence:LaunchConditions\t\t100",
634 "InstallExecuteSequence:MigrateFeatureStates\t\t1200",
635 "InstallExecuteSequence:ProcessComponents\t\t1600",
636 "InstallExecuteSequence:PublishFeatures\t\t6300",
637 "InstallExecuteSequence:PublishProduct\t\t6400",
638 "InstallExecuteSequence:RegisterProduct\t\t6100",
639 "InstallExecuteSequence:RegisterUser\t\t6000",
640 "InstallExecuteSequence:RemoveExistingProducts\t\t1401",
641 "InstallExecuteSequence:RemoveFiles\t\t3500",
642 "InstallExecuteSequence:RemoveFolders\t\t3600",
643 "InstallExecuteSequence:UnpublishFeatures\t\t1800",
644 "InstallExecuteSequence:ValidateProductID\t\t700",
645 }, results);
646 }
647 }
648
649 [Fact]
650 public void PopulatesLockPermissionsTableWithEmptyPermissions()
651 {
652 var folder = TestData.Get(@"TestData");
653
654 using (var fs = new DisposableFileSystem())
655 {
656 var baseFolder = fs.GetFolder();
657 var intermediateFolder = Path.Combine(baseFolder, "obj");
658 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
659
660 var result = WixRunner.Execute(new[]
661 {
662 "build",
663 Path.Combine(folder, "LockPermissions", "EmptyPermissions.wxs"),
664 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
665 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
666 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
667 "-intermediateFolder", intermediateFolder,
668 "-o", msiPath
669 });
670
671 result.AssertSuccess();
672
673 Assert.True(File.Exists(msiPath));
674 var results = Query.QueryDatabase(msiPath, new[] { "LockPermissions" });
675 WixAssert.CompareLineByLine(new[]
676 {
677 "LockPermissions:INSTALLFOLDER\tCreateFolder\t\tAdministrator\t0",
678 }, results);
679 }
680 }
681
682 [Fact]
683 public void PopulatesMsiAssemblyTables()
684 {
685 var folder = TestData.Get(@"TestData");
686
687 using (var fs = new DisposableFileSystem())
688 {
689 var baseFolder = fs.GetFolder();
690 var intermediateFolder = Path.Combine(baseFolder, "obj");
691 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
692
693 var result = WixRunner.Execute(new[]
694 {
695 "build",
696 Path.Combine(folder, "Assembly", "Win32Assembly.wxs"),
697 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
698 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
699 "-bindpath", Path.Combine(folder, "Assembly", "data"),
700 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
701 "-intermediateFolder", intermediateFolder,
702 "-o", msiPath
703 });
704
705 result.AssertSuccess();
706
707 Assert.True(File.Exists(msiPath));
708 var results = Query.QueryDatabase(msiPath, new[] { "MsiAssembly", "MsiAssemblyName" });
709 WixAssert.CompareLineByLine(new[]
710 {
711 "MsiAssembly:test.txt\tProductFeature\ttest.dll.manifest\t\t1",
712 "MsiAssemblyName:test.txt\tname\tMyApplication.app",
713 "MsiAssemblyName:test.txt\tversion\t1.0.0.0",
714 }, results);
715 }
716 }
717
718 [Fact]
719 public void PopulatesReserveCostTable()
720 {
721 var folder = TestData.Get(@"TestData");
722
723 using (var fs = new DisposableFileSystem())
724 {
725 var baseFolder = fs.GetFolder();
726 var intermediateFolder = Path.Combine(baseFolder, "obj");
727 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
728
729 var result = WixRunner.Execute(new[]
730 {
731 "build",
732 Path.Combine(folder, "ReserveCost", "ReserveCost.wxs"),
733 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
734 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
735 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
736 "-intermediateFolder", intermediateFolder,
737 "-o", msiPath
738 });
739
740 result.AssertSuccess();
741
742 Assert.True(File.Exists(msiPath));
743 var results = Query.QueryDatabase(msiPath, new[] { "ReserveCost" });
744 WixAssert.CompareLineByLine(new[]
745 {
746 "ReserveCost:TestCost\tReserveCostComp\tINSTALLFOLDER\t100\t200",
747 }, results);
748 }
749 }
750
751 [Fact]
752 public void PopulatesServiceTables()
753 {
754 var folder = TestData.Get(@"TestData");
755
756 using (var fs = new DisposableFileSystem())
757 {
758 var baseFolder = fs.GetFolder();
759 var intermediateFolder = Path.Combine(baseFolder, "obj");
760 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
761
762 var result = WixRunner.Execute(new[]
763 {
764 "build",
765 Path.Combine(folder, "ServiceInstall", "OwnProcess.wxs"),
766 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
767 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
768 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
769 "-intermediateFolder", intermediateFolder,
770 "-o", msiPath
771 });
772
773 result.AssertSuccess();
774
775 Assert.True(File.Exists(msiPath));
776 var results = Query.QueryDatabase(msiPath, new[] { "ServiceInstall", "ServiceControl" });
777 WixAssert.CompareLineByLine(new[]
778 {
779 "ServiceControl:SampleService\tSampleService\t161\t\t1\ttest.txt",
780 "ServiceInstall:SampleService\tSampleService\t\t16\t4\t0\t\t\t\t\t\ttest.txt\t",
781 }, results);
782 }
783 }
784
785 [Fact]
786 public void PopulatesTextStyleTableWhenColorIsNull()
787 {
788 var folder = TestData.Get(@"TestData");
789
790 using (var fs = new DisposableFileSystem())
791 {
792 var baseFolder = fs.GetFolder();
793 var intermediateFolder = Path.Combine(baseFolder, "obj");
794 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
795
796 var result = WixRunner.Execute(new[]
797 {
798 "build",
799 Path.Combine(folder, "TextStyle", "ColorNull.wxs"),
800 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
801 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
802 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
803 "-intermediateFolder", intermediateFolder,
804 "-o", msiPath
805 });
806
807 result.AssertSuccess();
808
809 Assert.True(File.Exists(msiPath));
810 var results = Query.QueryDatabase(msiPath, new[] { "TextStyle" });
811 WixAssert.CompareLineByLine(new[]
812 {
813 "TextStyle:FirstTextStyle\tArial\t2\t\t",
814 }, results);
815 }
816 }
817
818 [Fact]
819 public void PopulatesTextStyleTableWhenSizeIsLocalized()
820 {
821 var folder = TestData.Get(@"TestData");
822
823 using (var fs = new DisposableFileSystem())
824 {
825 var baseFolder = fs.GetFolder();
826 var intermediateFolder = Path.Combine(baseFolder, "obj");
827 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
828
829 var result = WixRunner.Execute(new[]
830 {
831 "build",
832 Path.Combine(folder, "TextStyle", "SizeLocalized.wxs"),
833 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
834 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
835 "-loc", Path.Combine(folder, "TextStyle", "SizeLocalized.en-us.wxl"),
836 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
837 "-intermediateFolder", intermediateFolder,
838 "-o", msiPath,
839 });
840
841 result.AssertSuccess();
842
843 Assert.True(File.Exists(msiPath));
844 var results = Query.QueryDatabase(msiPath, new[] { "TextStyle" });
845 WixAssert.CompareLineByLine(new[]
846 {
847 "TextStyle:CustomFont\tTahoma\t8\t\t",
848 }, results);
849 }
850 }
851
852 [Fact]
853 public void PopulatesTypeLibTableWhenLanguageIsZero()
854 {
855 var folder = TestData.Get(@"TestData");
856
857 using (var fs = new DisposableFileSystem())
858 {
859 var baseFolder = fs.GetFolder();
860 var intermediateFolder = Path.Combine(baseFolder, "obj");
861 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
862
863 var result = WixRunner.Execute(new[]
864 {
865 "build",
866 Path.Combine(folder, "TypeLib", "Language0.wxs"),
867 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
868 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
869 "-intermediateFolder", intermediateFolder,
870 "-o", msiPath
871 });
872
873 result.AssertSuccess();
874
875 Assert.True(File.Exists(msiPath));
876 var results = Query.QueryDatabase(msiPath, new[] { "TypeLib" });
877 WixAssert.CompareLineByLine(new[]
878 {
879 "TypeLib:{765BE8EE-BD7F-491E-90D2-C5A972462B50}\t0\tTypeLibComp\t\t\t\tProductFeature\t",
880 }, results);
881 }
882 }
883
884 [Fact]
885 public void PopulatesUpgradeTableFromManualUpgrade()
886 {
887 var folder = TestData.Get(@"TestData\ManualUpgrade");
888
889 using (var fs = new DisposableFileSystem())
890 {
891 var intermediateFolder = fs.GetFolder();
892 var msiPath = Path.Combine(intermediateFolder, @"bin\test.msi");
893
894 var result = WixRunner.Execute(new[]
895 {
896 "build",
897 Path.Combine(folder, "Package.wxs"),
898 Path.Combine(folder, "PackageComponents.wxs"),
899 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
900 "-bindpath", Path.Combine(folder, "data"),
901 "-intermediateFolder", intermediateFolder,
902 "-o", msiPath
903 }, out var messages);
904
905 Assert.Equal(0, result);
906
907 Assert.True(File.Exists(msiPath));
908 var results = Query.QueryDatabase(msiPath, new[] { "Upgrade" });
909 WixAssert.CompareLineByLine(new[]
910 {
911 "Upgrade:{01120000-00E0-0000-0000-0000000FF1CE}\t12.0.0\t13.0.0\t\t260\t\tBLAHBLAHBLAH",
912 }, results);
913 }
914 }
915
916 [Fact]
917 public void PopulatesUpgradeTableFromDetectOnlyUpgrade()
918 {
919 var folder = TestData.Get(@"TestData");
920
921 using (var fs = new DisposableFileSystem())
922 {
923 var baseFolder = fs.GetFolder();
924 var intermediateFolder = Path.Combine(baseFolder, "obj");
925 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
926
927 var result = WixRunner.Execute(new[]
928 {
929 "build",
930 Path.Combine(folder, "Upgrade", "DetectOnly.wxs"),
931 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
932 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
933 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
934 "-intermediateFolder", intermediateFolder,
935 "-o", msiPath
936 });
937
938 result.AssertSuccess();
939
940 Assert.True(File.Exists(msiPath));
941 var results = Query.QueryDatabase(msiPath, new[] { "Upgrade" });
942 WixAssert.CompareLineByLine(new[]
943 {
944 "Upgrade:{12E4699F-E774-4D05-8A01-5BDD41BBA127}\t\t1.0.0.0\t1033\t1\t\tWIX_UPGRADE_DETECTED",
945 "Upgrade:{12E4699F-E774-4D05-8A01-5BDD41BBA127}\t1.0.0.0\t\t1033\t2\t\tWIX_DOWNGRADE_DETECTED",
946 "Upgrade:{B05772EA-82B8-4DE0-B7EB-45B5F0CCFE6D}\t1.0.0\t\t\t256\t\tRELPRODFOUND",
947 }, results);
948
949 var prefix = "Property:SecureCustomProperties\t";
950 var secureProperties = Query.QueryDatabase(msiPath, new[] { "Property" }).Where(p => p.StartsWith(prefix)).Single();
951 WixAssert.CompareLineByLine(new[]
952 {
953 "RELPRODFOUND",
954 "WIX_DOWNGRADE_DETECTED",
955 "WIX_UPGRADE_DETECTED",
956 }, secureProperties.Substring(prefix.Length).Split(';').OrderBy(p => p).ToArray());
957 }
958 }
959
960 [Fact]
961 public void CanMergeModule()
962 {
963 var folder = TestData.Get(@"TestData\SimpleMerge");
964
965 using (var fs = new DisposableFileSystem())
966 {
967 var intermediateFolder = fs.GetFolder();
968 var msiPath = Path.Combine(intermediateFolder, @"bin\test.msi");
969 var cabPath = Path.Combine(intermediateFolder, @"bin\cab1.cab");
970
971 var result = WixRunner.Execute(new[]
972 {
973 "build",
974 Path.Combine(folder, "Package.wxs"),
975 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
976 "-bindpath", Path.Combine(folder, ".data"),
977 "-intermediateFolder", intermediateFolder,
978 "-o", msiPath
979 });
980
981 result.AssertSuccess();
982
983 Assert.True(File.Exists(msiPath));
984 Assert.True(File.Exists(Path.Combine(intermediateFolder, @"bin\test.wixpdb")));
985
986 var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb"));
987 var section = intermediate.Sections.Single();
988 Assert.Empty(section.Symbols.OfType<FileSymbol>());
989
990 var data = WindowsInstallerData.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb"));
991 Assert.Empty(data.Tables["File"].Rows);
992
993 var results = Query.QueryDatabase(msiPath, new[] { "File" });
994 WixAssert.CompareLineByLine(new[]
995 {
996 "File:filyIq8rqcxxf903Hsn5K9L0SWV73g.243FB739_4D05_472F_9CFB_EF6B1017B6DE\tModuleComponent.243FB739_4D05_472F_9CFB_EF6B1017B6DE\ttest.txt\t17\t\t\t512\t0"
997 }, results);
998
999 var files = Query.GetCabinetFiles(cabPath);
1000 WixAssert.CompareLineByLine(new[]
1001 {
1002 "filyIq8rqcxxf903Hsn5K9L0SWV73g.243FB739_4D05_472F_9CFB_EF6B1017B6DE"
1003 }, files.Select(f => f.Name).ToArray());
1004 }
1005 }
1006
1007 [Fact]
1008 public void CanPublishComponentWithMultipleFeatureComponents()
1009 {
1010 var folder = TestData.Get(@"TestData\PublishComponent");
1011
1012 using (var fs = new DisposableFileSystem())
1013 {
1014 var baseFolder = fs.GetFolder();
1015 var intermediateFolder = Path.Combine(baseFolder, "obj");
1016 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
1017
1018 var result = WixRunner.Execute(new[]
1019 {
1020 "build",
1021 Path.Combine(folder, "Package.wxs"),
1022 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
1023 "-bindpath", Path.Combine(folder, "data"),
1024 "-intermediateFolder", intermediateFolder,
1025 "-o", msiPath
1026 });
1027
1028 result.AssertSuccess();
1029
1030 Assert.True(File.Exists(msiPath));
1031 var results = Query.QueryDatabase(msiPath, new[] { "PublishComponent" });
1032 WixAssert.CompareLineByLine(new[]
1033 {
1034 "PublishComponent:{0A82C8F6-9CE9-4336-B8BE-91A39B5F7081} Qualifier2 Component2 AppData2 ProductFeature2",
1035 "PublishComponent:{BD245B5A-EC33-46ED-98FF-E9D3D416AD04} Qualifier1 Component1 AppData1 ProductFeature1",
1036 }, results);
1037 }
1038 }
1039 }
1040}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System.IO;
6 using WixBuildTools.TestSupport;
7 using WixToolset.Core.TestPackage;
8 using Xunit;
9
10 public class MsiTransactionFixture
11 {
12 [Fact]
13 public void CantBuildX64AfterX86Bundle()
14 {
15 var folder = TestData.Get(@"TestData");
16
17 using (var fs = new DisposableFileSystem())
18 {
19 var baseFolder = fs.GetFolder();
20 var intermediateFolder = Path.Combine(baseFolder, "obj");
21 var binFolder = Path.Combine(baseFolder, "bin");
22 var exePath = Path.Combine(binFolder, "test.exe");
23
24 BuildMsiPackages(folder, intermediateFolder, binFolder);
25
26 var result = WixRunner.Execute(new[]
27 {
28 "build",
29 "-sw1151", // this is expected for this test
30 Path.Combine(folder, "MsiTransaction", "X64AfterX86Bundle.wxs"),
31 Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"),
32 "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
33 "-bindpath", binFolder,
34 "-intermediateFolder", intermediateFolder,
35 "-o", exePath,
36 });
37
38 Assert.Equal(390, result.ExitCode);
39 }
40 }
41
42 [Fact]
43 public void CanBuildX86AfterX64Bundle()
44 {
45 var folder = TestData.Get(@"TestData");
46
47 using (var fs = new DisposableFileSystem())
48 {
49 var baseFolder = fs.GetFolder();
50 var intermediateFolder = Path.Combine(baseFolder, "obj");
51 var binFolder = Path.Combine(baseFolder, "bin");
52 var exePath = Path.Combine(binFolder, "test.exe");
53
54 BuildMsiPackages(folder, intermediateFolder, binFolder);
55
56 var result = WixRunner.Execute(new[]
57 {
58 "build",
59 "-sw1151", // this is expected for this test
60 Path.Combine(folder, "MsiTransaction", "X86AfterX64Bundle.wxs"),
61 Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"),
62 "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
63 "-bindpath", binFolder,
64 "-intermediateFolder", intermediateFolder,
65 "-o", exePath,
66 });
67
68 result.AssertSuccess();
69
70 Assert.True(File.Exists(exePath));
71 }
72 }
73
74 private static void BuildMsiPackages(string folder, string intermediateFolder, string binFolder)
75 {
76 var result = WixRunner.Execute(new[]
77 {
78 "build",
79 Path.Combine(folder, "MsiTransaction", "FirstX86.wxs"),
80 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
81 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
82 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
83 "-intermediateFolder", intermediateFolder,
84 "-o", Path.Combine(binFolder, "FirstX86", "FirstX86.msi"),
85 });
86
87 result.AssertSuccess();
88
89 result = WixRunner.Execute(new[]
90 {
91 "build",
92 Path.Combine(folder, "MsiTransaction", "SecondX86.wxs"),
93 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
94 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
95 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
96 "-intermediateFolder", intermediateFolder,
97 "-o", Path.Combine(binFolder, "SecondX86", "SecondX86.msi"),
98 });
99
100 result.AssertSuccess();
101
102 result = WixRunner.Execute(new[]
103 {
104 "build",
105 Path.Combine(folder, "MsiTransaction", "FirstX64.wxs"),
106 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
107 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
108 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
109 "-intermediateFolder", intermediateFolder,
110 "-arch", "x64",
111 "-o", Path.Combine(binFolder, "FirstX64", "FirstX64.msi"),
112 });
113
114 result.AssertSuccess();
115
116 result = WixRunner.Execute(new[]
117 {
118 "build",
119 Path.Combine(folder, "MsiTransaction", "SecondX64.wxs"),
120 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
121 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
122 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
123 "-intermediateFolder", intermediateFolder,
124 "-arch", "x64",
125 "-o", Path.Combine(binFolder, "SecondX64", "SecondX64.msi"),
126 });
127
128 result.AssertSuccess();
129 }
130 }
131}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System.IO;
6 using WixBuildTools.TestSupport;
7 using WixToolset.Core.TestPackage;
8 using Xunit;
9
10 public class MsuPackageFixture
11 {
12 [Fact]
13 public void CanBuildBundleWithMsuPackage()
14 {
15 var folder = TestData.Get(@"TestData", "MsuPackage");
16
17 using (var fs = new DisposableFileSystem())
18 {
19 var baseFolder = fs.GetFolder();
20 var intermediateFolder = Path.Combine(baseFolder, "obj");
21
22 var result = WixRunner.Execute(new[]
23 {
24 "build",
25 Path.Combine(folder, "Bundle.wxs"),
26 "-bindpath", Path.Combine(folder, "data"),
27 "-intermediateFolder", intermediateFolder,
28 "-o", Path.Combine(baseFolder, "bin", "test.exe")
29 });
30
31 result.AssertSuccess();
32 Assert.True(File.Exists(Path.Combine(baseFolder, "bin", "test.exe")));
33 }
34 }
35 }
36}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System.Collections.Generic;
6 using System.IO;
7 using System.Linq;
8 using WixBuildTools.TestSupport;
9 using WixToolset.Core.TestPackage;
10 using Xunit;
11
12 public class PackagePayloadFixture
13 {
14 [Fact]
15 public void CanSpecifyPackagePayloadInPayloadGroup()
16 {
17 var folder = TestData.Get(@"TestData");
18
19 using (var fs = new DisposableFileSystem())
20 {
21 var baseFolder = fs.GetFolder();
22 var intermediateFolder = Path.Combine(baseFolder, "obj");
23 var bundlePath = Path.Combine(baseFolder, @"bin\test.exe");
24 var baFolderPath = Path.Combine(baseFolder, "ba");
25 var extractFolderPath = Path.Combine(baseFolder, "extract");
26
27 var result = WixRunner.Execute(new[]
28 {
29 "build",
30 Path.Combine(folder, "PackagePayload", "PackagePayloadInPayloadGroup.wxs"),
31 Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"),
32 "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
33 "-bindpath", Path.Combine(folder, ".Data"),
34 "-intermediateFolder", intermediateFolder,
35 "-o", bundlePath,
36 });
37
38 result.AssertSuccess();
39
40 Assert.True(File.Exists(bundlePath));
41
42 var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath);
43 extractResult.AssertSuccess();
44
45 var exePackageElements = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Chain/burn:ExePackage");
46 var ignoreAttributesByElementName = new Dictionary<string, List<string>>
47 {
48 { "ExePackage", new List<string> { "CacheId", "InstallSize", "Size" } },
49 };
50 Assert.Equal(1, exePackageElements.Count);
51 Assert.Equal("<ExePackage Id='PackagePayloadInPayloadGroup' Cache='keep' CacheId='*' InstallSize='*' Size='*' PerMachine='yes' Permanent='yes' Vital='yes' RollbackBoundaryForward='WixDefaultBoundary' RollbackBoundaryBackward='WixDefaultBoundary' LogPathVariable='WixBundleLog_PackagePayloadInPayloadGroup' RollbackLogPathVariable='WixBundleRollbackLog_PackagePayloadInPayloadGroup' DetectCondition='none' InstallArguments='' UninstallArguments='' RepairArguments='' Repairable='no'><PayloadRef Id='burn.exe' /></ExePackage>", exePackageElements[0].GetTestXml(ignoreAttributesByElementName));
52
53 var payloadElements = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload[@Id='burn.exe']");
54 Assert.Equal(1, payloadElements.Count);
55 Assert.Equal("<Payload Id='burn.exe' FilePath='burn.exe' FileSize='463360' Hash='F6E722518AC3AB7E31C70099368D5770788C179AA23226110DCF07319B1E1964E246A1E8AE72E2CF23E0138AFC281BAFDE45969204405E114EB20C8195DA7E5E' Packaging='embedded' SourcePath='a0' Container='WixAttachedContainer' />", payloadElements[0].GetTestXml());
56 }
57 }
58
59 [Fact]
60 public void ErrorWhenMissingSourceFileAndHash()
61 {
62 var folder = TestData.Get(@"TestData", "PackagePayload");
63
64 using (var fs = new DisposableFileSystem())
65 {
66 var baseFolder = fs.GetFolder();
67
68 var result = WixRunner.Execute(false, new[]
69 {
70 "build",
71 Path.Combine(folder, "MissingSourceFileAndHash.wxs"),
72 "-o", Path.Combine(baseFolder, "test.wixlib")
73 });
74
75 Assert.Equal(44, result.ExitCode);
76 WixAssert.CompareLineByLine(new[]
77 {
78 "The MsuPackagePayload element's SourceFile or Hash attribute was not found; one of these is required.",
79 }, result.Messages.Select(m => m.ToString()).ToArray());
80 }
81 }
82
83 [Fact]
84 public void ErrorWhenMissingSourceFileAndName()
85 {
86 var folder = TestData.Get(@"TestData", "PackagePayload");
87
88 using (var fs = new DisposableFileSystem())
89 {
90 var baseFolder = fs.GetFolder();
91
92 var result = WixRunner.Execute(false, new[]
93 {
94 "build",
95 Path.Combine(folder, "MissingSourceFileAndName.wxs"),
96 "-o", Path.Combine(baseFolder, "test.wixlib")
97 });
98
99 Assert.Equal(44, result.ExitCode);
100 WixAssert.CompareLineByLine(new[]
101 {
102 "The MsiPackagePayload element's Name or SourceFile attribute was not found; one of these is required.",
103 }, result.Messages.Select(m => m.ToString()).ToArray());
104 }
105 }
106
107 [Fact]
108 public void ErrorWhenSpecifiedHash()
109 {
110 var folder = TestData.Get(@"TestData", "PackagePayload");
111
112 using (var fs = new DisposableFileSystem())
113 {
114 var baseFolder = fs.GetFolder();
115
116 var result = WixRunner.Execute(new[]
117 {
118 "build",
119 Path.Combine(folder, "SpecifiedHash.wxs"),
120 "-o", Path.Combine(baseFolder, "test.wixlib")
121 });
122
123 Assert.Equal(4, result.ExitCode);
124 WixAssert.CompareLineByLine(new[]
125 {
126 "The MspPackagePayload element contains an unexpected attribute 'Hash'.",
127 }, result.Messages.Select(m => m.ToString()).ToArray());
128 }
129 }
130
131 [Fact]
132 public void ErrorWhenSpecifiedHashAndMissingDownloadUrl()
133 {
134 var folder = TestData.Get(@"TestData", "PackagePayload");
135
136 using (var fs = new DisposableFileSystem())
137 {
138 var baseFolder = fs.GetFolder();
139
140 var result = WixRunner.Execute(new[]
141 {
142 "build",
143 Path.Combine(folder, "SpecifiedHashAndMissingDownloadUrl.wxs"),
144 "-o", Path.Combine(baseFolder, "test.wixlib")
145 });
146
147 Assert.Equal(10, result.ExitCode);
148 WixAssert.CompareLineByLine(new[]
149 {
150 "The MsuPackagePayload/@DownloadUrl attribute was not found; it is required when attribute Hash is specified.",
151 }, result.Messages.Select(m => m.ToString()).ToArray());
152 }
153 }
154
155 [Fact]
156 public void ErrorWhenSpecifiedSourceFileAndHash()
157 {
158 var folder = TestData.Get(@"TestData", "PackagePayload");
159
160 using (var fs = new DisposableFileSystem())
161 {
162 var baseFolder = fs.GetFolder();
163
164 var result = WixRunner.Execute(new[]
165 {
166 "build",
167 Path.Combine(folder, "SpecifiedSourceFileAndHash.wxs"),
168 "-o", Path.Combine(baseFolder, "test.wixlib")
169 });
170
171 Assert.Equal(35, result.ExitCode);
172 WixAssert.CompareLineByLine(new[]
173 {
174 "The ExePackagePayload/@Hash attribute cannot be specified when attribute SourceFile is present.",
175 }, result.Messages.Select(m => m.ToString()).ToArray());
176 }
177 }
178
179 [Fact]
180 public void ErrorWhenWrongPackagePayloadInPayloadGroup()
181 {
182 var folder = TestData.Get(@"TestData");
183
184 using (var fs = new DisposableFileSystem())
185 {
186 var baseFolder = fs.GetFolder();
187 var intermediateFolder = Path.Combine(baseFolder, "obj");
188 var bundlePath = Path.Combine(baseFolder, @"bin\test.exe");
189
190 var result = WixRunner.Execute(new[]
191 {
192 "build",
193 Path.Combine(folder, "PackagePayload", "WrongPackagePayloadInPayloadGroup.wxs"),
194 Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"),
195 "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
196 "-bindpath", Path.Combine(folder, ".Data"),
197 "-intermediateFolder", intermediateFolder,
198 "-o", bundlePath,
199 });
200
201 Assert.Equal(407, result.ExitCode);
202 WixAssert.CompareLineByLine(new[]
203 {
204 "The ExePackagePayload element can only be used for ExePackages.",
205 "The location of the package related to previous error.",
206 "There is no payload defined for package 'WrongPackagePayloadInPayloadGroup'. This is specified on the MsiPackage element or a child MsiPackagePayload element.",
207 }, result.Messages.Select(m => m.ToString()).ToArray());
208 }
209 }
210 }
211}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System.Linq;
6 using WixToolset.Core;
7 using WixToolset.Data;
8 using WixToolset.Data.Symbols;
9 using WixToolset.Extensibility.Data;
10 using WixToolset.Extensibility.Services;
11 using Xunit;
12
13 public class ParseFixture
14 {
15 [Fact]
16 public void GeneratesCorrectCustomActionIdentifiers()
17 {
18 var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider();
19 var section = new IntermediateSection("section", SectionType.Fragment);
20 var parseHelper = serviceProvider.GetService<IParseHelper>();
21
22 parseHelper.CreateCustomActionReference(null, section, "CustomAction32", Platform.X86, CustomActionPlatforms.X86);
23 parseHelper.CreateCustomActionReference(null, section, "CustomArmAction", Platform.ARM64, CustomActionPlatforms.X86);
24 parseHelper.CreateCustomActionReference(null, section, "CustomArmAction", Platform.ARM64, CustomActionPlatforms.X86 | CustomActionPlatforms.X64 | CustomActionPlatforms.ARM64);
25 parseHelper.CreateCustomActionReference(null, section, "CustomAction", Platform.X64, CustomActionPlatforms.X86);
26 parseHelper.CreateCustomActionReference(null, section, "CustomAction", Platform.X64, CustomActionPlatforms.X86 | CustomActionPlatforms.X64);
27
28 var simpleReferences = section.Symbols.OfType<WixSimpleReferenceSymbol>();
29 Assert.NotNull(simpleReferences.Where(t => t.SymbolicName == "CustomAction:CustomAction32_X86").FirstOrDefault());
30 Assert.NotNull(simpleReferences.Where(t => t.SymbolicName == "CustomAction:CustomArmAction_X86").FirstOrDefault());
31 Assert.NotNull(simpleReferences.Where(t => t.SymbolicName == "CustomAction:CustomArmAction_A64").FirstOrDefault());
32 Assert.NotNull(simpleReferences.Where(t => t.SymbolicName == "CustomAction:CustomAction_X86").FirstOrDefault());
33 Assert.NotNull(simpleReferences.Where(t => t.SymbolicName == "CustomAction:CustomAction_X64").FirstOrDefault());
34 }
35 }
36}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System;
6 using System.Collections.Generic;
7 using System.ComponentModel;
8 using System.IO;
9 using System.Linq;
10 using System.Runtime.InteropServices;
11 using System.Text;
12 using System.Xml;
13 using System.Xml.Linq;
14 using Example.Extension;
15 using WixBuildTools.TestSupport;
16 using WixToolset.Core.TestPackage;
17 using WixToolset.Data;
18 using WixToolset.Data.Burn;
19 using Xunit;
20
21 public class PatchFixture
22 {
23 private static readonly XNamespace PatchNamespace = "http://www.microsoft.com/msi/patch_applicability.xsd";
24
25 [Fact]
26 public void CanBuildSimplePatch()
27 {
28 var folder = TestData.Get(@"TestData\PatchSingle");
29
30 using (var fs = new DisposableFileSystem())
31 {
32 var tempFolder = fs.GetFolder();
33
34 var baselinePdb = BuildMsi("Baseline.msi", folder, tempFolder, "1.0.0", "1.0.0", "1.0.0");
35 var update1Pdb = BuildMsi("Update.msi", folder, tempFolder, "1.0.1", "1.0.1", "1.0.1");
36 var patchPdb = BuildMsp("Patch1.msp", folder, tempFolder, "1.0.1");
37 var patchPath = Path.ChangeExtension(patchPdb, ".msp");
38
39 Assert.True(File.Exists(baselinePdb));
40 Assert.True(File.Exists(update1Pdb));
41
42 var doc = GetExtractPatchXml(patchPath);
43 Assert.Equal("{7D326855-E790-4A94-8611-5351F8321FCA}", doc.Root.Element(PatchNamespace + "TargetProductCode").Value);
44
45 var names = Query.GetSubStorageNames(patchPath);
46 Assert.Equal(new[] { "#RTM.1", "RTM.1" }, names);
47
48 var cab = Path.Combine(tempFolder, "foo.cab");
49 Query.ExtractStream(patchPath, "foo.cab", cab);
50 Assert.True(File.Exists(cab));
51
52 var files = Query.GetCabinetFiles(cab);
53 Assert.Equal(new[] { "a.txt", "b.txt" }, files.Select(f => f.Name).ToArray());
54 }
55 }
56
57 [Fact]
58 public void CanBuildSimplePatchWithNoFileChanges()
59 {
60 var folder = TestData.Get(@"TestData\PatchNoFileChanges");
61
62 using (var fs = new DisposableFileSystem())
63 {
64 var tempFolder = fs.GetFolder();
65
66 var baselinePdb = BuildMsi("Baseline.msi", folder, tempFolder, "1.0.0", "1.0.0", "1.0.0");
67 var update1Pdb = BuildMsi("Update.msi", folder, tempFolder, "1.0.1", "1.0.1", "1.0.1");
68 var patchPdb = BuildMsp("Patch1.msp", folder, tempFolder, "1.0.1", hasNoFiles: true);
69 var patchPath = Path.ChangeExtension(patchPdb, ".msp");
70
71 Assert.True(File.Exists(baselinePdb));
72 Assert.True(File.Exists(update1Pdb));
73
74 var doc = GetExtractPatchXml(patchPath);
75 Assert.Equal("{7D326855-E790-4A94-8611-5351F8321FCA}", doc.Root.Element(PatchNamespace + "TargetProductCode").Value);
76
77 var names = Query.GetSubStorageNames(patchPath);
78 Assert.Equal(new[] { "#RTM.1", "RTM.1" }, names);
79
80 var cab = Path.Combine(tempFolder, "foo.cab");
81 Query.ExtractStream(patchPath, "foo.cab", cab);
82 Assert.True(File.Exists(cab));
83
84 var files = Query.GetCabinetFiles(cab);
85 Assert.Empty(files);
86 }
87 }
88
89 [Fact(Skip = "https://github.com/wixtoolset/issues/issues/6387")]
90 public void CanBuildPatchFromProductWithFilesFromWixlib()
91 {
92 var folder = TestData.Get(@"TestData\PatchFromWixlib");
93
94 using (var fs = new DisposableFileSystem())
95 {
96 var tempFolderBaseline = fs.GetFolder();
97 var tempFolderUpdate = fs.GetFolder();
98 var tempFolderPatch = fs.GetFolder();
99
100 var baselinePdb = BuildMsi("Baseline.msi", folder, tempFolderBaseline, "1.0.0", "1.0.0", "1.0.0");
101 var update1Pdb = BuildMsi("Update.msi", folder, tempFolderUpdate, "1.0.1", "1.0.1", "1.0.1");
102 var patchPdb = BuildMsp("Patch1.msp", folder, tempFolderPatch, "1.0.1", bindpaths: new[] { Path.GetDirectoryName(baselinePdb), Path.GetDirectoryName(update1Pdb) }, hasNoFiles: true);
103 var patchPath = Path.ChangeExtension(patchPdb, ".msp");
104
105 Assert.True(File.Exists(baselinePdb));
106 Assert.True(File.Exists(update1Pdb));
107 }
108 }
109
110 [Fact]
111 public void CanBuildBundleWithNonSpecificPatches()
112 {
113 var folder = TestData.Get(@"TestData\PatchNonSpecific");
114
115 using (var fs = new DisposableFileSystem())
116 {
117 var tempFolder = fs.GetFolder();
118
119 var baselinePdb = BuildMsi("Baseline.msi", Path.Combine(folder, "PackageA"), tempFolder, "1.0.0", "A", "B");
120 var updatePdb = BuildMsi("Update.msi", Path.Combine(folder, "PackageA"), tempFolder, "1.0.1", "A", "B");
121 var patchAPdb = BuildMsp("PatchA.msp", Path.Combine(folder, "PatchA"), tempFolder, "1.0.1", hasNoFiles: true);
122 var patchBPdb = BuildMsp("PatchB.msp", Path.Combine(folder, "PatchB"), tempFolder, "1.0.1", hasNoFiles: true);
123 var patchCPdb = BuildMsp("PatchC.msp", Path.Combine(folder, "PatchC"), tempFolder, "1.0.1", hasNoFiles: true);
124 var bundleAPdb = BuildBundle("BundleA.exe", Path.Combine(folder, "BundleA"), tempFolder);
125 var bundleBPdb = BuildBundle("BundleB.exe", Path.Combine(folder, "BundleB"), tempFolder);
126 var bundleCPdb = BuildBundle("BundleC.exe", Path.Combine(folder, "BundleC"), tempFolder);
127
128 VerifyPatchTargetCodes(bundleAPdb, new[]
129 {
130 "<PatchTargetCode TargetCode='{26309973-0A5E-4979-B142-98A6E064EDC0}' Product='yes' />",
131 });
132 VerifyPatchTargetCodes(bundleBPdb, new[]
133 {
134 "<PatchTargetCode TargetCode='{26309973-0A5E-4979-B142-98A6E064EDC0}' Product='yes' />",
135 "<PatchTargetCode TargetCode='{32B0396A-CE36-4570-B16E-F88FA42DC409}' Product='no' />",
136 });
137 VerifyPatchTargetCodes(bundleCPdb, new string[0]);
138 }
139 }
140
141 [Fact]
142 public void CanBuildBundleWithSlipstreamPatch()
143 {
144 var folder = TestData.Get(@"TestData\PatchSingle");
145
146 using (var fs = new DisposableFileSystem())
147 {
148 var tempFolder = fs.GetFolder();
149
150 var baselinePdb = BuildMsi("Baseline.msi", folder, tempFolder, "1.0.0", "1.0.0", "1.0.0");
151 var update1Pdb = BuildMsi("Update.msi", folder, tempFolder, "1.0.1", "1.0.1", "1.0.1");
152 var patchPdb = BuildMsp("Patch1.msp", folder, tempFolder, "1.0.1");
153 var bundleAPdb = BuildBundle("BundleA.exe", Path.Combine(folder, "BundleA"), tempFolder);
154
155 using (var wixOutput = WixOutput.Read(bundleAPdb))
156 {
157 var manifestData = wixOutput.GetData(BurnConstants.BurnManifestWixOutputStreamName);
158 var doc = new XmlDocument();
159 doc.LoadXml(manifestData);
160 var nsmgr = BundleExtractor.GetBurnNamespaceManager(doc, "w");
161 var slipstreamMspNodes = doc.SelectNodes("/w:BurnManifest/w:Chain/w:MsiPackage/w:SlipstreamMsp", nsmgr);
162 Assert.Equal(1, slipstreamMspNodes.Count);
163 Assert.Equal("<SlipstreamMsp Id='PatchA' />", slipstreamMspNodes[0].GetTestXml());
164 }
165 }
166 }
167
168 private static void VerifyPatchTargetCodes(string pdbPath, string[] expected)
169 {
170 using (var wixOutput = WixOutput.Read(pdbPath))
171 {
172 var manifestData = wixOutput.GetData(BurnConstants.BurnManifestWixOutputStreamName);
173 var doc = new XmlDocument();
174 doc.LoadXml(manifestData);
175 var nsmgr = BundleExtractor.GetBurnNamespaceManager(doc, "w");
176 var patchTargetCodes = doc.SelectNodes("/w:BurnManifest/w:PatchTargetCode", nsmgr);
177
178 var actual = new List<string>();
179 foreach (XmlNode patchTargetCodeNode in patchTargetCodes)
180 {
181 actual.Add(patchTargetCodeNode.GetTestXml());
182 }
183
184 WixAssert.CompareLineByLine(expected, actual.ToArray());
185 }
186 }
187
188 private static string BuildMsi(string outputName, string sourceFolder, string baseFolder, string defineV, string defineA, string defineB)
189 {
190 var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath);
191 var outputPath = Path.Combine(baseFolder, Path.Combine("bin", outputName));
192
193 var result = WixRunner.Execute(new[]
194 {
195 "build",
196 Path.Combine(sourceFolder, @"Package.wxs"),
197 "-d", "V=" + defineV,
198 "-d", "A=" + defineA,
199 "-d", "B=" + defineB,
200 "-bindpath", Path.Combine(sourceFolder, ".data"),
201 "-intermediateFolder", Path.Combine(baseFolder, "obj"),
202 "-o", outputPath,
203 "-ext", extensionPath,
204 });
205
206 result.AssertSuccess();
207
208 return Path.ChangeExtension(outputPath, ".wixpdb");
209 }
210
211 private static string BuildMsp(string outputName, string sourceFolder, string baseFolder, string defineV, IEnumerable<string> bindpaths = null, bool hasNoFiles = false)
212 {
213 var outputPath = Path.Combine(baseFolder, Path.Combine("bin", outputName));
214
215 var args = new List<string>
216 {
217 "build",
218 hasNoFiles ? "-sw1079" : " ",
219 Path.Combine(sourceFolder, @"Patch.wxs"),
220 "-d", "V=" + defineV,
221 "-bindpath", Path.Combine(baseFolder, "bin"),
222 "-intermediateFolder", Path.Combine(baseFolder, "obj"),
223 "-o", outputPath
224 };
225
226 foreach (var additionaBindPath in bindpaths ?? Enumerable.Empty<string>())
227 {
228 args.Add("-bindpath");
229 args.Add(additionaBindPath);
230 }
231
232 var result = WixRunner.Execute(args.ToArray());
233
234 result.AssertSuccess();
235
236 return Path.ChangeExtension(outputPath, ".wixpdb");
237 }
238
239 private static string BuildBundle(string outputName, string sourceFolder, string baseFolder)
240 {
241 var outputPath = Path.Combine(baseFolder, Path.Combine("bin", outputName));
242
243 var result = WixRunner.Execute(new[]
244 {
245 "build",
246 Path.Combine(sourceFolder, @"Bundle.wxs"),
247 Path.Combine(sourceFolder, "..", "..", "BundleWithPackageGroupRef", "Bundle.wxs"),
248 "-bindpath", Path.Combine(sourceFolder, "..", "..", "SimpleBundle", "data"),
249 "-bindpath", Path.Combine(baseFolder, "bin"),
250 "-intermediateFolder", Path.Combine(baseFolder, "obj"),
251 "-o", outputPath
252 });
253
254 result.AssertSuccess();
255
256 return Path.ChangeExtension(outputPath, ".wixpdb");
257 }
258
259 private static XDocument GetExtractPatchXml(string path)
260 {
261 var buffer = new StringBuilder(65535);
262 var size = buffer.Capacity;
263
264 var er = MsiExtractPatchXMLData(path, 0, buffer, ref size);
265 if (er != 0)
266 {
267 throw new Win32Exception(er);
268 }
269
270 return XDocument.Parse(buffer.ToString());
271 }
272
273 [DllImport("msi.dll", EntryPoint = "MsiExtractPatchXMLDataW", CharSet = CharSet.Unicode, ExactSpelling = true)]
274 private static extern int MsiExtractPatchXMLData(string szPatchPath, int dwReserved, StringBuilder szXMLData, ref int pcchXMLData);
275
276 [DllImport("msi.dll", EntryPoint = "MsiApplyPatchW", CharSet = CharSet.Unicode, ExactSpelling = true)]
277 private static extern int MsiApplyPatch(string szPatchPackage, string szInstallPackage, int eInstallType, string szCommandLine);
278 }
279}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 using System.Xml;
10 using WixBuildTools.TestSupport;
11 using WixToolset.Core;
12 using WixToolset.Core.TestPackage;
13 using WixToolset.Data;
14 using WixToolset.Data.Symbols;
15 using Xunit;
16
17 public class PayloadFixture
18 {
19 [Fact]
20 public void CanParseValidName()
21 {
22 var folder = TestData.Get(@"TestData\Payload");
23
24 using (var fs = new DisposableFileSystem())
25 {
26 var baseFolder = fs.GetFolder();
27 var intermediateFolder = Path.Combine(baseFolder, "obj");
28 var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib");
29
30 var result = WixRunner.Execute(new[]
31 {
32 "build",
33 Path.Combine(folder, "ValidName.wxs"),
34 "-intermediateFolder", intermediateFolder,
35 "-o", wixlibPath,
36 });
37
38 result.AssertSuccess();
39
40 Assert.Empty(result.Messages);
41
42 var intermediate = Intermediate.Load(wixlibPath);
43 var allSymbols = intermediate.Sections.SelectMany(s => s.Symbols);
44 var payloadSymbol = allSymbols.OfType<WixBundlePayloadSymbol>()
45 .SingleOrDefault();
46 Assert.NotNull(payloadSymbol);
47
48 var fields = payloadSymbol.Fields.Select(field => field?.Type == IntermediateFieldType.Bool
49 ? field.AsNullableNumber()?.ToString()
50 : field?.AsString())
51 .ToList();
52 Assert.Equal(@"dir\file.ext", fields[(int)WixBundlePayloadSymbolFields.Name]);
53 }
54 }
55
56 [Fact]
57 public void CanCanonicalizeName()
58 {
59 var folder = TestData.Get(@"TestData\Payload");
60
61 using (var fs = new DisposableFileSystem())
62 {
63 var baseFolder = fs.GetFolder();
64 var intermediateFolder = Path.Combine(baseFolder, "obj");
65 var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib");
66
67 var result = WixRunner.Execute(warningsAsErrors: false, new[]
68 {
69 "build",
70 Path.Combine(folder, "CanonicalizeName.wxs"),
71 "-intermediateFolder", intermediateFolder,
72 "-o", wixlibPath,
73 });
74
75 result.AssertSuccess();
76
77 Assert.Single(result.Messages, m => m.Id == (int)WarningMessages.Ids.PathCanonicalized);
78
79 var intermediate = Intermediate.Load(wixlibPath);
80 var allSymbols = intermediate.Sections.SelectMany(s => s.Symbols);
81 var payloadSymbol = allSymbols.OfType<WixBundlePayloadSymbol>()
82 .SingleOrDefault();
83 Assert.NotNull(payloadSymbol);
84
85 var fields = payloadSymbol.Fields.Select(field => field?.Type == IntermediateFieldType.Bool
86 ? field.AsNullableNumber()?.ToString()
87 : field?.AsString())
88 .ToList();
89 Assert.Equal(@"c\d.exe", fields[(int)WixBundlePayloadSymbolFields.Name]);
90 }
91 }
92
93 [Fact]
94 public void RejectsAbsoluteName()
95 {
96 var folder = TestData.Get(@"TestData\Payload");
97
98 using (var fs = new DisposableFileSystem())
99 {
100 var baseFolder = fs.GetFolder();
101 var intermediateFolder = Path.Combine(baseFolder, "obj");
102 var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib");
103
104 var result = WixRunner.Execute(new[]
105 {
106 "build",
107 Path.Combine(folder, "AbsoluteName.wxs"),
108 "-intermediateFolder", intermediateFolder,
109 "-o", wixlibPath,
110 });
111
112 Assert.InRange(result.ExitCode, 2, int.MaxValue);
113
114 var expectedIllegalRelativeLongFileName = 1;
115 var expectedPayloadMustBeRelativeToCache = 2;
116 Assert.Equal(expectedIllegalRelativeLongFileName, result.Messages.Where(m => m.Id == (int)ErrorMessages.Ids.IllegalRelativeLongFilename).Count());
117 Assert.Equal(expectedPayloadMustBeRelativeToCache, result.Messages.Where(m => m.Id == (int)ErrorMessages.Ids.PayloadMustBeRelativeToCache).Count());
118 }
119 }
120
121 [Fact]
122 public void RejectsPayloadSharedBetweenPackageAndBA()
123 {
124 var folder = TestData.Get(@"TestData");
125
126 using (var fs = new DisposableFileSystem())
127 {
128 var baseFolder = fs.GetFolder();
129 var intermediateFolder = Path.Combine(baseFolder, "obj");
130 var bundlePath = Path.Combine(baseFolder, @"bin\test.exe");
131
132 var result = WixRunner.Execute(new[]
133 {
134 "build",
135 Path.Combine(folder, "Payload", "SharedBAAndPackagePayloadBundle.wxs"),
136 Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"),
137 "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
138 "-bindpath", Path.Combine(folder, ".Data"),
139 "-intermediateFolder", intermediateFolder,
140 "-o", bundlePath,
141 });
142
143 Assert.Equal((int)LinkerErrors.Ids.PayloadSharedWithBA, result.ExitCode);
144 }
145 }
146
147 [Fact]
148 public void ReplacesDownloadUrlPlaceholders()
149 {
150 var folder = TestData.Get(@"TestData");
151
152 using (var fs = new DisposableFileSystem())
153 {
154 var baseFolder = fs.GetFolder();
155 var intermediateFolder = Path.Combine(baseFolder, "obj");
156 var bundlePath = Path.Combine(baseFolder, @"bin\test.exe");
157 var baFolderPath = Path.Combine(baseFolder, "ba");
158 var extractFolderPath = Path.Combine(baseFolder, "extract");
159
160 var result = WixRunner.Execute(false, new[]
161 {
162 "build",
163 Path.Combine(folder, "Payload", "DownloadUrlPlaceholdersBundle.wxs"),
164 Path.Combine(folder, "SimpleBundle", "MultiFileBootstrapperApplication.wxs"),
165 "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
166 "-bindpath", Path.Combine(folder, ".Data"),
167 "-intermediateFolder", intermediateFolder,
168 "-o", bundlePath,
169 });
170
171 result.AssertSuccess();
172
173 WixAssert.CompareLineByLine(new string[]
174 {
175 "The Payload 'burn.exe' is being added to Container 'PackagesContainer', overriding its Compressed value of 'no'.",
176 }, result.Messages.Select(m => m.ToString()).ToArray());
177
178 Assert.True(File.Exists(bundlePath));
179
180 var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath);
181 extractResult.AssertSuccess();
182
183 var ignoreAttributesByElementName = new Dictionary<string, List<string>>
184 {
185 { "Container", new List<string> { "FileSize", "Hash" } },
186 { "Payload", new List<string> { "FileSize", "Hash" } },
187 };
188 var payloads = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Payload")
189 .Cast<XmlElement>()
190 .Select(e => e.GetTestXml(ignoreAttributesByElementName))
191 .ToArray();
192 WixAssert.CompareLineByLine(new string[]
193 {
194 "<Payload Id='burn.exe' FilePath='burn.exe' FileSize='*' Hash='*' Packaging='embedded' SourcePath='a0' Container='PackagesContainer' />",
195 "<Payload Id='test.msi' FilePath='test.msi' FileSize='*' Hash='*' DownloadUrl='http://example.com/id/test.msi/test.msi' Packaging='external' SourcePath='test.msi' />",
196 "<Payload Id='LayoutOnlyPayload' FilePath='DownloadUrlPlaceholdersBundle.wxs' FileSize='*' Hash='*' LayoutOnly='yes' DownloadUrl='http://example.com/id/LayoutOnlyPayload/DownloadUrlPlaceholdersBundle.wxs' Packaging='external' SourcePath='DownloadUrlPlaceholdersBundle.wxs' />",
197 @"<Payload Id='fhuZsOcBDTuIX8rF96kswqI6SnuI' FilePath='MsiPackage\test.txt' FileSize='*' Hash='*' Packaging='external' SourcePath='MsiPackage\test.txt' />",
198 @"<Payload Id='faf_OZ741BG7SJ6ZkcIvivZ2Yzo8' FilePath='MsiPackage\Shared.dll' FileSize='*' Hash='*' Packaging='external' SourcePath='MsiPackage\Shared.dll' />",
199 }, payloads);
200
201 var containers = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Container")
202 .Cast<XmlElement>()
203 .Select(e => e.GetTestXml(ignoreAttributesByElementName))
204 .ToArray();
205 WixAssert.CompareLineByLine(new string[]
206 {
207 "<Container Id='PackagesContainer' FileSize='*' Hash='*' DownloadUrl='http://example.com/id/PackagesContainer/packages.cab' FilePath='packages.cab' />",
208 }, containers);
209 }
210 }
211 }
212}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System.IO;
6 using System.Linq;
7 using WixBuildTools.TestSupport;
8 using WixToolset.Core;
9 using WixToolset.Core.TestPackage;
10 using WixToolset.Data;
11 using WixToolset.Data.Symbols;
12 using WixToolset.Extensibility.Data;
13 using WixToolset.Extensibility.Services;
14 using Xunit;
15
16 public class PreprocessorFixture
17 {
18 [Fact]
19 public void PreprocessDirectly()
20 {
21 var folder = TestData.Get(@"TestData\IncludePath");
22 var sourcePath = Path.Combine(folder, "Package.wxs");
23 var includeFolder = Path.Combine(folder, "data");
24 var includeFile = Path.Combine(includeFolder, "Package.wxi");
25
26 var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider();
27
28 var context = serviceProvider.GetService<IPreprocessContext>();
29 context.SourcePath = sourcePath;
30 context.IncludeSearchPaths = new[] { includeFolder };
31
32 var preprocessor = serviceProvider.GetService<IPreprocessor>();
33 var result = preprocessor.Preprocess(context);
34
35 var includedFile = result.IncludedFiles.Single();
36 Assert.NotNull(result.Document);
37 Assert.Equal(includeFile, includedFile.Path);
38 Assert.Equal(sourcePath, includedFile.SourceLineNumbers.FileName);
39 Assert.Equal(1, includedFile.SourceLineNumbers.LineNumber.Value);
40 Assert.Equal($"{sourcePath}*1", includedFile.SourceLineNumbers.QualifiedFileName);
41 Assert.Null(includedFile.SourceLineNumbers.Parent);
42 }
43
44 [Fact]
45 public void IncludeSourceLineNumbersPreserved()
46 {
47 var folder = TestData.Get(@"TestData\IncludePath");
48
49 using (var fs = new DisposableFileSystem())
50 {
51 var baseFolder = fs.GetFolder();
52 var intermediateFolder = Path.Combine(baseFolder, "obj");
53
54 var result = WixRunner.Execute(warningsAsErrors: false, new[]
55 {
56 "build",
57 Path.Combine(folder, "Package.wxs"),
58 Path.Combine(folder, "PackageComponents.wxs"),
59 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
60 "-includepath", Path.Combine(folder, "data"),
61 "-bindpath", Path.Combine(folder, "data"),
62 "-intermediateFolder", intermediateFolder,
63 "-o", Path.Combine(baseFolder, @"bin\test.msi")
64 });
65
66 result.AssertSuccess();
67
68 using (var output = WixOutput.Read(Path.Combine(baseFolder, @"bin\test.wixpdb")))
69 {
70 var intermediate = Intermediate.Load(output);
71 var component = intermediate.Sections.Single().Symbols.OfType<ComponentSymbol>().Single();
72 Assert.Equal(3, component.SourceLineNumbers.LineNumber);
73 Assert.Equal(5, component.SourceLineNumbers.Parent.LineNumber);
74
75 var encoded = component.SourceLineNumbers.GetEncoded();
76 var decoded = SourceLineNumber.CreateFromEncoded(encoded);
77 Assert.Equal(3, decoded.LineNumber);
78 Assert.Equal(5, decoded.Parent.LineNumber);
79 }
80 }
81 }
82
83 [Fact]
84 /// <remarks>
85 /// This test will fail on 32-bit operating systems because it depends on "CommonProgramFiles(x86)"
86 /// which is only defined on 64-bit Windows.
87 /// </remarks>
88 public void SupportParensInEnvironmentVariables()
89 {
90 var folder = TestData.Get(@"TestData", "Preprocessor");
91
92 var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider();
93 var context = serviceProvider.GetService<IPreprocessContext>();
94 context.SourcePath = Path.Combine(folder, "EnvParens.wxs");
95
96 var preprocessor = serviceProvider.GetService<IPreprocessor>();
97 var result = preprocessor.Preprocess(context);
98 Assert.NotNull(result.Document);
99 }
100
101 [Fact]
102 public void VariableRedefinitionIsAWarning()
103 {
104 var folder = TestData.Get(@"TestData\Variables");
105
106 using (var fs = new DisposableFileSystem())
107 {
108 var baseFolder = fs.GetFolder();
109 var intermediateFolder = Path.Combine(baseFolder, "obj");
110
111 var result = WixRunner.Execute(warningsAsErrors: false, new[]
112 {
113 "build",
114 Path.Combine(folder, "Package.wxs"),
115 Path.Combine(folder, "PackageComponents.wxs"),
116 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
117 "-bindpath", Path.Combine(folder, "data"),
118 "-intermediateFolder", intermediateFolder,
119 "-o", Path.Combine(baseFolder, @"bin\test.msi")
120 });
121
122 result.AssertSuccess();
123
124 var warning = result.Messages.Where(message => message.Id == (int)WarningMessages.Ids.VariableDeclarationCollision);
125 Assert.Single(warning);
126 }
127 }
128
129 [Fact]
130 public void ForEachLoopsWork()
131 {
132 var folder = TestData.Get(@"TestData\ForEach");
133
134 using (var fs = new DisposableFileSystem())
135 {
136 var baseFolder = fs.GetFolder();
137 var intermediateFolder = Path.Combine(baseFolder, "obj");
138
139 var result = WixRunner.Execute(new[]
140 {
141 "build",
142 Path.Combine(folder, "Package.wxs"),
143 Path.Combine(folder, "PackageComponents.wxs"),
144 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
145 "-bindpath", Path.Combine(folder, "data"),
146 "-intermediateFolder", intermediateFolder,
147 "-o", Path.Combine(baseFolder, @"bin\test.msi")
148 });
149
150 result.AssertSuccess();
151 }
152 }
153
154 [Fact]
155 public void NonterminatedPreprocessorInstructionShowsSourceLineNumber()
156 {
157 var folder = TestData.Get(@"TestData\BadIf");
158
159 using (var fs = new DisposableFileSystem())
160 {
161 var baseFolder = fs.GetFolder();
162 var intermediateFolder = Path.Combine(baseFolder, "obj");
163
164 var result = WixRunner.Execute(new[]
165 {
166 "build",
167 Path.Combine(folder, "Package.wxs"),
168 Path.Combine(folder, "PackageComponents.wxs"),
169 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
170 "-bindpath", Path.Combine(folder, "data"),
171 "-intermediateFolder", intermediateFolder,
172 "-o", Path.Combine(baseFolder, @"bin\test.msi")
173 });
174
175 Assert.Equal(147, result.ExitCode);
176 Assert.StartsWith("Found a <?if?>", result.Messages.Single().ToString());
177 }
178 }
179 }
180}
181
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 using System.Text;
10 using WixBuildTools.TestSupport;
11 using WixToolset.Core.TestPackage;
12 using WixToolset.Data;
13 using Xunit;
14
15 public class RegistryFixture
16 {
17 [Fact]
18 public void PopulatesRegistryTableFromRegistryValue()
19 {
20 var folder = TestData.Get(@"TestData");
21
22 using (var fs = new DisposableFileSystem())
23 {
24 var baseFolder = fs.GetFolder();
25 var intermediateFolder = Path.Combine(baseFolder, "obj");
26 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
27
28 var result = WixRunner.Execute(new[]
29 {
30 "build",
31 Path.Combine(folder, "Registry", "RegistryValue.wxs"),
32 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
33 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
34 "-intermediateFolder", intermediateFolder,
35 "-o", msiPath
36 });
37
38 result.AssertSuccess();
39
40 Assert.True(File.Exists(msiPath));
41 var results = Query.QueryDatabase(msiPath, new[] { "Registry" });
42 WixAssert.CompareLineByLine(new[]
43 {
44 "Registry:reg04OIwIchl.9ZTjisTT6NzGSsQSM\t2\tPath\\To\\AnotherKey\tSecret\t#x\tMiscComponent",
45 "Registry:regEblTuusqFNSUQNy88zaP_UA5kIY\t2\tPath\\To\\Key\t\t1.0.1234.123\tMiscComponent",
46 }, results);
47 }
48 }
49
50 [Fact]
51 public void PopulatesRegistryTableFromRegistryValueMultiString()
52 {
53 var folder = TestData.Get(@"TestData");
54
55 using (var fs = new DisposableFileSystem())
56 {
57 var baseFolder = fs.GetFolder();
58 var intermediateFolder = Path.Combine(baseFolder, "obj");
59 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
60
61 var result = WixRunner.Execute(new[]
62 {
63 "build",
64 Path.Combine(folder, "Registry", "RegistryValueMultiString.wxs"),
65 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
66 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
67 "-intermediateFolder", intermediateFolder,
68 "-o", msiPath
69 });
70
71 result.AssertSuccess();
72
73 var results = Query.QueryDatabase(msiPath, new[] { "Registry" });
74 WixAssert.CompareLineByLine(new[]
75 {
76 "Registry:regitq_Wx9LfvJuNSc2un6gIHAzr4A\t2\tPath\\To\\AnotherKey\tSecret\t#x\tMultiStringComponent",
77 "Registry:regmeTJMpOD41igfxhTcUVZ7kNG1Mo\t2\tPath\\To\\Key\t\ta[~]b[~][~]c[~]\tMultiStringComponent",
78 }, results);
79 }
80 }
81
82 [Fact]
83 public void DuplicateRegistryValueIdsAreDetectedSmoothly()
84 {
85 var folder = TestData.Get(@"TestData");
86
87 using (var fs = new DisposableFileSystem())
88 {
89 var baseFolder = fs.GetFolder();
90 var intermediateFolder = Path.Combine(baseFolder, "obj");
91 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
92
93 var result = WixRunner.Execute(new[]
94 {
95 "build",
96 Path.Combine(folder, "Registry", "DuplicateRegistryValueIds.wxs"),
97 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
98 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
99 "-intermediateFolder", intermediateFolder,
100 "-o", msiPath
101 }, out var messages);
102
103 Assert.Equal(2, messages.Where(m => m.Id == (int)ErrorMessages.Ids.DuplicateSymbol).Count());
104 Assert.Equal(2, messages.Where(m => m.Id == (int)ErrorMessages.Ids.DuplicateSymbol2).Count());
105 }
106 }
107
108 [Fact]
109 public void PopulatesRegistryTableFromRemoveRegistryKey()
110 {
111 var folder = TestData.Get(@"TestData");
112
113 using (var fs = new DisposableFileSystem())
114 {
115 var baseFolder = fs.GetFolder();
116 var intermediateFolder = Path.Combine(baseFolder, "obj");
117 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
118
119 var result = WixRunner.Execute(new[]
120 {
121 "build",
122 Path.Combine(folder, "Registry", "RemoveRegistryKey.wxs"),
123 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
124 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
125 "-intermediateFolder", intermediateFolder,
126 "-o", msiPath
127 });
128
129 result.AssertSuccess();
130
131 Assert.True(File.Exists(msiPath));
132 var results = Query.QueryDatabase(msiPath, new[] { "Registry" });
133 WixAssert.CompareLineByLine(new[]
134 {
135 "Registry:RemoveAKeyName\t2\tAKeyName\t-\t\tRemoveRegistryKeyComp",
136 }, results);
137 }
138 }
139
140 [Fact]
141 public void PopulatesRegistryTableWithoutExtraBackslash()
142 {
143 var folder = TestData.Get(@"TestData");
144
145 using (var fs = new DisposableFileSystem())
146 {
147 var baseFolder = fs.GetFolder();
148 var intermediateFolder = Path.Combine(baseFolder, "obj");
149 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
150
151 var result = WixRunner.Execute(new[]
152 {
153 "build",
154 Path.Combine(folder, "Registry", "RegistryKeyEndingWithBackslash.wxs"),
155 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
156 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
157 "-intermediateFolder", intermediateFolder,
158 "-o", msiPath
159 });
160
161 result.AssertSuccess();
162
163 Assert.True(File.Exists(msiPath));
164 var results = Query.QueryDatabase(msiPath, new[] { "Registry" });
165 WixAssert.CompareLineByLine(new[]
166 {
167 "Registry:reg1\t2\tSoftware\\WBM\\WB\t*\t\tMiscComponent",
168 "Registry:reg2\t2\tSoftware\\WBM\\WB\tInstallationPath\t[INSTALLFOLDER]\tMiscComponent",
169 }, results);
170 }
171 }
172 }
173}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System.IO;
6 using WixBuildTools.TestSupport;
7 using WixToolset.Core.TestPackage;
8 using Xunit;
9
10 public class RollbackBoundaryFixture
11 {
12 [Fact]
13 public void CanStartChainWithRollbackBoundary()
14 {
15 var folder = TestData.Get(@"TestData");
16
17 using (var fs = new DisposableFileSystem())
18 {
19 var baseFolder = fs.GetFolder();
20 var intermediateFolder = Path.Combine(baseFolder, "obj");
21 var exePath = Path.Combine(baseFolder, @"bin\test.exe");
22
23 var result = WixRunner.Execute(new[]
24 {
25 "build",
26 Path.Combine(folder, "RollbackBoundary", "BeginningOfChain.wxs"),
27 Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"),
28 Path.Combine(folder, "BundleWithPackageGroupRef", "MinimalPackageGroup.wxs"),
29 "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
30 "-intermediateFolder", intermediateFolder,
31 "-o", exePath,
32 });
33
34 result.AssertSuccess();
35
36 Assert.True(File.Exists(exePath));
37 }
38 }
39
40 }
41}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System.IO;
6 using WixBuildTools.TestSupport;
7 using WixToolset.Core.TestPackage;
8 using Xunit;
9
10 public class ShortcutFixture
11 {
12 [Fact]
13 public void CanBuildShortcutNameWithShortname()
14 {
15 var folder = TestData.Get(@"TestData");
16
17 using (var fs = new DisposableFileSystem())
18 {
19 var baseFolder = fs.GetFolder();
20 var intermediateFolder = Path.Combine(baseFolder, "obj");
21 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
22
23 var result = WixRunner.Execute(new[]
24 {
25 "build",
26 Path.Combine(folder, "Shortcut", "ShortcutSameNameShortName.wxs"),
27 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
28 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
29 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
30 "-intermediateFolder", intermediateFolder,
31 "-o", msiPath
32 });
33
34 result.AssertSuccess();
35
36 var results = Query.QueryDatabase(msiPath, new[] { "Shortcut" });
37 WixAssert.CompareLineByLine(new[]
38 {
39 "Shortcut:sctzJpBYlrhdx4Mm9Xh41X0KPWYiX0\tINSTALLFOLDER\tDaName\tShortcutComp\t[#filcV1yrx0x8wJWj4qMzcH21jwkPko]\t\t\t\t\t\t\t\t\t\t\t",
40 }, results);
41 }
42 }
43
44 [Fact]
45 public void PopulatesMsiShortcutPropertyTable()
46 {
47 var folder = TestData.Get(@"TestData");
48
49 using (var fs = new DisposableFileSystem())
50 {
51 var baseFolder = fs.GetFolder();
52 var intermediateFolder = Path.Combine(baseFolder, "obj");
53 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
54
55 var result = WixRunner.Execute(new[]
56 {
57 "build",
58 Path.Combine(folder, "Shortcut", "ShortcutProperty.wxs"),
59 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
60 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
61 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
62 "-intermediateFolder", intermediateFolder,
63 "-o", msiPath
64 });
65
66 result.AssertSuccess();
67
68 Assert.True(File.Exists(msiPath));
69 var results = Query.QueryDatabase(msiPath, new[] { "MsiShortcutProperty", "Shortcut" });
70 WixAssert.CompareLineByLine(new[]
71 {
72 "MsiShortcutProperty:scp4GOCIx4Eskci4nBG1MV_vSUOZt4\tTheShortcut\tCustomShortcutKey\tCustomShortcutValue",
73 "Shortcut:TheShortcut\tINSTALLFOLDER\td\tShortcutComp\t[#filcV1yrx0x8wJWj4qMzcH21jwkPko]\t\t\t\t\t\t\t\t\t\t\t",
74 }, results);
75 }
76 }
77 }
78}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System.IO;
6 using System.Linq;
7 using System.Xml.Linq;
8 using WixBuildTools.TestSupport;
9 using WixToolset.Core.TestPackage;
10 using WixToolset.Data;
11 using Xunit;
12
13 public class SoftwareTagFixture
14 {
15 private static readonly XNamespace BurnManifestNamespace = "http://wixtoolset.org/schemas/v4/2008/Burn";
16 private static readonly XNamespace SwidTagNamespace = "http://standards.iso.org/iso/19770/-2/2009/schema.xsd";
17
18 [Fact]
19 public void CanBuildPackageWithTag()
20 {
21 var folder = TestData.Get(@"TestData\ProductTag");
22 var build = new Builder(folder, null, new[] { folder });
23
24 var results = build.BuildAndQuery(Build, "File", "SoftwareIdentificationTag");
25
26 var replacePackageCodeStart = results[2].IndexOf("\tmsi:package/") + "\tmsi:package/".Length;
27 var replacePackageCodeEnd = results[2].IndexOf("\t", replacePackageCodeStart);
28 results[2] = results[2].Substring(0, replacePackageCodeStart) + "???" + results[2].Substring(replacePackageCodeEnd);
29 WixAssert.CompareLineByLine(new[]
30 {
31 "File:filF5_pLhBuF5b4N9XEo52g_hUM5Lo\tfilF5_pLhBuF5b4N9XEo52g_hUM5Lo\texample.txt\t20\t\t\t512\t1",
32 "File:tagEYRYWwOt95punO7qPPAQ9p1GBpY\ttagEYRYWwOt95punO7qPPAQ9p1GBpY\trdcfonyt.swi|~TagTestPackage.swidtag\t449\t\t\t1\t2",
33 "SoftwareIdentificationTag:tagEYRYWwOt95punO7qPPAQ9p1GBpY\twixtoolset.org\tmsi:package/???\tmsi:upgrade/047730A5-30FE-4A62-A520-DA9381B8226A\t"
34 }, results.ToArray());
35 }
36
37 [Fact]
38 public void CanBuildBundleWithTag()
39 {
40 var testDataFolder = TestData.Get(@"TestData");
41
42 using (var fs = new DisposableFileSystem())
43 {
44 var baseFolder = fs.GetFolder();
45 var intermediateFolder = Path.Combine(baseFolder, "obj");
46
47 var result = WixRunner.Execute(new[]
48 {
49 "build",
50 Path.Combine(testDataFolder, "ProductTag", "PackageWithTag.wxs"),
51 Path.Combine(testDataFolder, "ProductTag", "PackageComponents.wxs"),
52 "-loc", Path.Combine(testDataFolder, "ProductTag", "Package.en-us.wxl"),
53 "-bindpath", Path.Combine(testDataFolder, "ProductTag"),
54 "-intermediateFolder", Path.Combine(intermediateFolder, "package"),
55 "-o", Path.Combine(baseFolder, "package", @"test.msi")
56 });
57
58 result.AssertSuccess();
59
60 result = WixRunner.Execute(new[]
61 {
62 "build",
63 Path.Combine(testDataFolder, "BundleTag", "BundleWithTag.wxs"),
64 "-bindpath", Path.Combine(testDataFolder, "BundleTag"),
65 "-bindpath", Path.Combine(baseFolder, "package"),
66 "-intermediateFolder", intermediateFolder,
67 "-o", Path.Combine(baseFolder, @"bin\test.exe")
68 });
69
70 result.AssertSuccess();
71
72 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.exe")));
73 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb")));
74
75 using (var ouput = WixOutput.Read(Path.Combine(baseFolder, @"bin\test.wixpdb")))
76 {
77 var badata = ouput.GetDataStream("wix-burndata.xml");
78 var doc = XDocument.Load(badata);
79
80 var swidTag = doc.Root.Element(BurnManifestNamespace + "Registration").Element(BurnManifestNamespace + "SoftwareTag").Value;
81
82 var swidTagPath = Path.Combine(baseFolder, "test.swidtag");
83 File.WriteAllText(swidTagPath, swidTag);
84
85 var docTag = XDocument.Load(swidTagPath);
86 var title = docTag.Root.Attribute("name").Value;
87 var version = docTag.Root.Attribute("version").Value;
88 Assert.Equal("~TagTestBundle", title);
89 Assert.Equal("4.3.2.1", version);
90 }
91 }
92 }
93
94 private static void Build(string[] args)
95 {
96 var result = WixRunner.Execute(args)
97 .AssertSuccess();
98 }
99 }
100}
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
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/.Data/burn.exe
Binary files 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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents">
5 <Component Id="AppIdComp" Directory="INSTALLFOLDER" Guid="171BEE79-43CC-4A28-892F-7EFAC696FA4B">
6 <File Id="AppIdComp.txt" Source="test.txt" Name="AppIdComp.txt"></File>
7 <AppId Id="D6040299-B15C-4C94-AE26-0C9B60D14C35" Advertise="yes" />
8 </Component>
9 </ComponentGroup>
10 </Fragment>
11</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents">
5 <ComponentGroupRef Id="MinimalComponentGroup"></ComponentGroupRef>
6 </ComponentGroup>
7
8 <Property Id="SAMPLECOMPFOUND">
9 <ComponentSearch Id="SampleCompSearch" Guid="{4D9A0D20-D0CC-40DE-B580-EAD38B985217}"></ComponentSearch>
10 </Property>
11 </Fragment>
12</Wix>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Codepage="1252" Language="1033" Manufacturer="Example Corporation" Name="MsiPackage" UpgradeCode="{12E4699F-E774-4D05-8A01-5BDD41BBA127}" Version="1.0.0.0" ProductCode="{33C58183-7333-4257-AEFD-6705DA66E617}">
3 <StandardDirectory Id="ProgramFilesFolder">
4 <Directory Id="INSTALLFOLDER" Name="MsiPackage" ShortName="ykd0udtb">
5 <Component Id="test.txt" Guid="{E597A58A-03CB-50D8-93E3-DABA263F233A}" Bitness="always32">
6 <File Id="test.txt" Name="test.txt" KeyPath="yes" Source="MsiPackage\test.txt" />
7 </Component>
8 </Directory>
9 </StandardDirectory>
10 <Feature Id="ProductFeature" Level="1" Title="MsiPackageTitle">
11 <ComponentRef Id="test.txt" />
12 </Feature>
13 <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
14 <Media Id="1" />
15 <Property Id="SAMPLEREGFOUND">
16 <RegistrySearch Id="RegSearch" Root="HKLM" Key="Reg" Type="raw" Bitness="always32" />
17 </Property>
18 <Property Id="NESTEDDIRFOUND">
19 <RegistrySearch Id="ARegKeySearch" Root="HKLM" Key="ARegKey" Type="raw" Bitness="always32">
20 <DirectorySearch Id="TopDirSearch" Path="TopDir">
21 <DirectorySearch Id="SecondDirSearch" Path="SecondDir">
22 <DirectorySearch Id="ThirdDirSearch" Path="ThirdDir">
23 <DirectorySearch Id="FourthDirSearch" Path="FourthDir" />
24 </DirectorySearch>
25 </DirectorySearch>
26 </DirectorySearch>
27 </RegistrySearch>
28 </Property>
29 <Property Id="SAMPLENESTEDDIRFOUND">
30 <RegistrySearch Id="NestedRegSearch" Root="HKLM" Key="NestedReg" Type="raw" Bitness="always32">
31 <DirectorySearch Id="SampleNestedDirSearch" Path="NestedDir" />
32 </RegistrySearch>
33 </Property>
34 <Property Id="SAMPLEDIRFOUND">
35 <RegistrySearch Id="SubRegSearch" Root="HKLM" Key="SampleReg" Type="raw" Bitness="always32">
36 <DirectorySearch Id="SampleDirSearch" Path="SampleDir">
37 <DirectorySearch Id="SubDirSearch" Path="Subdir" />
38 </DirectorySearch>
39 </RegistrySearch>
40 </Property>
41 </Package>
42</Wix> \ 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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents">
5 <ComponentGroupRef Id="MinimalComponentGroup"></ComponentGroupRef>
6 </ComponentGroup>
7
8 <Property Id="SAMPLEDIRFOUND">
9 <DirectorySearch Id="SampleDirSearch" AssignToProperty="yes" Path="C:\SampleDir"></DirectorySearch>
10 </Property>
11 </Fragment>
12</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents">
5 <ComponentGroupRef Id="MinimalComponentGroup"></ComponentGroupRef>
6 </ComponentGroup>
7
8 <Property Id="SAMPLEFILEFOUND">
9 <IniFileSearch Id="SampleIniFileSearch" Name="sample.fil" Section="MySection" Key="MyKey">
10 <FileSearch Id="SampleFileSearch" Name="sample.fil"></FileSearch>
11 </IniFileSearch>
12 </Property>
13 </Fragment>
14</Wix>
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
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/AppSearch/NestedDirSearchUnderRegSearch.msi
Binary files 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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents">
5 <ComponentGroupRef Id="MinimalComponentGroup"></ComponentGroupRef>
6 </ComponentGroup>
7
8 <Property Id="SAMPLEREGFOUND">
9 <RegistrySearch Id="SampleRegSearch" Root="HKLM" Key="SampleReg" Type="raw"></RegistrySearch>
10 </Property>
11 </Fragment>
12</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents">
5 <ComponentGroupRef Id="MinimalComponentGroup"></ComponentGroupRef>
6 </ComponentGroup>
7
8 <Property Id="SAMPLEREGFOUND">
9 <RegistrySearch Id="SampleRegSearch" Root="HKLM" Key="SampleReg" Type="raw" Bitness="always64"></RegistrySearch>
10 </Property>
11 </Fragment>
12</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2
3<!--
4This file contains the declaration of all the localizable strings.
5-->
6<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
7
8 <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String>
9 <String Id="FeatureTitle">MsiPackage</String>
10
11</WixLocalization>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Name="AssemblyMsiPackage" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="no" InstallerVersion="200" Scope="perMachine">
3
4
5 <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" />
6
7 <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)">
8 <ComponentGroupRef Id="ProductComponents" />
9 </Feature>
10 </Package>
11
12 <Fragment>
13 <StandardDirectory Id="ProgramFilesFolder">
14 <Directory Id="INSTALLFOLDER" Name="AssemblyMsiPackage" />
15 </StandardDirectory>
16 </Fragment>
17</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
5 <Component>
6 <File Source="candle.exe" Assembly=".net" />
7 </Component>
8 </ComponentGroup>
9 </Fragment>
10</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents">
5 <Component Directory="INSTALLFOLDER">
6 <File Id="test.txt" Source="test.txt" Assembly="win32" AssemblyManifest="test.dll.manifest" />
7 </Component>
8 <Component Id="test.dll.manifest" Directory="INSTALLFOLDER">
9 <File Id="test.dll.manifest" Source="test.manifest" Name="test.dll.manifest"></File>
10 </Component>
11 </ComponentGroup>
12 </Fragment>
13</Wix>
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
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Assembly/data/candle.exe
Binary files 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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
3 <assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
4 <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
5 <security>
6 <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
7 <!-- UAC Manifest Options
8 If you want to change the Windows User Account Control level replace the
9 requestedExecutionLevel node with one of the following.
10
11 <requestedExecutionLevel level="asInvoker" uiAccess="false" />
12 <requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
13 <requestedExecutionLevel level="highestAvailable" uiAccess="false" />
14
15 Specifying requestedExecutionLevel element will disable file and registry virtualization.
16 Remove this element if your application requires this virtualization for backwards
17 compatibility.
18 -->
19 <requestedExecutionLevel level="asInvoker" uiAccess="false" />
20 </requestedPrivileges>
21 </security>
22 </trustInfo>
23
24 <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
25 <application>
26 <!-- A list of the Windows versions that this application has been tested on
27 and is designed to work with. Uncomment the appropriate elements
28 and Windows will automatically select the most compatible environment. -->
29
30 <!-- Windows Vista -->
31 <!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->
32
33 <!-- Windows 7 -->
34 <!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->
35
36 <!-- Windows 8 -->
37 <!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->
38
39 <!-- Windows 8.1 -->
40 <!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->
41
42 <!-- Windows 10 -->
43 <!--<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />-->
44
45 </application>
46 </compatibility>
47
48 <!-- Indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher
49 DPIs. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need
50 to opt in. Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should
51 also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config. -->
52 <!--
53 <application xmlns="urn:schemas-microsoft-com:asm.v3">
54 <windowsSettings>
55 <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
56 </windowsSettings>
57 </application>
58 -->
59
60 <!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->
61 <!--
62 <dependency>
63 <dependentAssembly>
64 <assemblyIdentity
65 type="win32"
66 name="Microsoft.Windows.Common-Controls"
67 version="6.0.0.0"
68 processorArchitecture="*"
69 publicKeyToken="6595b64144ccf1df"
70 language="*"
71 />
72 </dependentAssembly>
73 </dependency>
74 -->
75
76</assembly>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"
3 xmlns:ex="http://www.example.com/scheams/v1/wxs">
4 <Fragment>
5 <ComponentGroup Id="ProductComponents">
6 <ComponentGroupRef Id="MinimalComponentGroup" />
7 </ComponentGroup>
8
9 <ex:ExampleEnsureTable />
10 </Fragment>
11</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2
3<!--
4This file contains the declaration of all the localizable strings.
5-->
6<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
7
8 <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String>
9 <String Id="FeatureTitle">MsiPackage</String>
10
11</WixLocalization>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Name="MsiPackage" Language="1033" Version="1.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="no" InstallerVersion="200" Scope="perMachine">
3
4
5 <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" />
6
7 <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)">
8 <ComponentGroupRef Id="ProductComponents.x86" />
9 <!-- <?if x64 == $(sys.BUILDARCH) ?> -->
10 <!-- <?if arm==$(sys.BUILDARCH) ?> -->
11 <?if $(sys.BUILDARCH) = "x64" ?>
12 <ComponentGroupRef Id="ProductComponents.x64" />
13 <ComponentGroupRef Id="ProductComponents.arm" />
14 </Feature>
15 </Package>
16
17 <Fragment>
18 <Directory Id="TARGETDIR" Name="SourceDir">
19 <Directory Id="ProgramFilesFolder">
20 <Directory Id="INSTALLFOLDER" Name="MsiPackage" />
21 </Directory>
22 </Directory>
23 </Fragment>
24</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <?foreach ComponentPlatform in x86;x64;arm ?>
4 <Fragment>
5 <ComponentGroup Id="ProductComponents.$(var.ComponentPlatform)" Directory="INSTALLFOLDER">
6 <Component>
7 <File Name="$(var.ComponentPlatform).dll" Source="test.txt" />
8 </Component>
9 </ComponentGroup>
10 </Fragment>
11 <?endforeach?>
12</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8" ?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <Variable Name="BadType" Type="doesnotexist" Value="dne" />
5 </Fragment>
6</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <ExePackage Id="Manual1" SourceFile="burn.exe" Name="manual1\burn.exe" DetectCondition="test" CacheId="!(wix.WixVariable1)" />
6 <ExePackage Id="Manual2" SourceFile="burn.exe" Name="manual2\burn.exe" DetectCondition="test" CacheId="!(wix.WixVariable2)" />
7 </PackageGroup>
8
9 <WixVariable Id="WixVariable1" Value="CollidingCacheId" />
10 <WixVariable Id="WixVariable2" Value="CollidingCacheId" />
11 </Fragment>
12</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <ExePackage Id="Auto1" SourceFile="burn.exe" CacheId="Auto1" DetectCondition="none" />
6 <ExePackage Id="Auto2" SourceFile="burn.exe" CacheId="Auto2" DetectCondition="none" />
7 <ExePackage Id="DuplicateCacheIds.wxs" SourceFile="$(sys.SOURCEFILEDIR)DuplicateCacheIds.wxs" Compressed="no" DetectCondition="none" Name="PayloadCollision">
8 <Payload SourceFile="$(sys.SOURCEFILEDIR)BundleVariable.wxs" Compressed="no" Name="ContainerCollision" />
9 </ExePackage>
10 <ExePackage Id="HiddenPersistedBundleVariable.wxs" SourceFile="$(sys.SOURCEFILEDIR)HiddenPersistedBundleVariable.wxs" Compressed="no" DetectCondition="none" Name="PayloadCollision" />
11 <PackageGroupRef Id="MsiPackages" />
12 </PackageGroup>
13
14 <PackageGroup Id="MsiPackages">
15 <MsiPackage SourceFile="test.msi">
16 <Payload SourceFile="$(sys.SOURCEFILEDIR)InvalidIds.wxs" Name="MsiPackage\test.txt" />
17 <Payload SourceFile="$(sys.SOURCEFILEDIR)RegistryKey.wxs" Name="test.msi" />
18 </MsiPackage>
19 </PackageGroup>
20
21 <Container Id="MsiPackagesContainer" Type="detached" Name="ContainerCollision">
22 <PackageGroupRef Id="MsiPackages" />
23 </Container>
24
25 <BootstrapperApplication>
26 <Payload Id="DuplicatePayloadNames.wxs" SourceFile="$(sys.SOURCEFILEPATH)" Name="fakeba.dll" />
27 <Payload Id="UnscheduledPackage.wxs" SourceFile="$(sys.SOURCEFILEDIR)UnscheduledPackage.wxs" Name="BootstrapperApplicationData.xml" />
28 <Payload Id="UnscheduledRollbackBoundary.wxs" SourceFile="$(sys.SOURCEFILEDIR)UnscheduledRollbackBoundary.wxs" Name="BundleExtensionData.xml" />
29 </BootstrapperApplication>
30 </Fragment>
31</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8" ?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <Variable Name="BadType" Hidden="yes" Persisted="yes" Value="dne" />
5 </Fragment>
6</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8" ?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <Component Id="@#$" Guid="{2F18F52A-9E24-4ebe-A5FC-974089AA03D2}" Directory="WixTestFolder">
5 <CreateFolder Directory="WixTestFolder" />
6 </Component>
7 </Fragment>
8</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <PackageGroupRef Id="MinimalPackageGroup" />
6 </PackageGroup>
7 <PayloadGroup Id="OrphanPayloads">
8 <Payload Id="OrphanPayload" SourceFile="$(sys.SOURCEFILEPATH)" />
9 </PayloadGroup>
10 </Fragment>
11</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <PackageGroupRef Id="MinimalPackageGroup" />
6 </PackageGroup>
7 <Container Id="First">
8 <PackageGroupRef Id="BundlePackages" />
9 </Container>
10 <Container Id="Second">
11 <PackageGroupRef Id="BundlePackages" />
12 </Container>
13 </Fragment>
14</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8" ?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"
3 xmlns:ex="http://www.example.com/scheams/v1/wxs">
4 <Fragment>
5 <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
6 <Component Id="MiscComponent" Guid="7C40C257-AB36-4B8C-8FD1-C56E0AC4AAEF">
7 <RegistryKey>
8 <ex:Example />
9 </RegistryKey>
10 </Component>
11 </ComponentGroup>
12 </Fragment>
13</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <ExePackage Id="Auto1" SourceFile="burn.exe" CacheId="Auto1" DetectCondition="none" />
6 <ExePackage Id="Auto2" SourceFile="burn.exe" CacheId="Auto2" DetectCondition="none" />
7 </PackageGroup>
8 <SetVariableRef Id="Dummy" />
9 </Fragment>
10 <Fragment>
11 <SetVariable Id="Dummy" Variable="Dummy" />
12 <PackageGroup Id="Unscheduled">
13 <ExePackage Id="Unscheduled1" SourceFile="burn.exe" CacheId="Unscheduled1" DetectCondition="none" />
14 </PackageGroup>
15 </Fragment>
16</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <ExePackage Id="Auto1" SourceFile="burn.exe" CacheId="Auto1" DetectCondition="none" />
6 <ExePackage Id="Auto2" SourceFile="burn.exe" CacheId="Auto2" DetectCondition="none" />
7 </PackageGroup>
8 <SetVariableRef Id="Dummy" />
9 </Fragment>
10 <Fragment>
11 <SetVariable Id="Dummy" Variable="Dummy" />
12 <PackageGroup Id="Unscheduled">
13 <RollbackBoundary Id="Unscheduled1" />
14 </PackageGroup>
15 </Fragment>
16</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8" ?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <Binary Id="Bound" SourceFile="!(wix.Test=data\test.txt)" />
5 </Fragment>
6</Wix>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Fragment>
3 <BootstrapperApplication Id="fakeba">
4 <BootstrapperApplicationDll SourceFile="$(sys.SOURCEFILEPATH)" DpiAwareness="gdiScaled" />
5 </BootstrapperApplication>
6 </Fragment>
7</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <MsiPackage SourceFile="test.msi" CacheId="!(bind.packageDescription.test.msi)" />
6 </PackageGroup>
7 </Fragment>
8</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <PackageGroupRef Id="MinimalPackageGroup" />
6 </PackageGroup>
7
8 <BundleCustomData Id="BundleCustomTableBA">
9 <BundleAttributeDefinition Id="Id" />
10 <BundleAttributeDefinition Id="Column2" />
11
12 <BundleElement>
13 <BundleAttribute Id="Id" Value="one" />
14 <BundleAttribute Id="Column2" Value="two" />
15 </BundleElement>
16 <BundleElement>
17 <BundleAttribute Id="Column2" Value="&lt;" />
18 <BundleAttribute Id="Id" Value="&gt;" />
19 </BundleElement>
20 </BundleCustomData>
21
22 <BundleCustomData Id="BundleCustomTableBE" ExtensionId="CustomTableExtension">
23 <BundleAttributeDefinition Id="Id" />
24 <BundleAttributeDefinition Id="Column2" />
25 </BundleCustomData>
26 </Fragment>
27
28 <Fragment>
29 <BundleCustomDataRef Id="BundleCustomTableBA">
30 <BundleElement>
31 <BundleAttribute Id="Id" Value="1" />
32 <BundleAttribute Id="Column2" Value="2" />
33 </BundleElement>
34 </BundleCustomDataRef>
35
36 <BundleCustomDataRef Id="BundleCustomTableBE">
37 <BundleElement>
38 <BundleAttribute Id="Id" Value="one" />
39 <BundleAttribute Id="Column2" Value="two" />
40 </BundleElement>
41 <BundleElement>
42 <BundleAttribute Id="Column2" Value="&lt;" />
43 <BundleAttribute Id="Id" Value="&gt;" />
44 </BundleElement>
45 <BundleElement>
46 <BundleAttribute Id="Id" Value="1" />
47 <BundleAttribute Id="Column2" Value="2" />
48 </BundleElement>
49 </BundleCustomDataRef>
50
51 <BundleExtension Id="CustomTableExtension" SourceFile="fakeba.dll" Name="fakebext.dll" />
52 </Fragment>
53</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <BundleExtension Id="ExampleBext" SourceFile="fakeba.dll" Name="fakebext.dll" />
5 </Fragment>
6</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"
3 xmlns:ex="http://www.example.com/scheams/v1/wxs">
4 <Fragment>
5 <ex:ExampleSearch Id="ExampleSearchBar" Variable="SearchBar" Condition="WixBundleInstalled" SearchFor="Bar" />
6 <ex:ExampleSearch Id="ExampleSearchFoo" Variable="SearchFoo" SearchFor="Foo" />
7 </Fragment>
8</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"
3 xmlns:ex="http://www.example.com/scheams/v1/wxs">
4 <Fragment>
5 <PackageGroup Id="BundlePackages">
6 <PackageGroupRef Id="MinimalPackageGroup" />
7 </PackageGroup>
8
9 <ex:ExampleSearchRef Id="ExampleSearchFoo" />
10 </Fragment>
11</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <PackageGroupRef Id="MinimalPackageGroup" />
6 </PackageGroup>
7
8 <BundleExtensionRef Id="ExampleBext" />
9 </Fragment>
10</Wix>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" >
2 <Bundle Name="~TagTestBundle" Version="4.3.2.1" Manufacturer="Example Corporation" UpgradeCode="047730A5-30FE-4A62-A520-DA9381B8226A">
3 <BootstrapperApplication>
4 <BootstrapperApplicationDll SourceFile="fakeba.dll" />
5 </BootstrapperApplication>
6
7 <SoftwareTag Regid="wixtoolset.org" InstallPath="[ProgramFiles6432Folder]\Test\swidtag" />
8
9 <Chain>
10 <MsiPackage SourceFile="test.msi">
11 <MsiProperty Name="TEST" Value="1" />
12 </MsiPackage>
13 </Chain>
14 </Bundle>
15</Wix>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Bundle Name="BurnBundle" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="B94478B1-E1F3-4700-9CE8-6AA090854AEC">
3 <ApprovedExeForElevation Id="TestExe" Key="WixToolset\BurnTesting" Value="Test" Bitness="always32" />
4 </Bundle>
5</Wix> \ 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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Bundle Name="BurnBundle64" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="B94478B1-E1F3-4700-9CE8-6AA090854AEC">
3 <ApprovedExeForElevation Id="TestExe" Key="WixToolset\BurnTesting" Value="Test" Bitness="always64" />
4 </Bundle>
5</Wix> \ 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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Fragment>
3 <PackageGroup Id="BundlePackages">
4 <PackageGroupRef Id="MinimalPackageGroup"/>
5 </PackageGroup>
6 <Container Id="_1" Type="detached">
7 <PackageGroupRef Id="MinimalPackageGroup"/>
8 </Container>
9 </Fragment>
10</Wix> \ 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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Bundle Name="BurnBundle" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="B94478B1-E1F3-4700-9CE8-6AA090854AEC">
3 <BootstrapperApplication>
4 <BootstrapperApplicationDll SourceFile="fakeba.dll" />
5 </BootstrapperApplication>
6 <Chain>
7 <PackageGroupRef Id="BundlePackages" />
8 </Chain>
9 </Bundle>
10</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="MinimalPackageGroup">
5 <MsiPackage SourceFile="test.msi" />
6 </PackageGroup>
7 </Fragment>
8</Wix>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Codepage="1252" Language="1033" Manufacturer="Example Corporation" Name="MsiPackage" UpgradeCode="{12E4699F-E774-4D05-8A01-5BDD41BBA127}" Version="1.0.0.0" ProductCode="{FE17A505-11A9-44D2-8D94-EB6BEAB8FF93}">
3 <StandardDirectory Id="ProgramFilesFolder">
4 <Directory Id="INSTALLFOLDER" Name="MsiPackage" ShortName="oekcr5lq">
5 <Component Id="ProgIdComp" Guid="{5B3B3FC1-533D-4C29-BFB3-0E88B51E59D8}" Bitness="always32">
6 <Class Id="{F12A6F69-117F-471F-AE73-F8E74218F498}" Context="LocalServer32" Description="FakeClassF12A" Advertise="yes">
7 <ProgId Id="73E7DF7E-EFAC-4E11-90E2-6EBAEB8DE58D" Description="FakeClassF12A" Advertise="yes" />
8 </Class>
9 <File Id="filTki4JQ2gSapF7wK4K1vd.4mDSFQ" Name="ProgIdComp.txt" KeyPath="yes" ShortName="bnvvntsc.txt" Source="MsiPackage\ProgIdComp.txt" />
10 <RegistryValue Id="regUIIK326nDZpkWHuexeF58EikQvA" Root="HKCR" Key="73E7DF7E-EFAC-4E11-90E2-6EBAEB8DE58D" Name="NoOpen" Value="NoOpen73E7" Type="string" />
11 <RegistryValue Id="regY1F4E2lvu_Up6gV6c3jeN5ukn8s" Root="HKCR" Key="CLSID\{F12A6F69-117F-471F-AE73-F8E74218F498}\LocalServer32" Name="ThreadingModel" Value="Apartment" Type="string" />
12 <RegistryValue Id="regvrhMurMp98anbQJkpgA8yJCefdM" Root="HKCR" Key="CLSID\{F12A6F69-117F-471F-AE73-F8E74218F498}\Version" Value="0.0.0.1" Type="string" />
13 </Component>
14 </Directory>
15 </StandardDirectory>
16 <Feature Id="ProductFeature" Level="1" Title="MsiPackageTitle">
17 <ComponentRef Id="ProgIdComp" Primary="yes" />
18 </Feature>
19 <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
20 <Media Id="1" />
21 </Package>
22</Wix> \ 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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents">
5 <Component Id="ClassComp" Directory="INSTALLFOLDER" Guid="9BFDA7DC-CA16-40B3-A6B5-961E60B30892">
6 <File Source="test.txt" Name="ClassComp.txt"></File>
7 <Class Id="3FAED4CC-C473-4B8A-BE8B-303871377A4A" Advertise="yes" Context="LocalServer32" Description="FakeClass3FAE" ThreadingModel="apartment" Version="0.0.0.1" Icon="SampleIcon" IconIndex="0" />
8 </Component>
9 </ComponentGroup>
10 </Fragment>
11</Wix>
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
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Class/OldClassTableDef.msi
Binary files 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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"
3 xmlns:ex="http://www.example.com/scheams/v1/wxs">
4 <Fragment>
5 <ComponentGroup Id="OtherComponents" Directory="INSTALLFOLDER">
6 <Component>
7 <File Source="other.txt" />
8 <ex:Example Id="Other" Value="Value" />
9 </Component>
10 </ComponentGroup>
11 </Fragment>
12</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2
3<!--
4This file contains the declaration of all the localizable strings.
5-->
6<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
7
8 <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String>
9 <String Id="FeatureTitle">MsiPackage</String>
10
11</WixLocalization>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Name="MsiPackage" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="no" InstallerVersion="200" Scope="perMachine">
3
4
5 <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" />
6
7 <Property Id="ExampleProperty" Value="$(ex.Test)" />
8
9 <PropertyRef Id="PropertyFromExampleWir" />
10
11 <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)">
12 <ComponentGroupRef Id="ProductComponents" />
13 <ComponentGroupRef Id="OtherComponents" />
14 </Feature>
15 </Package>
16
17 <Fragment>
18 <StandardDirectory Id="ProgramFilesFolder">
19 <Directory Id="INSTALLFOLDER" Name="MsiPackage" />
20 </StandardDirectory>
21 </Fragment>
22</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"
3 xmlns:ex="http://www.example.com/scheams/v1/wxs">
4 <Fragment>
5 <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
6 <Component>
7 <File Source="example.txt" />
8 <ex:Example Id="Foo" Value="Bar" />
9 </Component>
10 </ComponentGroup>
11 </Fragment>
12</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
5 <Component Guid="5917d193-7b5f-4d5d-bc2e-06aa210699cb">
6 <RegistryValue Root="HKLM" Key="SOFTWARE\WixToolset" Name="Test" Value="test value" />
7 </Component>
8
9 <Component Guid="5917d193-7b5f-4d5d-bc2e-06aa210699cb">
10 <File Source="test.txt" />
11 </Component>
12 </ComponentGroup>
13 </Fragment>
14</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2
3<!--
4This file contains the declaration of all the localizable strings.
5-->
6<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
7
8 <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String>
9 <String Id="FeatureTitle">MsiPackage</String>
10
11</WixLocalization>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Name="MsiPackage" Codepage="1252" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="no" InstallerVersion="200" Scope="perMachine">
3
4
5 <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" />
6
7 <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)">
8 <ComponentGroupRef Id="ProductComponents" />
9 </Feature>
10 </Package>
11
12 <Fragment>
13 <StandardDirectory Id="ProgramFilesFolder">
14 <Directory Id="INSTALLFOLDER" Name="MsiPackage" />
15 </StandardDirectory>
16 </Fragment>
17</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
5 <Component Id="NullKeypathComponent" Guid="{C493379B-D655-4331-8F03-B618C70EA779}" KeyPath="yes">
6 <File Source="test.txt" />
7 </Component>
8 </ComponentGroup>
9 </Fragment>
10</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <MsiPackage Id="FirstX86">
6 <PayloadGroupRef Id="FirstX86Payloads" />
7 </MsiPackage>
8 <MsiPackage Id="FirstX64" Name="FirstX64\FirstX64.msi" SourceFile="FirstX64\" DownloadUrl="http://example.com/{0}/{1}/{2}" />
9 </PackageGroup>
10 <Container Id="BundlePackages" Type="attached">
11 <PackageGroupRef Id="BundlePackages" />
12 </Container>
13 <PayloadGroup Id="FirstX86Payloads">
14 <MsiPackagePayload Name="FirstX86\FirstX86.msi" SourceFile="FirstX86\" DownloadUrl="http://example.com/{0}/{1}/{2}" />
15 </PayloadGroup>
16 </Fragment>
17</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <MsiPackage SourceFile="FirstX86.msi" />
6 <PackageGroupRef Id="FirstX64" />
7 </PackageGroup>
8 <PackageGroup Id="FirstX64">
9 <MsiPackage SourceFile="FirstX64.msi" />
10 </PackageGroup>
11 <Container Id="FirstX64" Name="FirstX64" Type="detached">
12 <PackageGroupRef Id="FirstX64" />
13 </Container>
14 </Fragment>
15</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Bundle Name="BurnBundle" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="{B5B23622-239B-4E3B-BDAB-67648CB975BF}">
4 <BootstrapperApplication>
5 <BootstrapperApplicationDll SourceFile="fakeba.dll" />
6 </BootstrapperApplication>
7 <Chain>
8 <PackageGroupRef Id="BundlePackages" />
9 </Chain>
10 <PayloadGroupRef Id="Shared" />
11 </Bundle>
12 <Fragment>
13 <PackageGroup Id="BundlePackages">
14 <PackageGroupRef Id="FirstX64" />
15 </PackageGroup>
16 <PackageGroup Id="FirstX64">
17 <MsiPackage SourceFile="FirstX64.msi">
18 <PayloadGroupRef Id="Shared" />
19 </MsiPackage>
20 </PackageGroup>
21 <Container Id="FirstX64" Name="FirstX64" Type="detached">
22 <PackageGroupRef Id="FirstX64" />
23 </Container>
24 <PayloadGroup Id="Shared">
25 <Payload Id="SharedPayload" SourceFile="$(sys.SOURCEFILEPATH)" />
26 </PayloadGroup>
27 </Fragment>
28</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <MsiPackage SourceFile="FirstX86.msi" />
6 <PackageGroupRef Id="FirstX64" />
7 </PackageGroup>
8 <PackageGroup Id="FirstX64">
9 <MsiPackage SourceFile="FirstX64.msi" />
10 </PackageGroup>
11 <Container Id="FirstX64" Name="FirstX64" Type="attached">
12 <PackageGroupRef Id="FirstX64" />
13 </Container>
14 </Fragment>
15</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <PackageGroupRef Id="FirstX86" />
6 <PackageGroupRef Id="FirstX64" />
7 </PackageGroup>
8 <PackageGroup Id="FirstX86">
9 <MsiPackage SourceFile="FirstX86.msi">
10 <PayloadGroupRef Id="Shared" />
11 </MsiPackage>
12 </PackageGroup>
13 <PackageGroup Id="FirstX64">
14 <MsiPackage SourceFile="FirstX64.msi">
15 <PayloadGroupRef Id="Shared" />
16 </MsiPackage>
17 </PackageGroup>
18 <Container Id="FirstX86" Name="FirstX86" Type="detached">
19 <PackageGroupRef Id="FirstX86" />
20 </Container>
21 <Container Id="FirstX64" Name="FirstX64" Type="detached">
22 <PackageGroupRef Id="FirstX64" />
23 </Container>
24 <PayloadGroup Id="Shared">
25 <Payload Id="SharedPayload" SourceFile="$(sys.SOURCEFILEPATH)" />
26 </PayloadGroup>
27 </Fragment>
28</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents">
5 <Component Id="CopyFileComp" Directory="INSTALLFOLDER">
6 <File Id="test.txt" Source="test.txt" />
7 <CopyFile Id="MoveText" Delete="yes" SourceName="*.txt" DestinationDirectory="OtherFolder"/>
8 </Component>
9 </ComponentGroup>
10 </Fragment>
11
12 <Fragment>
13 <DirectoryRef Id="INSTALLFOLDER">
14 <Directory Id="OtherFolder" Name="other" />
15 </DirectoryRef>
16 </Fragment>
17</Wix>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Fragment>
3 <ComponentGroup Id="ProductComponents">
4 <ComponentGroupRef Id="MinimalComponentGroup" />
5 </ComponentGroup>
6
7 <Binary Id="Binary1" SourceFile="test.txt" />
8 <CustomAction Id="Action1" DllEntry="EntryPoint1" BinaryRef="Binary1" />
9 <CustomAction Id="Action2" DllEntry="EntryPoint2" BinaryRef="Binary1" />
10 <CustomAction Id="Action3" DllEntry="EntryPoint3" BinaryRef="Binary1" />
11
12 <InstallExecuteSequence>
13 <Custom Action="Action1" After="Action2" />
14 <Custom Action="Action2" After="Action3" />
15 <Custom Action="Action3" After="Action1" />
16 </InstallExecuteSequence>
17 </Fragment>
18</Wix>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Fragment>
3 <ComponentGroup Id="ProductComponents">
4 <ComponentGroupRef Id="MinimalComponentGroup" />
5 </ComponentGroup>
6
7 <Binary Id="Binary1" SourceFile="test.txt" />
8 <CustomAction Id="Action1" DllEntry="EntryPoint1" BinaryRef="Binary1" />
9 <CustomAction Id="Action2" DllEntry="EntryPoint2" BinaryRef="Binary1" />
10 <CustomAction Id="Action3" DllEntry="EntryPoint3" BinaryRef="Binary1" />
11 <CustomAction Id="Action4" DllEntry="EntryPoint4" BinaryRef="Binary1" />
12
13 <InstallExecuteSequence>
14 <Custom Action="Action1" After="Action2" />
15 <Custom Action="Action2" After="Action3" />
16 <Custom Action="Action3" After="Action4" />
17 <Custom Action="Action4" After="Action2" />
18 </InstallExecuteSequence>
19 </Fragment>
20</Wix> \ 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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Fragment>
3 <ComponentGroup Id="ProductComponents">
4 <ComponentGroupRef Id="MinimalComponentGroup" />
5 </ComponentGroup>
6
7 <Binary Id="Binary1" SourceFile="test.txt" />
8 <CustomAction Id="Action1" DllEntry="EntryPoint1" BinaryRef="Binary1" />
9
10 <InstallExecuteSequence>
11 <Custom Action="Action1" After="InstallFiles" />
12 </InstallExecuteSequence>
13 </Fragment>
14</Wix>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Fragment>
3 <ComponentGroup Id="ProductComponents">
4 <ComponentGroupRef Id="MinimalComponentGroup" />
5 </ComponentGroup>
6
7 <Binary Id="Binary1" SourceFile="test.txt" />
8 <CustomAction Id="CustomAction1" DllEntry="InvalidEntryPoint" BinaryRef="Binary1" />
9 <CustomAction Id="DiscardOptimismAllBeingsWhoProceed" Error="Abandon hope all ye who enter here." />
10 <CustomAction Id="CustomActionWithHiddenTarget" DllEntry="InvalidEntryPoint" Execute="deferred" HideTarget="yes" BinaryRef="Binary1" />
11 <CustomAction Id="CustomAction2" Property="TestAdvtExecuteSequenceProperty" Value="1" />
12 <AdminExecuteSequence>
13 <Custom Action="CustomAction2" After="CostInitialize" />
14 </AdminExecuteSequence>
15 <AdminUISequence>
16 <Custom Action="CustomAction2" After="CostInitialize" />
17 </AdminUISequence>
18 <AdvertiseExecuteSequence>
19 <Custom Action="CustomAction2" After="CostInitialize" />
20 </AdvertiseExecuteSequence>
21 <InstallExecuteSequence>
22 <Custom Action="CustomAction2" After="CostInitialize" />
23 </InstallExecuteSequence>
24 <InstallUISequence>
25 <Custom Action="CustomAction2" After="CostInitialize" />
26 </InstallUISequence>
27
28 <UI>
29 <ProgressText Action="CustomAction2" Message="Progess2Text" />
30 </UI>
31 </Fragment>
32</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <ExePackage DetectCondition="ForTestPurposesOnly" SourceFile="burn.exe" Permanent="yes" />
6 <ExePackage Id="RemotePayloadExe" DetectCondition="ForTestPurposesOnly" Description="Override RemotePayload description" DisplayName="Override RemotePayload display name" Permanent="yes">
7 <ExePackagePayload Description="RemotePayload description" Hash="a" ProductName="RemotePayload product name" Size="1" Version="1.0.0.0" Name="fake.exe" DownloadUrl="example.com" />
8 </ExePackage>
9 <ExePackage DetectCondition="ForTestPurposesOnly" SourceFile="C:\Windows\system32\calc.exe" Permanent="yes" Description="Override harvested description" DisplayName="Override harvested display name" />
10 </PackageGroup>
11 </Fragment>
12</Wix>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Codepage="1252" Language="1033" Manufacturer="Example Corporation" Name="MsiPackage" UpgradeCode="{12E4699F-E774-4D05-8A01-5BDD41BBA127}" Version="1.0.0.0" ProductCode="{83F9C623-26FE-42AB-951E-170022117F54}">
3 <CustomTable Id="CustomTable1">
4 <Column Id="Column1" PrimaryKey="yes" Type="string" Width="0" Category="text" Description="The first custom column." />
5 <Column Id="Component_" Type="string" Width="72" KeyTable="Component" KeyColumn="1" Description="The custom table's Component reference" />
6 <Row>
7 <Data Column="Column1" Value="Row1" />
8 <Data Column="Component_" Value="test.txt" />
9 </Row>
10 <Row>
11 <Data Column="Column1" Value="Row2" />
12 <Data Column="Component_" Value="test.txt" />
13 </Row>
14 </CustomTable>
15 <StandardDirectory Id="ProgramFiles6432Folder">
16 <Directory Id="INSTALLFOLDER" Name="MsiPackage" ShortName="1egc1laj">
17 <Component Id="filcV1yrx0x8wJWj4qMzcH21jwkPko" Guid="{E597A58A-03CB-50D8-93E3-DABA263F233A}" Bitness="always32">
18 <File Id="filcV1yrx0x8wJWj4qMzcH21jwkPko" Name="test.txt" KeyPath="yes" Source="MsiPackage\test.txt" />
19 </Component>
20 </Directory>
21 </StandardDirectory>
22 <StandardDirectory Id="ProgramFilesFolder" />
23 <Feature Id="ProductFeature" Level="1" Title="MsiPackageTitle">
24 <ComponentRef Id="filcV1yrx0x8wJWj4qMzcH21jwkPko" />
25 </Feature>
26 <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
27 <Media Id="1" />
28 </Package>
29</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8" ?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents">
5 <ComponentGroupRef Id="MinimalComponentGroup" />
6 </ComponentGroup>
7
8 <CustomTable Id="CustomTable1">
9 <Column Id="Column1" Type="string" PrimaryKey="yes" Category="text" Modularize="column" Description="The first custom column." />
10 <Column Id="Component_" Type="string" Width="72" KeyTable="Component" KeyColumn="1" Description="The custom table's Component reference" Modularize="column" />
11 <Row>
12 <Data Column="Column1" Value="Row1" />
13 <Data Column="Component_" Value="test.txt" />
14 </Row>
15 <Row>
16 <Data Column="Column1" Value="Row2" />
17 <Data Column="Component_" Value="test.txt" />
18 </Row>
19 </CustomTable>
20
21 <CustomTable Id="CustomTable2" Unreal="yes">
22 <Column Id="ColumnA" Type="string" PrimaryKey="yes" />
23 <Column Id="Component_" Type="string" Width="72" KeyTable="Component" KeyColumn="1" Modularize="column" />
24 <Row>
25 <Data Column="ColumnA" Value="RowA" />
26 <Data Column="Component_" Value="test.txt" />
27 </Row>
28 <Row>
29 <Data Column="ColumnA" Value="RowB" />
30 <Data Column="Component_" Value="test.txt" />
31 </Row>
32 </CustomTable>
33 </Fragment>
34</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8" ?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents">
5 <ComponentGroupRef Id="MinimalComponentGroup" />
6 </ComponentGroup>
7
8 <CustomTable Id="CustomTableWithFile">
9 <Column Id="Column1" Type="string" PrimaryKey="yes" />
10 <Column Id="Source" Type="binary" Width="0" />
11 <Row>
12 <Data Column="Column1" Value="Row1" />
13 <Data Column="Source" Value="file1.txt" />
14 </Row>
15 <Row>
16 <Data Column="Source" Value="SourceDir\file2.txt" />
17 <Data Column="Column1" Value="Row2" />
18 </Row>
19 </CustomTable>
20
21 </Fragment>
22</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
3
4 <String Id="Loc1">This is row one</String>
5 <String Id="Loc2">This is row two</String>
6
7</WixLocalization>
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 @@
1<?xml version="1.0" encoding="utf-8" ?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents">
5 <ComponentGroupRef Id="MinimalComponentGroup" />
6 </ComponentGroup>
7
8 <CustomTable Id="CustomTableLocalized">
9 <Column Id="Column1" Type="string" PrimaryKey="yes" />
10 <Column Id="DataColumn" Type="string" Localizable="yes" Width="255" />
11 <Row>
12 <Data Column="Column1" Value="Row1" />
13 <Data Column="DataColumn" Value="!(loc.Loc1)" />
14 </Row>
15 <Row>
16 <Data Column="Column1" Value="Row2" />
17 <Data Column="DataColumn" Value="!(loc.Loc2)" />
18 </Row>
19 </CustomTable>
20 </Fragment>
21</Wix>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Codepage="65001" Language="1033" Manufacturer="Example Corporation" Name="MsiPackage" UpgradeCode="{047730A5-30FE-4A62-A520-DA9381B8226A}" Version="1.0.0.0" Compressed="yes" InstallerVersion="200" ProductCode="{6F9B5694-F0F1-437C-919B-0D2DAF2D9DEA}">
3 <StandardDirectory Id="ProgramFilesFolder">
4 <Directory Id="INSTALLFOLDER" Name="MsiPackage" ShortName="oekcr5lq">
5 <Component Id="filcV1yrx0x8wJWj4qMzcH21jwkPko" Guid="" Bitness="always32">
6 <File Id="filcV1yrx0x8wJWj4qMzcH21jwkPko" Name="test.txt" KeyPath="yes" Source="SourceDir\File\filcV1yrx0x8wJWj4qMzcH21jwkPko" />
7 </Component>
8 </Directory>
9 </StandardDirectory>
10 <Feature Id="ProductFeature" Level="1" Title="MsiPackage">
11 <ComponentRef Id="filcV1yrx0x8wJWj4qMzcH21jwkPko" />
12 </Feature>
13 <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
14 <Media Id="1" Cabinet="example.cab" />
15 </Package>
16</Wix>
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
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.cab
Binary files 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
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileNullComponent/example.msi
Binary files 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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Codepage="65001" Language="1033" Manufacturer="Example Corporation" Name="MsiPackage" UpgradeCode="{047730A5-30FE-4A62-A520-DA9381B8226A}" Version="1.0.0.0" Compressed="yes" InstallerVersion="200" ProductCode="{6F9B5694-F0F1-437C-919B-0D2DAF2D9DEA}">
3 <StandardDirectory Id="ProgramFilesFolder">
4 <Directory Id="INSTALLFOLDER" Name="MsiPackage" ShortName="oekcr5lq">
5 <Component Id="filcV1yrx0x8wJWj4qMzcH21jwkPko" Guid="{E597A58A-03CB-50D8-93E3-DABA263F233A}" Bitness="always32">
6 <File Id="filcV1yrx0x8wJWj4qMzcH21jwkPko" Name="test.txt" KeyPath="yes" Source="SourceDir\File\filcV1yrx0x8wJWj4qMzcH21jwkPko" />
7 </Component>
8 </Directory>
9 </StandardDirectory>
10 <Feature Id="ProductFeature" Level="1" Title="MsiPackage">
11 <ComponentRef Id="filcV1yrx0x8wJWj4qMzcH21jwkPko" />
12 </Feature>
13 <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
14 <Media Id="1" Cabinet="example.cab" />
15 </Package>
16</Wix>
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
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.cab
Binary files 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
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed/example.msi
Binary files 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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Codepage="65001" Language="1033" Manufacturer="Example Corporation" Name="MsiPackage" UpgradeCode="{047730A5-30FE-4A62-A520-DA9381B8226A}" Version="1.0.0.0" Compressed="yes" ProductCode="{C51B773A-B3BE-4F29-A8A9-549AAF7FF6EC}">
3 <StandardDirectory Id="ProgramFiles64Folder">
4 <Directory Id="INSTALLFOLDER" Name="MsiPackage" ShortName="oekcr5lq">
5 <Component Id="filcV1yrx0x8wJWj4qMzcH21jwkPko" Guid="{E597A58A-03CB-50D8-93E3-DABA263F233A}" Bitness="always64">
6 <File Id="filcV1yrx0x8wJWj4qMzcH21jwkPko" Name="test.txt" KeyPath="yes" Source="SourceDir\File\filcV1yrx0x8wJWj4qMzcH21jwkPko" />
7 </Component>
8 </Directory>
9 </StandardDirectory>
10 <Feature Id="ProductFeature" Level="1" Title="MsiPackage">
11 <ComponentRef Id="filcV1yrx0x8wJWj4qMzcH21jwkPko" />
12 </Feature>
13 <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
14 <Media Id="1" Cabinet="example.cab" />
15 </Package>
16</Wix>
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
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.cab
Binary files 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
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileSingleFileCompressed64/example.msi
Binary files 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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Module Id="MergeModule1" Language="1033" Version="1.0.0.0" InstallerVersion="200">
4 <Directory Id="MergeRedirectFolder">
5 <Component Id="ModuleComponent2" Guid="{BB222EE8-229B-4051-9443-49E348F0CC77}" Bitness="always32">
6 <File Id="File2" ShortName="sfmxqeab.wxs" Name="MergeModule.wxs" KeyPath="yes" Source="SourceDir\File\File2.F844F0E3_8CB4_4A0F_973E_31C4F9338382" />
7 </Component>
8 </Directory>
9 <StandardDirectory Id="ProgramFilesFolder">
10 <Directory Id="WixTestDir" ShortName="7bhhvaai" Name="WiX Toolset Test Directory">
11 <Component Id="ModuleComponent1" Guid="{D86EC5A2-9576-4699-BDC3-00586FF72CBE}" Bitness="always32">
12 <File Id="File1" ShortName="gahushls.wxs" Name="MergeModule.wxs" KeyPath="yes" Source="SourceDir\File\File1.F844F0E3_8CB4_4A0F_973E_31C4F9338382" />
13 </Component>
14 </Directory>
15 </StandardDirectory>
16 <SummaryInformation Description="MergeModule1" Manufacturer="WiX Toolset contributors" />
17 </Module>
18</Wix> \ 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
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/DecompileTargetDirMergeModule/MergeModule1.msm
Binary files 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 @@
1<?xml version="1.0" encoding="utf-8" ?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
5 <Component>
6 <File Source="test.txt" />
7 </Component>
8 </ComponentGroup>
9
10 <DirectoryRef Id="INSTALLFOLDER">
11 <Directory Id="DUPLICATENAMEANDSHORTNAME" Name="duplicat" ShortName="duplicat"></Directory>
12 <Directory Id="NAMEWITHSHORTVALUE" Name="SHORTVAL"></Directory>
13 <Directory Id="NAMEANDSHORTNAME" Name="NameAndShortName" ShortName="SHORTNAM"></Directory>
14 <Directory Id="SHORTNAMEONLY" Name="SHORTONL"></Directory>
15 <Directory Id="SOURCENAME" Name="NameAndSourceName" SourceName="SourceNameWithName"></Directory>
16 <Directory Id="SOURCENAMESONLY" SourceName="SourceNameOnly" ShortSourceName="SRCNAMON"></Directory>
17 <Directory Id="NAMEANDSHORTSOURCENAME" Name="NameAndShortSourceName" ShortName="NAMEASSN"></Directory>
18 <Directory Id="SHORTNAMEANDLONGSOURCENAME" SourceName="ShortNameAndLongSourceName" Name="SHNALSNM"></Directory>
19 <Directory Id="SOURCENAMEWITHSHORTVALUE" SourceName="SRTSRCVL"></Directory>
20 <Directory Id="Folder1" Name="Folder.1"></Directory>
21 <Directory Id="Folder12" Name="Folder.12"></Directory>
22 <Directory Id="Folder123" Name="Folder.123"></Directory>
23 <Directory Id="Folder1234" Name="Folder.1234"></Directory>
24 </DirectoryRef>
25 </Fragment>
26</Wix>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Bundle Name="BurnBundle" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="B94478B1-E1F3-4700-9CE8-6AA090854AEC" ProviderKey="MyProviderKey,v1.0">
3 <BootstrapperApplication>
4 <BootstrapperApplicationDll SourceFile="fakeba.dll" />
5 </BootstrapperApplication>
6 <Chain>
7 <PackageGroupRef Id="MinimalPackageGroup" />
8 </Chain>
9 </Bundle>
10</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <ExePackage DetectCondition="DetectedSomething" SourceFile="burn.exe">
6 <Provides Key="DependencyTests_ExeA,v1.0" Version="1.0.0.0" />
7 </ExePackage>
8 </PackageGroup>
9 </Fragment>
10</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <MsiPackage SourceFile="UsingProvides.msi" />
6 </PackageGroup>
7 </Fragment>
8</Wix>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Fragment>
3 <UI Id="CustomDialog">
4 <Dialog Id="FirstDialog" Width="100" Height="100">
5 <Control Id="Title" Type="Text" X="0" Y="0" Width="90" Height="13" TabSkip="no" Text="FirstDialogTitle" />
6 <Control Id="Header" Type="Text" X="0" Y="13" Width="90" Height="13" TabSkip="no" Text="FirstDialogHeader" HideCondition="Installed" DisableCondition="Installed" />
7 </Dialog>
8 <Dialog Id="SecondDialog" Width="100" Height="100">
9 <Control Id="Title" Type="Text" X="0" Y="0" Width="90" Height="13" TabSkip="no" Text="SecondDialogTitle" />
10 <Control Id="OptionalCheckBox" Type="CheckBox" X="0" Y="13" Width="100" Height="40" Hidden="yes" Property="WIXUI_EXITDIALOGOPTIONALCHECKBOX" CheckBoxValue="1" Text="[WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT]" ToolTip="Optional checkbox" Help="Check this box for fun" ShowCondition="WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT AND NOT Installed" />
11 </Dialog>
12
13 <InstallUISequence>
14 <Show Dialog="SecondDialog" Before="FirstDialog" Overridable="yes" Condition="NOT Installed" />
15 </InstallUISequence>
16 </UI>
17 </Fragment>
18 <Fragment>
19 <UI Id="CustomUI">
20 <DialogRef Id="FirstDialog" />
21 <DialogRef Id="SecondDialog" />
22
23 <Publish Dialog="FirstDialog" Control="Next" Event="NewDialog" Value="SecondDialog" Condition="Installed AND PATCH" />
24
25 <InstallUISequence>
26 <Show Dialog="FirstDialog" Before="SecondDialog" Condition="Installed AND PATCH" />
27 <Show Dialog="SecondDialog" Before="ExecuteAction" Condition="NOT Installed" />
28 </InstallUISequence>
29 </UI>
30 <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
31 <Component>
32 <File Source="test.txt" />
33 </Component>
34 </ComponentGroup>
35 </Fragment>
36</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents" Directory="BinFolder" />
5 </Fragment>
6 <Fragment>
7 <StandardDirectory Id="ProgramFilesFolder">
8 <Directory Id="CompanyFolder" Name="!(bind.Property.Manufacturer)">
9 <Directory Id="BinFolder" Name="." />
10 </Directory>
11 </StandardDirectory>
12 </Fragment>
13</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents" Directory="BinFolder" />
5 </Fragment>
6 <Fragment>
7 <StandardDirectory Id="ProgramFilesFolder">
8 <Directory Id="BinFolder" Name="bin" SourceName="bin" />
9 </StandardDirectory>
10 </Fragment>
11</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER" />
5 </Fragment>
6</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents" Directory="BinFolder" />
5 </Fragment>
6 <Fragment>
7 <StandardDirectory Id="ProgramFilesFolder">
8 <Directory Id="BinFolder" Name="Example Corporation\Test Product\bin" />
9 </StandardDirectory>
10 </Fragment>
11</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8" ?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents">
5 <ComponentGroupRef Id="GroupA" />
6 <ComponentGroupRef Id="GroupB" />
7 </ComponentGroup>
8 </Fragment>
9
10 <Fragment>
11 <ComponentGroup Id="GroupA" Directory="INSTALLFOLDER" Subdirectory="dupe">
12 <Component>
13 <File Name="a.txt" Source="test.txt" />
14 </Component>
15 </ComponentGroup>
16 </Fragment>
17
18 <Fragment>
19 <ComponentGroup Id="GroupB" Directory="INSTALLFOLDER" Subdirectory="dupe">
20 <Component>
21 <File Name="b.txt" Source="test.txt" />
22 </Component>
23 </ComponentGroup>
24 </Fragment>
25</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents">
5 <ComponentGroupRef Id="MinimalComponentGroup" />
6 </ComponentGroup>
7
8 <EnsureTable Id="Wix4Example" />
9 </Fragment>
10</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents">
5 <Component Id="WixEnvironmentTest" Guid="{068C1CF4-DA54-4221-B0D2-E7310770DF0B}" Directory="INSTALLFOLDER">
6 <Environment Id="WixEnvironmentTest1" Action="set" Name="WixEnvTest1"/>
7 <Environment Id="WixEnvironmentTest2" Action="create" Name="WixEnvTest1"/>
8 <Environment Id="WixEnvironmentTest3" Action="remove" Name="WixEnvTest1"/>
9 <Environment Id="WixEnvironmentTest4" Name="WIX" Action="set" System="yes" Value="[INSTALLFOLDER]" />
10 <Environment Id="PATH" Name="PATH" Action="set" Part="first" Value="[INSTALLFOLDER]; " System="yes" />
11 </Component>
12 </ComponentGroup>
13 </Fragment>
14</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2
3<!--
4This file contains the declaration of all the localizable strings.
5-->
6<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
7 <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String>
8 <String Id="FeatureTitle">MsiPackage</String>
9</WixLocalization>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Name="MsiPackage" Codepage="1252" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="no" InstallerVersion="200" Scope="perMachine">
3
4
5 <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" />
6
7 <CustomAction Id="CanWeReferenceAnError_YesWeCan" Error="1234" />
8 <CustomAction Id="TextErrorsWorkOKToo" Error="If you see this, something went wrong." />
9
10 <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)">
11 <ComponentGroupRef Id="ProductComponents" />
12 </Feature>
13 </Package>
14
15 <Fragment>
16 <StandardDirectory Id="ProgramFilesFolder">
17 <Directory Id="INSTALLFOLDER" Name="MsiPackage" />
18 </StandardDirectory>
19 </Fragment>
20</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <UI>
5 <Error Id="1234" Message="Category 55 Emergency Doomsday Crisis" />
6 <Error Id="5678" Message=" " />
7 </UI>
8 <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
9 <Component>
10 <File Source="test.txt" />
11 </Component>
12 </ComponentGroup>
13 </Fragment>
14</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2
3<!--
4This file contains the declaration of all the localizable strings.
5-->
6<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
7
8 <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String>
9 <String Id="FeatureTitle">MsiPackage</String>
10
11</WixLocalization>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Name="MsiPackage" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="no" InstallerVersion="200" Scope="perMachine">
3
4
5 <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" />
6
7 <Property Id="ExampleProperty" Value="$(ex.Test)" />
8
9 <PropertyRef Id="PropertyFromExampleWir" />
10
11 <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)">
12 <ComponentGroupRef Id="ProductComponents" />
13 </Feature>
14 </Package>
15
16 <Fragment>
17 <StandardDirectory Id="ProgramFilesFolder">
18 <Directory Id="INSTALLFOLDER" Name="MsiPackage" />
19 </StandardDirectory>
20 </Fragment>
21</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"
3 xmlns:ex="http://www.example.com/scheams/v1/wxs">
4 <Fragment>
5 <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
6 <Component>
7 <File Source="example.txt" />
8 <ex:Example Id="Foo" Value="Bar" />
9 </Component>
10 </ComponentGroup>
11 </Fragment>
12</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="TestPackageGroup">
5 <ExePackage InstallArguments="-install"
6 SourceFile="testsetup.exe" />
7 </PackageGroup>
8 </Fragment>
9</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="TestPackageGroup">
5 <ExePackage DetectCondition=""
6 InstallArguments="-install"
7 UninstallArguments="-uninstall"
8 SourceFile="testsetup.exe" />
9 </PackageGroup>
10 </Fragment>
11</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8" ?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents">
5 <ComponentGroupRef Id="MinimalComponentGroup"></ComponentGroupRef>
6 </ComponentGroup>
7
8 <FeatureGroup Id="ProductFeatureGroup">
9 <Feature Id="ParentFeature" Title="ParentFeatureTitle">
10 <Feature Id="ChildFeature" Title="ChildFeatureTitle"></Feature>
11 </Feature>
12 </FeatureGroup>
13 </Fragment>
14</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents">
5 <Component Id="FontComp" Directory="INSTALLFOLDER">
6 <File Id="test.txt" Source="test.txt" FontTitle="FakeFont" />
7 </Component>
8 </ComponentGroup>
9 </Fragment>
10</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents">
5 <Component Id="TrueTypeFontComp" Directory="INSTALLFOLDER" Guid="C8116E5E-F731-427C-AF43-6FF8B8D7DA64">
6 <File Id="TrueTypeFontFile" Source="test.txt" Name="TrueTypeFontComp.ttf" TrueType="yes" />
7 </Component>
8 </ComponentGroup>
9 </Fragment>
10</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2
3<!--
4This file contains the declaration of all the localizable strings.
5-->
6<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
7
8 <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String>
9 <String Id="FeatureTitle">MsiPackage</String>
10
11</WixLocalization>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Name="MsiPackage" Language="1033" Version="1.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="no" InstallerVersion="200" Scope="perMachine">
3
4
5 <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" />
6
7 <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)">
8 <ComponentGroupRef Id="ProductComponents.x86" />
9 <ComponentGroupRef Id="ProductComponents.x64" />
10 <ComponentGroupRef Id="ProductComponents.arm" />
11 </Feature>
12 </Package>
13
14 <Fragment>
15 <StandardDirectory Id="ProgramFilesFolder">
16 <Directory Id="INSTALLFOLDER" Name="MsiPackage" />
17 </StandardDirectory>
18 </Fragment>
19</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <?foreach ComponentPlatform in x86;x64;arm ?>
4 <Fragment>
5 <ComponentGroup Id="ProductComponents.$(var.ComponentPlatform)" Directory="INSTALLFOLDER">
6 <Component>
7 <File Name="$(var.ComponentPlatform).dll" Source="test.txt" />
8 </Component>
9 </ComponentGroup>
10 </Fragment>
11 <?endforeach?>
12</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <Icon Id="SampleIcon" SourceFile="burn.exe" />
5 </Fragment>
6</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2
3<!--
4This file contains the declaration of all the localizable strings.
5-->
6<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
7
8 <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String>
9 <String Id="FeatureTitle">MsiPackage</String>
10
11</WixLocalization>
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 @@
1<?include Package.wxi ?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Package Name="MsiPackage" Language="1033" Version="$(var.ProductVersion)" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="no" InstallerVersion="200" Scope="perMachine">
4
5
6 <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" />
7
8 <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)">
9 <ComponentGroupRef Id="ProductComponents" />
10 </Feature>
11 </Package>
12
13 <Fragment>
14 <StandardDirectory Id="ProgramFilesFolder">
15 <Directory Id="INSTALLFOLDER" Name="MsiPackage" />
16 </StandardDirectory>
17 </Fragment>
18</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
5 <?include DontDoThis.wxi ?>
6 </ComponentGroup>
7 </Fragment>
8</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Include xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Component>
4 <File Source="test.txt" />
5 </Component>
6</Include>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Include xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <?define ProductVersion = "1.2.3" ?>
4</Include>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2
3<!--
4This file contains the declaration of all the localizable strings.
5-->
6<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
7
8 <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String>
9 <String Id="FeatureTitle">MsiPackage</String>
10
11</WixLocalization>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Name="MsiPackage" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="no" InstallerVersion="200" Scope="perMachine">
3
4
5 <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" />
6
7 <Property Id="INSTANCEPROPERTY" Secure="yes" />
8
9 <InstanceTransforms Property="INSTANCEPROPERTY">
10 <Instance Id="I1" ProductCode="*" ProductName="MsiPackage (Instance 1)" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" />
11 </InstanceTransforms>
12
13 <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)">
14 <ComponentGroupRef Id="ProductComponents" />
15 </Feature>
16 </Package>
17
18 <Fragment>
19 <StandardDirectory Id="ProgramFilesFolder">
20 <Directory Id="INSTALLFOLDER" Name="MsiPackageInstance" />
21 </StandardDirectory>
22 </Fragment>
23</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
5 <Component>
6 <File Source="test.txt" />
7 </Component>
8 </ComponentGroup>
9 </Fragment>
10</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
3
4 <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String>
5 <String Id="FeatureTitle">MsiPackage</String>
6
7</WixLocalization>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="ja-JP">
3
4 <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String>
5 <String Id="FeatureTitle">MsiPackage</String>
6
7</WixLocalization>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl">
3
4 <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String>
5 <String Id="FeatureTitle">MsiPackage</String>
6
7</WixLocalization>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Name="~DefaultLanguagePackage" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a">
3
4 <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" />
5
6 <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)">
7 <Component Directory="INSTALLFOLDER">
8 <File Source="test.txt" />
9 </Component>
10 </Feature>
11 </Package>
12
13 <Fragment>
14 <StandardDirectory Id="ProgramFilesFolder">
15 <Directory Id="INSTALLFOLDER" Name="Example Corporation\MsiPackage" />
16 </StandardDirectory>
17 </Fragment>
18</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" SummaryInformationCodepage="1252" Culture="ja-JP">
3
4 <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String>
5 <String Id="FeatureTitle">MsiPackage</String>
6
7</WixLocalization>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents">
5 <ComponentGroupRef Id="MinimalComponentGroup"></ComponentGroupRef>
6 <Component Id="MiscComponent" Guid="D1414BA5-F8DE-4979-938D-C8D0F61A62C9" Directory="INSTALLFOLDER">
7 <CreateFolder>
8 <Permission User="Administrator"></Permission>
9 </CreateFolder>
10 </Component>
11 </ComponentGroup>
12 </Fragment>
13</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2
3<!--
4This file contains the declaration of all the localizable strings.
5-->
6<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
7
8 <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String>
9 <String Id="FeatureTitle">MsiPackage</String>
10
11</WixLocalization>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Name="MsiPackage" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="no" InstallerVersion="200" Scope="perMachine">
3
4
5 <Upgrade Id="01120000-00E0-0000-0000-0000000FF1CE">
6 <UpgradeVersion ExcludeLanguages="no" IgnoreRemoveFailure="yes" IncludeMaximum="no" IncludeMinimum="yes" Maximum="13.0.0" Minimum="12.0.0" OnlyDetect="no" Property="BLAHBLAHBLAH" />
7 </Upgrade>
8 <!--<Property Id="BLAHBLAHBLAH" Secure="yes" />-->
9
10 <InstallExecuteSequence>
11 <RemoveExistingProducts After="InstallValidate" />
12 </InstallExecuteSequence>
13
14 <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)">
15 <ComponentGroupRef Id="ProductComponents" />
16 </Feature>
17 </Package>
18
19 <Fragment>
20 <StandardDirectory Id="ProgramFilesFolder">
21 <Directory Id="INSTALLFOLDER" Name="MsiPackage" />
22 </StandardDirectory>
23 </Fragment>
24</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
5 <Component>
6 <File Source="test.txt" />
7 </Component>
8 </ComponentGroup>
9 </Fragment>
10</Wix>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Name="~MultiMedia" Codepage="1252" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation"
3 UpgradeCode="12E4699F-E774-4D05-8A01-5BDD41BBA127">
4
5 <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
6
7 <Media Id="1" Cabinet="cab1.cab" />
8 <Media Id="2" Cabinet="cab2.cab" />
9
10 <Feature Id="ProductFeature" Title="MsiPackageTitle">
11 <Component Directory="ProgramFilesFolder" Subdirectory="~MultiMedia" DiskId="1">
12 <File Source="a1.txt" />
13 </Component>
14
15 <Component Directory="ProgramFilesFolder" Subdirectory="~MultiMedia" DiskId="1">
16 <File Source="a2.txt" />
17 </Component>
18
19 <Component Directory="ProgramFilesFolder" Subdirectory="~MultiMedia" DiskId="2">
20 <File Source="b2.txt" />
21 </Component>
22
23 <Component Directory="ProgramFilesFolder" Subdirectory="~MultiMedia" DiskId="2">
24 <File Source="b1.txt" />
25 </Component>
26 </Feature>
27 </Package>
28</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents">
5 <ComponentGroupRef Id="MinimalComponentGroup" />
6 </ComponentGroup>
7 </Fragment>
8</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents">
5 <ComponentGroupRef Id="MinimalComponentGroup" />
6 </ComponentGroup>
7 </Fragment>
8</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents">
5 <ComponentGroupRef Id="MinimalComponentGroup" />
6 </ComponentGroup>
7 </Fragment>
8</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents">
5 <ComponentGroupRef Id="MinimalComponentGroup" />
6 </ComponentGroup>
7 </Fragment>
8</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <MsiPackage SourceFile="FirstX64\" Name="FirstX64\FirstX64.msi" />
6 <RollbackBoundary Transaction="yes" />
7 <MsiPackage SourceFile="FirstX86\" Name="FirstX86\FirstX86.msi" />
8 <MsiPackage SourceFile="SecondX86\" Name="SecondX86\SecondX86.msi" />
9 <MsiPackage SourceFile="SecondX64\" Name="SecondX64\SecondX64.msi" />
10 </PackageGroup>
11 </Fragment>
12</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <MsiPackage SourceFile="FirstX86\" Name="FirstX86\FirstX86.msi" />
6 <RollbackBoundary Transaction="yes" />
7 <MsiPackage SourceFile="FirstX64\" Name="FirstX64\FirstX64.msi" />
8 <MsiPackage SourceFile="SecondX64\" Name="SecondX64\SecondX64.msi" />
9 <MsiPackage SourceFile="SecondX86\" Name="SecondX86\SecondX86.msi" />
10 </PackageGroup>
11 </Fragment>
12</Wix>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Bundle Name="BurnBundle" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="B94478B1-E1F3-4700-9CE8-6AA090854AEC">
3 <BootstrapperApplication>
4 <BootstrapperApplicationDll SourceFile="fakeba.dll" />
5 </BootstrapperApplication>
6
7 <Chain>
8 <MsuPackage DetectCondition="DetectedTheMsu" SourceFile="test.msu" />
9 </Chain>
10 </Bundle>
11</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2
3<!--
4This file contains the declaration of all the localizable strings.
5-->
6<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
7
8 <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String>
9 <String Id="FeatureTitle">MsiPackage</String>
10
11</WixLocalization>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Name="MsiPackage" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="yes" InstallerVersion="200" Scope="perMachine">
3
4
5 <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" />
6
7 <?ifndef MediaTemplateCompressionLevel?>
8 <Media Id="1" Cabinet="example1.cab" />
9 <Media Id="2" Cabinet="example2.cab" />
10 <?elseif $(MediaTemplateCompressionLevel) = ""?>
11 <MediaTemplate />
12 <?else?>
13 <MediaTemplate CabinetTemplate="lowcab{0}.cab" CompressionLevel="$(MediaTemplateCompressionLevel)" />
14 <?endif?>
15
16 <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)">
17 <ComponentGroupRef Id="ProductComponents" />
18 </Feature>
19 </Package>
20
21 <Fragment>
22 <StandardDirectory Id="ProgramFilesFolder">
23 <Directory Id="INSTALLFOLDER" Name="MsiPackage" />
24 </StandardDirectory>
25 </Fragment>
26</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
5 <Component>
6 <File Source="$(env.WINDIR)\Notepad.exe" />
7 </Component>
8 <Component>
9 <File Source="test.txt" />
10 </Component>
11 </ComponentGroup>
12 </Fragment>
13</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2
3<!--
4This file contains the declaration of all the localizable strings.
5-->
6<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
7
8 <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String>
9 <String Id="FeatureTitle">MsiPackage</String>
10
11</WixLocalization>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Name="MsiPackage" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="no" InstallerVersion="200" Scope="perMachine">
3
4
5 <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" />
6
7 <InstallExecuteSequence>
8 <ValidateProductID Suppress="yes" />
9 </InstallExecuteSequence>
10
11 <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)">
12 <ComponentGroupRef Id="ProductComponents" />
13 <ComponentGroupRef Id="Foo1" />
14 <ComponentGroupRef Id="Foo2" />
15 </Feature>
16
17 <!--<CustomActionRef Id="SetFoo" />-->
18
19 </Package>
20
21 <Fragment Id="SetFoo">
22 <CustomAction Id="SetFoo" Property="FOO" Value="BOB" />
23 <CustomAction Id="SetBar" Property="BAR" Value="BOB" />
24 </Fragment>
25
26 <Fragment Id="Foo1">
27 <ComponentGroup Id="Foo1" />
28
29 <InstallExecuteSequence>
30 <Custom Action="SetFoo" Before="SetBar" />
31 <Custom Action="SetBar" Overridable="yes" Before="AppSearch" />
32 </InstallExecuteSequence>
33 </Fragment>
34
35 <Fragment Id="Foo2">
36 <ComponentGroup Id="Foo2" />
37
38 <InstallExecuteSequence>
39 <Custom Action="SetBar" Before="AppSearch" />
40 </InstallExecuteSequence>
41 </Fragment>
42
43 <Fragment Id="Directories">
44 <StandardDirectory Id="ProgramFilesFolder">
45 <Directory Id="INSTALLFOLDER" Name="MsiPackage" />
46 </StandardDirectory>
47 </Fragment>
48</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
5 <Component>
6 <File Source="test.txt" />
7 </Component>
8 </ComponentGroup>
9 </Fragment>
10</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <MsuPackage Id="MissingSourceFileAndHash" Permanent="yes" DetectCondition="none">
6 <MsuPackagePayload DownloadUrl="example.com" />
7 </MsuPackage>
8 </PackageGroup>
9 </Fragment>
10</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <MsiPackage Id="MissingSourceFileAndName">
6 <MsiPackagePayload DownloadUrl="example.com" />
7 </MsiPackage>
8 </PackageGroup>
9 </Fragment>
10</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <ExePackage Id="PackagePayloadInPayloadGroup" Permanent="yes" DetectCondition="none">
6 <PayloadGroupRef Id="PackagePayloadGroup" />
7 </ExePackage>
8 </PackageGroup>
9 </Fragment>
10 <Fragment>
11 <PayloadGroup Id="PackagePayloadGroup">
12 <ExePackagePayload SourceFile="burn.exe" />
13 </PayloadGroup>
14 </Fragment>
15</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <MspPackage Id="SpecifiedHash">
6 <MspPackagePayload SourceFile="example.msp" DownloadUrl="example.com" Hash="abcd" />
7 </MspPackage>
8 </PackageGroup>
9 </Fragment>
10</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <MsuPackage Id="SpecifiedHashAndMissingDownloadUrl" Permanent="yes" DetectCondition="none">
6 <MsuPackagePayload Name="example.msu" Hash="abcd" Size="1" Version="1.0.0.0" ProductName="KB1234567" Description="fake msu" />
7 </MsuPackage>
8 </PackageGroup>
9 </Fragment>
10</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <ExePackage Id="SpecifiedSourceFileAndHash" Permanent="yes" DetectCondition="none">
6 <ExePackagePayload SourceFile="example.exe" Hash="abcd" />
7 </ExePackage>
8 </PackageGroup>
9 </Fragment>
10</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <MsiPackage Id="WrongPackagePayloadInPayloadGroup">
6 <PayloadGroupRef Id="WrongPackagePayloadGroup" />
7 </MsiPackage>
8 </PackageGroup>
9 </Fragment>
10 <Fragment>
11 <PayloadGroup Id="WrongPackagePayloadGroup">
12 <ExePackagePayload SourceFile="burn.exe" />
13 </PayloadGroup>
14 </Fragment>
15</Wix>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Name="~Test Package" Version="$(var.V)" Manufacturer="Example Corporation" Language="1033" UpgradeCode="e703bf17-4765-444c-91fd-88550fa681d4" Scope="perMachine" ProductCode="e703bf17-4765-444c-91fd-88550fa681d4">
3
4
5 <MajorUpgrade DowngradeErrorMessage="Newer version already installed." />
6
7 <Directory Id="TARGETDIR" Name="SourceDir">
8 <Directory Id="ProgramFilesFolder">
9 <Directory Id="INSTALLFOLDER" Name="~Test App" />
10 </Directory>
11 </Directory>
12
13 <Feature Id="Main">
14 <ComponentGroupRef Id="Components" />
15 </Feature>
16 </Package>
17
18 <Fragment>
19 <ComponentGroup Id="Components" Directory="INSTALLFOLDER">
20 <Component Id="A">
21 <File Id="a.txt" Name="a.txt" Source="Av$(var.A).txt" />
22 </Component>
23 <Component Id="B">
24 <File Id="b.txt" Name="b.txt" Source="Bv$(var.B).txt" />
25 </Component>
26 </ComponentGroup>
27 </Fragment>
28</Wix>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Patch AllowRemoval="yes" Manufacturer="FireGiant" MoreInfoURL="http://www.example.com/" DisplayName="~Test Patch v$(var.V)" Description="~Test Small Update Patch v($var.V)" Classification="Update">
3
4 <Media Id="1" Cabinet="foo.cab">
5 <PatchBaseline Id="RTM" />
6 </Media>
7
8 <PatchFamilyRef Id="SamplePatchFamily" />
9 </Patch>
10
11 <Fragment>
12 <PatchFamily Id="SamplePatchFamily" Version="$(var.V)" Supersede="yes">
13 <ComponentRef Id="A" />
14 </PatchFamily>
15 </Fragment>
16</Wix>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Name="~Test Package" Version="$(var.V)" Manufacturer="Example Corporation" Language="1033" UpgradeCode="{6B8097B9-A5D0-4BDE-B21E-AF6622DDCA01}" Scope="perMachine" ProductCode="{7C871EC1-1F89-4850-A6A9-D7A4C21769F6}">
3 <MajorUpgrade DowngradeErrorMessage="Newer version already installed." />
4 <MediaTemplate EmbedCab="yes" />
5
6 <CustomAction Id="CAFromExtension" DllEntry="DoesntExist" BinaryRef="BinFromWir" />
7
8 <Directory Id="TARGETDIR" Name="SourceDir">
9 <Directory Id="ProgramFilesFolder">
10 <Directory Id="INSTALLFOLDER" Name="~Test App" />
11 </Directory>
12 </Directory>
13
14 <Feature Id="Main">
15 <ComponentGroupRef Id="Components" />
16 </Feature>
17 </Package>
18
19 <Fragment>
20 <ComponentGroup Id="Components" Directory="INSTALLFOLDER">
21 <Component>
22 <File Source="$(sys.SOURCEFILEPATH)" />
23 </Component>
24
25 <Component>
26 <RegistryValue Root="HKLM" Key="SOFTWARE\!(bind.property.ProductName)\Patch" Name="Version" Value="$(var.V)" />
27 </Component>
28 </ComponentGroup>
29 </Fragment>
30</Wix>
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 @@
1<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>
2 <Patch
3 AllowRemoval="yes"
4 DisplayName="~Test Patch v$(var.V)"
5 Description="~Test Small Update Patch v$(var.V)"
6 MoreInfoURL="http://www.example.com/"
7 Manufacturer="Example Corporation"
8 Classification="Update">
9
10 <Media Id="1" Cabinet="foo.cab">
11 <PatchBaseline Id="RTM" BaselineFile="Baseline.wixpdb" UpdateFile="Update.wixpdb" />
12 </Media>
13
14 <PatchFamily Id='SequenceFamily' Version='$(var.V)' />
15 </Patch>
16</Wix>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Name="~Test Package" Version="$(V)" Manufacturer="Example Corporation" Language="1033" UpgradeCode="7d326855-e790-4a94-8611-5351f8321fca" Compressed="yes" Scope="perMachine" ProductCode="7d326855-e790-4a94-8611-5351f8321fca">
3
4 <MajorUpgrade DowngradeErrorMessage="Newer version already installed." />
5 <MediaTemplate EmbedCab="yes" />
6
7 <StandardDirectory Id="ProgramFilesFolder">
8 <Directory Id="INSTALLFOLDER" Name="~Test App" />
9 </StandardDirectory>
10
11 <Feature Id="Main">
12 <ComponentGroupRef Id="Components" />
13 </Feature>
14 </Package>
15
16 <Fragment>
17 <ComponentGroup Id="Components" Directory="INSTALLFOLDER">
18 <Component>
19 <File Id="a.txt" Name="a.txt" Source="A.txt" />
20 </Component>
21
22 <Component>
23 <RegistryValue Root="HKLM" Key="SOFTWARE\!(bind.property.ProductName)\Patch" Name="Version" Value="$(V)" />
24 </Component>
25 </ComponentGroup>
26 </Fragment>
27</Wix>
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 @@
1<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>
2 <Patch
3 AllowRemoval="yes"
4 DisplayName="~Test Patch v$(V)"
5 Description="~Test Small Update Patch v$(V)"
6 MoreInfoURL="http://www.example.com/"
7 Manufacturer="Example Corporation"
8 Classification="Update">
9
10 <Media Id="1" Cabinet="foo.cab">
11 <PatchBaseline Id="RTM" BaselineFile="Baseline.wixpdb" UpdateFile="Update.wixpdb" />
12 </Media>
13
14 <PatchFamily Id='SequenceFamily' Version='$(V)' />
15 </Patch>
16</Wix>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Fragment>
3 <PackageGroup Id="BundlePackages">
4 <MspPackage Id="PatchA" SourceFile="PatchA.msp" PerMachine="yes" />
5 </PackageGroup>
6 </Fragment>
7</Wix>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Fragment>
3 <PackageGroup Id="BundlePackages">
4 <MspPackage Id="PatchA" SourceFile="PatchA.msp" PerMachine="yes" />
5 <MspPackage Id="PatchB" SourceFile="PatchB.msp" PerMachine="yes" />
6 </PackageGroup>
7 </Fragment>
8</Wix>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Fragment>
3 <PackageGroup Id="BundlePackages">
4 <MspPackage Id="PatchA" SourceFile="PatchA.msp" PerMachine="yes" />
5 <MspPackage Id="PatchB" SourceFile="PatchB.msp" PerMachine="yes" />
6 <MspPackage Id="PatchC" SourceFile="PatchC.msp" PerMachine="yes" />
7 </PackageGroup>
8 </Fragment>
9</Wix>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package ProductCode="26309973-0A5E-4979-B142-98A6E064EDC0" Name="PackageA" Language="1033" Version="$(var.V)" Manufacturer="Example Corporation"
3 UpgradeCode="32B0396A-CE36-4570-B16E-F88FA42DC409" Scope="perMachine" Compressed="yes">
4
5 <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
6 <MediaTemplate EmbedCab="yes" />
7
8 <PropertyRef Id="TestVersion"/>
9
10 <Feature Id="Complete" Level="1">
11 <ComponentRef Id="FileComponent"/>
12 <ComponentRef Id="RegistryComponent"/>
13 <ComponentRef Id="RegistryComponent2" />
14 </Feature>
15 </Package>
16
17 <Fragment>
18 <StandardDirectory Id="ProgramFilesFolder">
19 <Directory Id="INSTALLFOLDER" Name="~Test A" />
20 </StandardDirectory>
21 </Fragment>
22
23 <Fragment>
24 <Component Id="FileComponent" Directory="INSTALLFOLDER">
25 <File Source="$(sys.SOURCEFILEPATH)"/>
26 </Component>
27 </Fragment>
28
29 <Fragment>
30 <Component Id="RegistryComponent" Directory="INSTALLFOLDER">
31 <RegistryValue Root="HKLM" Key="Software\WiX\Tests\$(var.A)" Name="A" Value="!(bind.Property.TestVersion)" Type="string" />
32 </Component>
33 </Fragment>
34
35 <Fragment>
36 <Component Id="RegistryComponent2" Directory="INSTALLFOLDER">
37 <RegistryValue Root="HKLM" Key="Software\WiX\Tests\$(var.B)" Name="A2" Value="!(bind.Property.TestVersion)" Type="string" />
38 </Component>
39 </Fragment>
40
41 <Fragment>
42 <Property Id="TestVersion" Value="$(var.V)"/>
43 </Fragment>
44</Wix>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Patch AllowRemoval="yes" Classification="Update" ClientPatchId="PatchA" Description="Patch A" DisplayName="Patch A" Manufacturer="Example Corporation" MinorUpdateTargetRTM="yes">
3 <Media Id="100" Cabinet="A" EmbedCab="yes">
4 <PatchBaseline Id="PatchA" BaselineFile="Baseline.wixpdb" UpdateFile="Update.wixpdb" />
5 </Media>
6
7 <PatchFamily Id="A" Version="$(var.V)" Supersede="yes">
8 <ComponentRef Id="RegistryComponent" />
9 <PropertyRef Id="TestVersion" />
10 </PatchFamily>
11 </Patch>
12</Wix>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Patch AllowRemoval="yes" Classification="Update" ClientPatchId="PatchB" Description="Patch B" DisplayName="Patch B" Manufacturer="Example Corporation" MinorUpdateTargetRTM="yes">
3 <Media Id="100" Cabinet="B" EmbedCab="yes">
4 <PatchBaseline Id="PatchB" BaselineFile="Baseline.wixpdb" UpdateFile="Update.wixpdb">
5 <Validate ProductId="no" />
6 </PatchBaseline>
7 </Media>
8
9 <PatchFamily Id="B" Version="$(var.V)" Supersede="yes">
10 <ComponentRef Id="RegistryComponent" />
11 <PropertyRef Id="TestVersion" />
12 </PatchFamily>
13 </Patch>
14</Wix>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Patch AllowRemoval="yes" Classification="Update" ClientPatchId="PatchC" Description="Patch C" DisplayName="Patch C" Manufacturer="Example Corporation" MinorUpdateTargetRTM="yes">
3 <Media Id="100" Cabinet="C" EmbedCab="yes">
4 <PatchBaseline Id="PatchC" BaselineFile="Baseline.wixpdb" UpdateFile="Update.wixpdb">
5 <Validate ProductId="no" UpgradeCode="no" />
6 </PatchBaseline>
7 </Media>
8
9 <PatchFamily Id="C" Version="$(var.V)" Supersede="yes">
10 <ComponentRef Id="RegistryComponent" />
11 <PropertyRef Id="TestVersion" />
12 </PatchFamily>
13 </Patch>
14</Wix>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Fragment>
3 <PackageGroup Id="BundlePackages">
4 <MsiPackage Id="PackageA" SourceFile="Baseline.msi">
5 <SlipstreamMsp Id="PatchA" />
6 </MsiPackage>
7 <MspPackage Id="PatchA" SourceFile="Patch1.msp" />
8 </PackageGroup>
9 </Fragment>
10</Wix>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Name="~Test Package" Version="$(var.V)" Manufacturer="Example Corporation" Language="1033" UpgradeCode="7d326855-e790-4a94-8611-5351f8321fca" Compressed="yes" Scope="perMachine" ProductCode="7d326855-e790-4a94-8611-5351f8321fca">
3
4
5 <MajorUpgrade DowngradeErrorMessage="Newer version already installed." />
6 <MediaTemplate EmbedCab="yes" />
7
8 <StandardDirectory Id="ProgramFilesFolder">
9 <Directory Id="INSTALLFOLDER" Name="~Test A" />
10 </StandardDirectory>
11
12 <Feature Id="Main">
13 <ComponentGroupRef Id="Components" />
14 </Feature>
15 </Package>
16
17 <Fragment>
18 <ComponentGroup Id="Components" Directory="INSTALLFOLDER">
19 <Component>
20 <File Id="a.txt" Name="a.txt" Source="Av$(var.A).txt" />
21 </Component>
22 <Component>
23 <File Id="b.txt" Name="b.txt" Source="Bv$(var.B).txt" />
24 </Component>
25 </ComponentGroup>
26 </Fragment>
27</Wix>
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 @@
1<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>
2 <Patch
3 AllowRemoval="yes"
4 DisplayName="~Test Patch v$(var.V)"
5 Description="~Test Small Update Patch v$(var.V)"
6 MoreInfoURL="http://www.example.com/"
7 Manufacturer="Example Corporation"
8 Classification="Update">
9
10 <Media Id="1" Cabinet="foo.cab">
11 <PatchBaseline Id="RTM" BaselineFile="Baseline.wixpdb" UpdateFile="Update.wixpdb" />
12 </Media>
13
14 <PatchFamily Id='SequenceFamily' Version='$(var.V)' />
15 </Patch>
16</Wix>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Fragment>
3 <PayloadGroup Id="AbsoluteName">
4 <Payload SourceFile="dir\file.ext" Name="\\server\share\target.file" />
5 <Payload SourceFile="dir\file.ext" Name="C:\target.file" />
6 <Payload SourceFile="dir\file.ext" Name="\dir\target.file" />
7 </PayloadGroup>
8 </Fragment>
9</Wix>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Fragment>
3 <PayloadGroup Id="CanonicalizeName">
4 <Payload SourceFile="dir\file.ext" Name="a\..\c\.\d.exe" />
5 </PayloadGroup>
6 </Fragment>
7</Wix>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Bundle Name="DownloadUrlPlaceholders" Version="1.0.0.0" Manufacturer="test" UpgradeCode="{B04C20B8-70C3-4DE1-8D91-4F11C7C68DED}">
3 <BootstrapperApplicationRef Id="fakeba" />
4
5 <Chain>
6 <PackageGroupRef Id="ContainerPackages" />
7 <PackageGroupRef Id="UncompressedPackages" />
8 </Chain>
9
10 <PayloadGroupRef Id="LayoutOnlyPayloads" />
11 <Container Id="PackagesContainer" Name="packages.cab" DownloadUrl="http://example.com/{0}id/{1}/{2}">
12 <PackageGroupRef Id="ContainerPackages" />
13 </Container>
14 </Bundle>
15 <Fragment>
16 <PackageGroup Id="ContainerPackages">
17 <ExePackage SourceFile="burn.exe" DetectCondition="none" Compressed="no" />
18 </PackageGroup>
19 </Fragment>
20 <Fragment>
21 <PackageGroup Id="UncompressedPackages">
22 <MsiPackage SourceFile="test.msi" DownloadUrl="http://example.com/{0}id/{1}/{2}" Compressed="no" />
23 </PackageGroup>
24 </Fragment>
25 <Fragment>
26 <PayloadGroup Id="LayoutOnlyPayloads">
27 <Payload Id="LayoutOnlyPayload" SourceFile="$(sys.SOURCEFILEPATH)" DownloadUrl="http://example.com/{0}id/{1}/{2}" Compressed="no" />
28 </PayloadGroup>
29 </Fragment>
30</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <ExePackage SourceFile="burn.exe" DetectCondition="none">
6 <PayloadGroupRef Id="Shared" />
7 </ExePackage>
8 </PackageGroup>
9 <BootstrapperApplication>
10 <PayloadGroupRef Id="Shared" />
11 </BootstrapperApplication>
12 <PayloadGroup Id="Shared">
13 <Payload SourceFile="$(sys.SOURCEFILEPATH)" />
14 </PayloadGroup>
15 </Fragment>
16</Wix>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Fragment>
3 <PayloadGroup Id="ValidName">
4 <Payload SourceFile="dir\file.ext" Name="dir\file.ext" />
5 </PayloadGroup>
6 </Fragment>
7</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8" ?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <?define Test = "$(env.CommonProgramFiles(x86))" ?>
4</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2
3<!--
4This file contains the declaration of all the localizable strings.
5-->
6<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
7
8 <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String>
9 <String Id="FeatureTitle">MsiPackage</String>
10
11</WixLocalization>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
5 <Component>
6 <File Source="example.txt" />
7 </Component>
8 </ComponentGroup>
9 </Fragment>
10</Wix>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package ProductCode="8738B0C5-C4AA-4634-8C03-11EAA2F1E15D" Name="~TagTestPackage" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a">
3
4 <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" />
5
6 <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)">
7 <ComponentGroupRef Id="ProductComponents" />
8 </Feature>
9
10 <SoftwareTag Regid="wixtoolset.org" InstallDirectory="INSTALLFOLDER" />
11 </Package>
12
13 <Fragment>
14 <StandardDirectory Id="ProgramFilesFolder">
15 <Directory Id="INSTALLFOLDER" Name="MsiPackage" />
16 </StandardDirectory>
17 </Fragment>
18</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8" ?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="MinimalComponentGroup" Directory="INSTALLFOLDER">
5 <Component>
6 <File Source="test.txt" />
7 </Component>
8 </ComponentGroup>
9 </Fragment>
10</Wix>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <?ifndef ProductCode?>
3 <?define ProductCode = *?>
4 <?endif?>
5 <Package Name="MsiPackage" Codepage="1252" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="12E4699F-E774-4D05-8A01-5BDD41BBA127" Compressed="no" Scope="perMachine" ProductCode="$(var.ProductCode)">
6
7 <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
8
9 <Feature Id="ProductFeature" Title="MsiPackageTitle">
10 <ComponentGroupRef Id="ProductComponents" />
11 </Feature>
12 </Package>
13
14 <Fragment>
15 <StandardDirectory Id="ProgramFiles6432Folder">
16 <Directory Id="INSTALLFOLDER" Name="MsiPackage" />
17 </StandardDirectory>
18 </Fragment>
19</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents">
5 <Component Id="ProgIdComp" Directory="INSTALLFOLDER" Guid="5B3B3FC1-533D-4C29-BFB3-0E88B51E59D8">
6 <File Source="test.txt" Name="ProgIdComp.txt"></File>
7 <Class Id="F12A6F69-117F-471F-AE73-F8E74218F498" Advertise="yes" Context="LocalServer32" Description="FakeClassF12A" ThreadingModel="apartment" Version="0.0.0.1">
8 <ProgId Id="73E7DF7E-EFAC-4E11-90E2-6EBAEB8DE58D" NoOpen="NoOpen73E7" />
9 </Class>
10 </Component>
11 </ComponentGroup>
12 </Fragment>
13</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2
3<!--
4This file contains the declaration of all the localizable strings.
5-->
6<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
7
8 <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String>
9 <String Id="FeatureTitle">MsiPackage</String>
10
11</WixLocalization>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Name="ProgId" Codepage="1252" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="no" InstallerVersion="200" Scope="perMachine">
3
4
5 <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" />
6
7 <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)">
8 <ComponentGroupRef Id="ProductComponents" />
9 </Feature>
10 </Package>
11
12 <Fragment>
13 <StandardDirectory Id="ProgramFilesFolder">
14 <Directory Id="INSTALLFOLDER" Name="MsiPackage" />
15 </StandardDirectory>
16 </Fragment>
17</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
5 <Component>
6 <File Name="Foo.exe" Source="test.txt" />
7 <ProgId Id="Foo.File.hol.15" Advertise="yes" Description="Foo Holiday File">
8 <ProgId Id="Foo.File.hol" />
9 <Extension Id="hol">
10 <Verb Id="Open" Argument="/hol &quot;%1&quot;" Sequence="1" />
11 </Extension>
12 </ProgId>
13 </Component>
14 </ComponentGroup>
15 </Fragment>
16</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2
3<!--
4This file contains the declaration of all the localizable strings.
5-->
6<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
7
8 <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String>
9 <String Id="FeatureTitle">MsiPackage</String>
10
11</WixLocalization>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Name="MsiPackage" Codepage="1252" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="no" InstallerVersion="200" Scope="perMachine">
3 <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" />
4
5 <Feature Id="ProductFeature1" Title="!(loc.FeatureTitle)">
6 <ComponentRef Id="Component1" Primary="yes" />
7 </Feature>
8
9 <Feature Id="ProductFeature2" Title="!(loc.FeatureTitle)">
10 <ComponentRef Id="Component1" />
11 <ComponentRef Id="Component2" />
12 </Feature>
13 </Package>
14
15 <Fragment>
16 <StandardDirectory Id="ProgramFilesFolder">
17 <Directory Id="INSTALLFOLDER" Name="MsiPackage" />
18 </StandardDirectory>
19 </Fragment>
20
21 <Fragment>
22 <Component Id="Component1" Directory="INSTALLFOLDER" Guid="C8EFA5DF-2876-4724-A003-A6BEBF140BB1">
23 <File Id="File1" Source="test.txt" />
24 <Category Id="{BD245B5A-EC33-46ED-98FF-E9D3D416AD04}" AppData="AppData1" Qualifier="Qualifier1" />
25 </Component>
26 </Fragment>
27
28 <Fragment>
29 <Component Id="Component2" Directory="INSTALLFOLDER" Guid="8DE79DE7-4B55-4D43-88F5-AD6A1E8D242A">
30 <File Id="File2" Source="test.txt" />
31 <Category Id="{0A82C8F6-9CE9-4336-B8BE-91A39B5F7081}" AppData="AppData2" Qualifier="Qualifier2" />
32 </Component>
33 </Fragment>
34</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8" ?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
5 <Component>
6 <RegistryKey Root="HKLM" Key="Software\Acme\Foobar 1.0">
7 <RegistryValue Type="string" Name="InstallDir" Value="[TARGETDIR]" />
8 <RegistryValue Type="string" Name="InstallDir" Value="[INSTALLDIR]" />
9 <RegistryValue Type="string" Name="InstallDir" Value="[ProgramFilesFolder]" />
10 </RegistryKey>
11 </Component>
12 </ComponentGroup>
13 </Fragment>
14</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8" ?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
5 <Component Id="MiscComponent" Guid="7C40C257-AB36-4B8C-8FD1-C56E0AC4AAEF">
6 <RegistryKey Id="reg1" Root="HKLM" ForceCreateOnInstall="yes" ForceDeleteOnUninstall="yes" Key="Software\WBM\WB\">
7 <RegistryValue Id="reg2" Type="string" Name="InstallationPath" Value="[INSTALLFOLDER]" />
8 </RegistryKey>
9 </Component>
10 </ComponentGroup>
11 </Fragment>
12</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8" ?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
5 <Component Id="MiscComponent" Guid="7C40C257-AB36-4B8C-8FD1-C56E0AC4AAEF">
6 <RegistryValue Root="HKLM" Key="Path\To\Key" Value="1.0.1234.123" Type="string" KeyPath="yes" />
7 <RegistryValue Root="HKLM" Key="Path\To\AnotherKey" Name="Secret" Type="binary" />
8 </Component>
9 </ComponentGroup>
10 </Fragment>
11</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8" ?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
5 <Component Id="MultiStringComponent" Guid="7C40C257-AB36-4B8C-8FD1-C56E0AC4AAEF">
6 <RegistryValue Root="HKLM" Key="Path\To\Key" Type="multiString" KeyPath="yes">
7 <MultiString Value="a" />
8 <MultiStringValue Value="b" />
9 <MultiStringValue />
10 <MultiString Value="c" />
11 <MultiStringValue />
12 </RegistryValue>
13 <RegistryValue Root="HKLM" Key="Path\To\AnotherKey" Name="Secret" Type="binary" />
14 </Component>
15 </ComponentGroup>
16 </Fragment>
17</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents">
5 <Component Id="RemoveRegistryKeyComp" Directory="INSTALLFOLDER" Guid="5B3B3FC1-533D-4C29-BFB3-0E88B51E59D8">
6 <File Source="test.txt" />
7 <RemoveRegistryKey Id="RemoveAKeyName" Action="removeOnUninstall" Root="HKLM" Key="AKeyName" />
8 </Component>
9 </ComponentGroup>
10 </Fragment>
11</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents">
5 <Component Id="ReserveCostComp" Directory="INSTALLFOLDER" Guid="5B3B3FC1-533D-4C29-BFB3-0E88B51E59D8">
6 <File Source="test.txt" />
7 <ReserveCost Id="TestCost" RunFromSource="200" RunLocal="100"></ReserveCost>
8 </Component>
9 </ComponentGroup>
10 </Fragment>
11</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <RollbackBoundary Id="nonvital" Vital="no" />
6 <PackageGroupRef Id="MinimalPackageGroup" />
7 </PackageGroup>
8 </Fragment>
9</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
5 <Component Subdirectory="a">
6 <File Source="a\test.txt" />
7 </Component>
8 <Component Subdirectory="b">
9 <File Source="b\test.txt" />
10 </Component>
11 <Component Subdirectory="c">
12 <File Source="c\test.txt" />
13 </Component>
14 </ComponentGroup>
15 </Fragment>
16</Wix>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Codepage="1252" Language="1033" Manufacturer="Example Corporation" Name="MsiPackage" UpgradeCode="{12E4699F-E774-4D05-8A01-5BDD41BBA127}" Version="1.0.0.0" ProductCode="{74C29381-1915-4948-B8B4-5646806A0BD4}">
3 <CustomAction Id="CustomAction2" Property="TestAdvtExecuteSequenceProperty" Value="1" />
4 <StandardDirectory Id="ProgramFilesFolder">
5 <Directory Id="INSTALLFOLDER" Name="MsiPackage" ShortName="ykd0udtb">
6 <Component Id="test.txt" Guid="{E597A58A-03CB-50D8-93E3-DABA263F233A}" Bitness="always32">
7 <File Id="test.txt" Name="test.txt" KeyPath="yes" Source="MsiPackage\test.txt" />
8 </Component>
9 </Directory>
10 </StandardDirectory>
11 <Feature Id="ProductFeature" Level="1" Title="MsiPackageTitle">
12 <ComponentRef Id="test.txt" />
13 </Feature>
14 <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
15 <Media Id="1" />
16 <InstallExecuteSequence>
17 <Custom Action="CustomAction2" After="CostInitialize" />
18 </InstallExecuteSequence>
19 <InstallUISequence>
20 <Custom Action="CustomAction2" After="CostInitialize" />
21 </InstallUISequence>
22 <AdminExecuteSequence>
23 <Custom Action="CustomAction2" After="CostInitialize" />
24 </AdminExecuteSequence>
25 <AdminUISequence>
26 <Custom Action="CustomAction2" After="CostInitialize" />
27 </AdminUISequence>
28 <AdvertiseExecuteSequence>
29 <Custom Action="CustomAction2" After="CostInitialize" />
30 </AdvertiseExecuteSequence>
31 </Package>
32</Wix> \ 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
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SequenceTables/SequenceTables.msi
Binary files 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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents">
5 <Component Directory="INSTALLFOLDER">
6 <File Id="test.txt" Source="test.txt" />
7 <ServiceInstall Name="SampleService" ErrorControl="ignore" Start="disabled" Type="ownProcess" />
8 <ServiceControl Name="SampleService" Start="install" Stop="uninstall" Remove="uninstall" Wait="yes" />
9 </Component>
10 </ComponentGroup>
11 </Fragment>
12</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2
3<!--
4This file contains the declaration of all the localizable strings.
5-->
6<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
7
8 <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String>
9 <String Id="FeatureTitle">MsiPackage</String>
10
11</WixLocalization>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Name="MsiPackage" Codepage="1252" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="no" InstallerVersion="200" Scope="perMachine">
3
4
5 <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" />
6
7 <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)">
8 <ComponentGroupRef Id="ProductComponents" />
9 </Feature>
10
11 <SetProperty Id="INSTALLLOCATION" Value="[INSTALLFOLDER]" After="CostFinalize" />
12 </Package>
13
14 <Fragment>
15 <StandardDirectory Id="ProgramFilesFolder">
16 <Directory Id="INSTALLFOLDER" Name="MsiPackage" />
17 </StandardDirectory>
18 </Fragment>
19</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
5 <Component>
6 <File Source="test.txt" />
7 </Component>
8 </ComponentGroup>
9 </Fragment>
10</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <PackageGroupRef Id="MinimalPackageGroup" />
6 </PackageGroup>
7
8 <SetVariable Id="SetCoercedNumber" Variable="CoercedNumber" Value="2" />
9 <SetVariable Id="SetCoercedString" Variable="CoercedString" Value="Bar" />
10 <SetVariable Id="SetCoercedVersion" Variable="CoercedVersion" Value="v2.0" />
11 <SetVariable Id="SetNeedsFormatting" Variable="NeedsFormatting" Value="[One] [Two] [Three]" />
12 <SetVariable Id="SetUnset" Variable="Unset" Condition="VersionString = v2.0" After="SetVersionString" />
13 <SetVariable Id="SetVersionString" Variable="VersionString" Value="v1.0" Type="string" />
14 </Fragment>
15</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <ExePackage SourceFile="C:\Windows\system32\credwiz.exe" Permanent="yes" DetectCondition="none">
6 <PayloadGroupRef Id="SharedPayloads" />
7 </ExePackage>
8 <ExePackage SourceFile="C:\Windows\system32\cscript.exe" Permanent="yes" DetectCondition="none">
9 <PayloadGroupRef Id="SharedPayloads" />
10 </ExePackage>
11 </PackageGroup>
12 </Fragment>
13 <Fragment>
14 <PayloadGroup Id="SharedPayloads">
15 <Payload Id="SourceFilePayload" SourceFile="$(sys.SOURCEFILEPATH)" />
16 </PayloadGroup>
17 </Fragment>
18</Wix>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Codepage="1252" Language="1033" Manufacturer="Example Corporation" Name="MsiPackage" UpgradeCode="{12E4699F-E774-4D05-8A01-5BDD41BBA127}" Version="1.0.0.0" ProductCode="{6CA94D1D-B568-4ED6-9EBC-3534C85970BB}">
3 <StandardDirectory Id="ProgramFilesFolder">
4 <Directory Id="INSTALLFOLDER" Name="MsiPackage" ShortName="ykd0udtb">
5 <Component Id="ShortcutComp" Guid="{5B3B3FC1-533D-4C29-BFB3-0E88B51E59D8}" Bitness="always32">
6 <File Id="test.txt" Name="test.txt" KeyPath="yes" Source="MsiPackage\test.txt" />
7 <Shortcut Id="FileTargetShortcut" Directory="INSTALLFOLDER" Name="FileTargetShortcut" ShortName="lm2tdtqp" Target="[#test.txt]" />
8 <Shortcut Id="CustomTargetShortcut" Directory="INSTALLFOLDER" Name="Planner" ShortName="PLANNER" Target="[INSTALLFOLDER]custom.target" />
9 <Shortcut Id="AdvtShortcut" Directory="INSTALLFOLDER" Name="AdvtShortcut" ShortName="mdbqel9r" Advertise="yes" />
10 </Component>
11 </Directory>
12 </StandardDirectory>
13 <Feature Id="ProductFeature" Level="1" Title="MsiPackageTitle">
14 <ComponentRef Id="ShortcutComp" Primary="yes" />
15 </Feature>
16 <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
17 <Media Id="1" />
18 </Package>
19</Wix> \ 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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents">
5 <Component Id="ShortcutComp" Directory="INSTALLFOLDER" Guid="5B3B3FC1-533D-4C29-BFB3-0E88B51E59D8">
6 <File Source="test.txt">
7 <Shortcut Id="TheShortcut" Name="d" Directory="INSTALLFOLDER">
8 <ShortcutProperty Key="CustomShortcutKey" Value="CustomShortcutValue"></ShortcutProperty>
9 </Shortcut>
10 </File>
11 </Component>
12 </ComponentGroup>
13 </Fragment>
14</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents">
5 <Component Id="ShortcutComp" Directory="INSTALLFOLDER" Guid="5B3B3FC1-533D-4C29-BFB3-0E88B51E59D8">
6 <File Source="test.txt">
7 <Shortcut Name="DaName" ShortName="DANAME" Directory="INSTALLFOLDER" />
8 </File>
9 </Component>
10 </ComponentGroup>
11 </Fragment>
12</Wix>
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
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/Shortcut/shortcuts.msi
Binary files 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 @@
1<?xml version="1.0" encoding="utf-8"?>
2
3<!--
4This file contains the declaration of all the localizable strings.
5-->
6<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
7
8 <String Id="BundleName">~TestBundle</String>
9
10</WixLocalization>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Bundle Name="!(loc.BundleName)" Version="!(bind.packageVersion.test.msi)" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a">
3 <BootstrapperApplication>
4 <BootstrapperApplicationDll SourceFile="fakeba.dll" />
5 </BootstrapperApplication>
6 <Chain>
7 <MsiPackage SourceFile="test.msi">
8 <MsiProperty Name="TEST" Value="1" />
9 </MsiPackage>
10 </Chain>
11 </Bundle>
12</Wix>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Fragment>
3 <BootstrapperApplication Id="fakeba">
4 <BootstrapperApplicationDll SourceFile="fakeba.dll" />
5 </BootstrapperApplication>
6 </Fragment>
7</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Bundle Name="!(loc.BundleName)" Version="!(bind.packageVersion.test.msi)" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a">
4 <BootstrapperApplicationRef Id="fakeba">
5 <PayloadGroupRef Id="TestPayloadGroup" />
6 </BootstrapperApplicationRef>
7 <Chain>
8 <MsiPackage SourceFile="test.msi">
9 <MsiProperty Name="TEST" Value="1" />
10 </MsiPackage>
11 </Chain>
12 </Bundle>
13 <Fragment>
14 <PayloadGroup Id="TestPayloadGroup">
15 <Payload SourceFile="MsiPackage\test.txt" />
16 </PayloadGroup>
17 </Fragment>
18</Wix>
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
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleBundle/data/test.msi
Binary files 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
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/SimpleMerge/.data/test.msm
Binary files 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 @@
1<?xml version="1.0" encoding="utf-8"?>
2
3<!--
4This file contains the declaration of all the localizable strings.
5-->
6<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
7
8 <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String>
9 <String Id="FeatureTitle">MsiPackage</String>
10
11</WixLocalization>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Name="MsiPackage" Codepage="1252" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="yes" InstallerVersion="200" Scope="perMachine">
3
4
5 <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" />
6
7 <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)">
8 <MergeRef Id="TestMsm" />
9 </Feature>
10 </Package>
11
12 <Fragment>
13 <StandardDirectory Id="ProgramFilesFolder">
14 <Directory Id="INSTALLFOLDER" Name="MsiPackage">
15 <!-- -->
16 <Merge Id="TestMsm" Language="1033" SourceFile="test.msm" />
17 </Directory>
18 </StandardDirectory>
19 </Fragment>
20</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2
3<!--
4This file contains the declaration of all the localizable strings.
5-->
6<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
7
8 <String Id="Manufacturer">Example Company</String>
9
10</WixLocalization>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3 <PropertyGroup>
4 <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
5 <Platform Condition=" '$(Platform)' == '' ">x86</Platform>
6 <ProductVersion>0.9</ProductVersion>
7 <ProjectGuid>27df04c6-3cef-4b9a-bac6-4e78d188384f</ProjectGuid>
8 <OutputName>MergeModule1</OutputName>
9 <OutputType>Module</OutputType>
10 <Name>MergeModule1</Name>
11 <RootNamespace>MergeModule1</RootNamespace>
12 </PropertyGroup>
13 <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
14 <PlatformName>$(Platform)</PlatformName>
15 <OutputPath>bin\$(Platform)\$(Configuration)\</OutputPath>
16 <DefineConstants>Debug</DefineConstants>
17 </PropertyGroup>
18 <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
19 <PlatformName>$(Platform)</PlatformName>
20 <OutputPath>bin\$(Platform)\$(Configuration)\</OutputPath>
21 </PropertyGroup>
22 <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' ">
23 <PlatformName>$(Platform)</PlatformName>
24 <OutputPath>bin\$(Platform)\$(Configuration)\</OutputPath>
25 <DefineConstants>Debug</DefineConstants>
26 </PropertyGroup>
27 <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x64' ">
28 <PlatformName>$(Platform)</PlatformName>
29 <OutputPath>bin\$(Platform)\$(Configuration)\</OutputPath>
30 </PropertyGroup>
31 <ItemGroup>
32 <Compile Include="MergeModule.wxs" />
33 </ItemGroup>
34 <ItemGroup>
35 <EmbeddedResource Include="MergeModule.en-us.wxl" />
36 </ItemGroup>
37 <ItemGroup>
38 <WixExtension Include="FgwepExtension.wixext">
39 <Name>FgwepExtension.wixext</Name>
40 <HintPath>$(WixExtDir)\FgwepExtension.wixext.dll</HintPath>
41 </WixExtension>
42 </ItemGroup>
43 <Import Project="$(WixTargetsPath)" Condition=" '$(WixTargetsPath)' != '' " />
44 <Import Project="$(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\wix.targets" Condition=" '$(WixTargetsPath)' == '' AND Exists('$(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\wix.targets') " />
45 <Target Name="EnsureWixToolsetInstalled" Condition=" '$(WixTargetsImported)' != 'true' ">
46 <Error Text="FG-WiX or WiX Toolset build tools (v3.11 or later) must be installed to build this project. To download FG-WiX, go to https://www.firegiant.com/downloads/. To download the WiX Toolset, go to http://wixtoolset.org/releases/." />
47 </Target>
48</Project> \ 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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Module Id="MergeModule1" Language="1033" Version="1.0.0.0" Guid="243FB739-4D05-472F-9CFB-EF6B1017B6DE" InstallerVersion="200">
3 <SummaryInformation Manufacturer="!(loc.Manufacturer)" />
4
5 <Directory Id="MergeRedirectFolder">
6 <Component Id="ModuleComponent1" Guid="A04E61B2-3ED4-4803-B2EB-4B773576FA45">
7 <File Id="File1" Source="test.txt" />
8 </Component>
9 </Directory>
10
11 <Directory Id="NotTheMergeRedirectFolder">
12 <Component Id="ModuleComponent2" Guid="EADB3047-BD32-417B-AABF-B8D9CCDC22DA">
13 <File Id="File2" Source="test.txt" />
14 </Component>
15 </Directory>
16 </Module>
17</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <ExePackage DetectCondition="DetectedSomething" SourceFile="burn.exe" />
6 </PackageGroup>
7 </Fragment>
8</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <ExePackage
6 InstallArguments="/q /norestart &quot;[WixBundleName]&quot; /log &quot;[NetFx462FullLog].html&quot;"
7 UninstallArguments="/uninstall /q /norestart &quot;[WixBundleName]&quot; /log &quot;[NetFx462FullLog].html&quot;"
8 PerMachine="yes"
9 DetectCondition="A"
10 InstallCondition="B"
11 Id="NetFx462Web"
12 Vital="yes"
13 Permanent="yes"
14 Protocol="netfx4"
15 LogPathVariable="NetFx462FullLog">
16 <ExePackagePayload
17 DownloadUrl="C"
18 Name="NDP462-KB3151802-Web.exe"
19 Description="Microsoft .NET Framework 4.6.2 Setup"
20 Hash="C42E6ED280290648BBD59F664008852F4CFE4548"
21 ProductName="Microsoft .NET Framework 4.6.2"
22 Size="9223372036854775807"
23 Version="4.6.1590.0" />
24 </ExePackage>
25 </PackageGroup>
26 </Fragment>
27</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2
3<!--
4This file contains the declaration of all the localizable strings.
5-->
6<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
7
8 <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String>
9 <String Id="FeatureTitle">MsiPackage</String>
10
11</WixLocalization>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Name="MsiPackage" Codepage="1252" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="no" InstallerVersion="200" Scope="perMachine">
3
4
5 <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" />
6
7 <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)">
8 <ComponentGroupRef Id="ProductComponents" />
9 </Feature>
10 </Package>
11
12 <Fragment>
13 <StandardDirectory Id="ProgramFilesFolder">
14 <Directory Id="INSTALLFOLDER" Name="MsiPackage" />
15 </StandardDirectory>
16 </Fragment>
17</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
5 <Component>
6 <File Source="test.txt" />
7 </Component>
8 <Component Id="Shared.dll" Shared="yes">
9 <File Name="Shared.dll" Source="test.txt" />
10 </Component>
11 </ComponentGroup>
12 </Fragment>
13</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2
3<!--
4This file contains the declaration of all the localizable strings.
5-->
6<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
7
8 <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String>
9 <String Id="FeatureTitle">MsiPackage</String>
10
11</WixLocalization>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Name="MsiPackage" Codepage="65001" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="yes" InstallerVersion="200" Scope="perMachine">
3
4
5 <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" />
6
7 <?ifndef MediaTemplateCompressionLevel?>
8 <Media Id="1" Cabinet="example.cab" />
9 <?elseif $(MediaTemplateCompressionLevel) = ""?>
10 <MediaTemplate />
11 <?else?>
12 <MediaTemplate CabinetTemplate="low{0}.cab" CompressionLevel="$(MediaTemplateCompressionLevel)" />
13 <?endif?>
14
15 <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)">
16 <ComponentGroupRef Id="ProductComponents" />
17 </Feature>
18 </Package>
19
20 <Fragment>
21 <StandardDirectory Id="ProgramFilesFolder">
22 <Directory Id="INSTALLFOLDER" Name="MsiPackage" />
23 </StandardDirectory>
24 </Fragment>
25</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
5 <Component>
6 <File Source="test.txt" />
7 </Component>
8 </ComponentGroup>
9 </Fragment>
10</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2
3<!--
4This file contains the declaration of all the localizable strings.
5-->
6<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
7
8 <String Id="Manufacturer">Example Company</String>
9
10</WixLocalization>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Module Id="MergeModule1" Language="1033" Version="1.0.0.0" Guid="243FB739-4D05-472F-9CFB-EF6B1017B6DE">
3 <SummaryInformation Manufacturer="!(loc.Manufacturer)" />
4
5 <Property Id="Test" Hidden="true" SuppressModularization="true" />
6 <CustomAction Id="Test" DllEntry="TestEntry" Execute="deferred" Return="check" Impersonate="no" HideTarget="yes" SuppressModularization="yes" BinaryRef="FakeCA" />
7
8 <Binary Id="FakeCA" SourceFile="test.txt" />
9 </Module>
10</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents">
5 <ComponentGroupRef Id="MinimalComponentGroup"></ComponentGroupRef>
6 </ComponentGroup>
7
8 <UI Id="CustomUI">
9 <TextStyle Id="FirstTextStyle" FaceName="Arial" Size="2" />
10 </UI>
11 </Fragment>
12</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2
3<!--
4This file contains the declaration of all the localizable strings.
5-->
6<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
7
8 <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String>
9 <String Id="FeatureTitle">MsiPackage</String>
10 <String Id="CustomFontName"><!-- NameComment -->Tahoma</String>
11 <String Id="CustomFontSize"><!-- SizeComment -->8</String>
12
13</WixLocalization>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents">
5 <ComponentGroupRef Id="MinimalComponentGroup"></ComponentGroupRef>
6 </ComponentGroup>
7
8 <UI Id="CustomUI">
9 <TextStyle Id="CustomFont" FaceName="!(loc.CustomFontName)" Size="!(loc.CustomFontSize)" />
10 </UI>
11 </Fragment>
12</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents">
5 <Component Id="TypeLibComp" Directory="INSTALLFOLDER" Guid="E85CB46B-BC22-4943-B678-5E399CBE53A6">
6 <File Source="test.txt" Name="TypeLibComp.txt"></File>
7 <TypeLib Id="765BE8EE-BD7F-491E-90D2-C5A972462B50" Advertise="yes" Language="0" />
8 </Component>
9 </ComponentGroup>
10 </Fragment>
11</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents">
5 <ComponentGroupRef Id="MinimalComponentGroup"></ComponentGroupRef>
6 </ComponentGroup>
7
8 <Upgrade Id="B05772EA-82B8-4DE0-B7EB-45B5F0CCFE6D">
9 <UpgradeVersion Minimum="1.0.0" Property="RELPRODFOUND"></UpgradeVersion>
10 </Upgrade>
11 </Fragment>
12</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2
3<!--
4This file contains the declaration of all the localizable strings.
5-->
6<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
7
8 <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String>
9 <String Id="FeatureTitle">MsiPackage</String>
10
11</WixLocalization>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package ProductCode="{A81D50F9-B696-4F3D-ABE0-E64D61590E5F}" Name="MsiPackage" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a">
3
4 <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" />
5
6 <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)">
7 <ComponentGroupRef Id="ProductComponents" />
8 </Feature>
9 </Package>
10
11 <Fragment>
12 <StandardDirectory Id="ProgramFilesFolder">
13 <Directory Id="INSTALLFOLDER" Name="MsiPackage" />
14 </StandardDirectory>
15 </Fragment>
16</Wix>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Fragment>
3 <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
4 <Component>
5 <File Source="example.txt" />
6 <Provides Key="UsingProvides" />
7 </Component>
8 </ComponentGroup>
9 </Fragment>
10</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2
3<!--
4This file contains the declaration of all the localizable strings.
5-->
6<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
7
8 <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String>
9 <String Id="FeatureTitle">MsiPackage</String>
10
11</WixLocalization>
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 @@
1<?define Foo = "Foo" ?>
2<?define Foo = "Foo" ?>
3
4<?define Bar = "Bar" ?>
5<?define Bar = "Baz" ?>
6
7<?ifdef $(sys.WIXVERSION) ?>
8<?if $(sys.WIXMAJORVERSION) >= 4 AND $(sys.WIXMAJORVERSION) < 5 ?>
9 <?warning WiX v4 is in effect! ?>
10<?endif?>
11<?endif?>
12
13<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
14 <Package Name="MsiPackage" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="no" InstallerVersion="200" Scope="perMachine">
15
16
17 <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" />
18
19 <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)">
20 <ComponentGroupRef Id="ProductComponents" />
21 </Feature>
22 </Package>
23
24 <Fragment>
25 <Directory Id="TARGETDIR" Name="SourceDir">
26 <Directory Id="ProgramFilesFolder">
27 <Directory Id="INSTALLFOLDER" Name="MsiPackage" />
28 </Directory>
29 </Directory>
30 </Fragment>
31</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
5 <Component>
6 <File Source="test.txt" />
7 </Component>
8 </ComponentGroup>
9 </Fragment>
10</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2
3<!--
4This file contains the declaration of all the localizable strings.
5-->
6<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
7
8 <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String>
9 <String Id="FeatureTitle">MsiPackage</String>
10
11</WixLocalization>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Name="MsiPackage" Codepage="1252" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="no" InstallerVersion="200" Scope="perMachine">
3
4
5 <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" />
6
7 <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)">
8 <ComponentGroupRef Id="ProductComponents" />
9 </Feature>
10
11 <WixVariable Id="TestFile" Value="test2.txt" />
12 </Package>
13
14 <Fragment>
15 <StandardDirectory Id="ProgramFilesFolder">
16 <Directory Id="INSTALLFOLDER" Name="MsiPackage" />
17 </StandardDirectory>
18 </Fragment>
19</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <Binary Id="Test.txt" SourceFile="!(wix.TestFile=test.txt)" />
5 <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
6 <Component>
7 <File Source="test.txt" />
8 </Component>
9 <Component Id="Shared.dll" Shared="yes">
10 <File Name="Shared.dll" Source="test.txt" />
11 </Component>
12 </ComponentGroup>
13 </Fragment>
14</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2
3<!--
4This file contains the declaration of all the localizable strings.
5-->
6<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
7
8 <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String>
9 <String Id="FeatureTitle">MsiPackage</String>
10
11</WixLocalization>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Name="MsiPackage" Codepage="1252" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="no" InstallerVersion="200" Scope="perMachine">
3
4
5 <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" />
6
7 <CustomAction Id="CAFromExtension" DllEntry="DoesntExist" BinaryRef="BinFromWir" />
8
9 <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)">
10 <ComponentGroupRef Id="ProductComponents" />
11 </Feature>
12 </Package>
13
14 <Fragment>
15 <StandardDirectory Id="ProgramFilesFolder">
16 <Directory Id="INSTALLFOLDER" Name="MsiPackage" />
17 </StandardDirectory>
18 </Fragment>
19</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
5 <Component>
6 <File Source="test.txt" />
7 </Component>
8 </ComponentGroup>
9 </Fragment>
10</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2
3<!--
4This file contains the declaration of all the localizable strings.
5-->
6<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
7
8 <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String>
9 <String Id="FeatureTitle">MsiPackage</String>
10
11</WixLocalization>
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 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Package Name="MsiPackage" Codepage="1252" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a" Compressed="no" InstallerVersion="200" Scope="perMachine">
3
4
5 <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" />
6
7 <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)">
8 <ComponentGroupRef Id="ProductComponents" />
9 </Feature>
10 </Package>
11
12 <Fragment>
13 <Directory Id="TARGETDIR" Name="SourceDir">
14 <Directory Id="ProgramFilesFolder">
15 <Directory Id="INSTALLFOLDER" Name="MsiPackage" />
16 </Directory>
17 </Directory>
18 </Fragment>
19</Wix>
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <Binary Id="FooAlpha" SourceFile="!(bindpath.AlphaBits)foo.dll" />
5 </Fragment>
6
7 <Fragment>
8 <Binary Id="FooMips" SourceFile="!(bindpath.MipsBits)foo.dll" />
9 </Fragment>
10
11 <Fragment>
12 <Binary Id="FooPowerPC" SourceFile="!(bindpath.PowerBits)foo.dll" />
13 </Fragment>
14
15 <Fragment>
16 <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
17 <Component>
18 <File Source="test.txt" />
19 </Component>
20
21 <Component Id="Shared.dll" Shared="yes">
22 <File Name="Shared.dll" Source="test.txt" />
23 </Component>
24 </ComponentGroup>
25 </Fragment>
26</Wix>
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System.Collections.Generic;
6 using WixToolset.Core.TestPackage;
7 using Xunit;
8
9 public class TestXmlFixture
10 {
11 [Fact]
12 public void ChangesIgnoredAttributesToStarToHelpMakeTestsLessFragile()
13 {
14 var original = @"<Top One='f'>
15 <First Two='t'>
16 <Target One='a' Two='b' Three='c' />
17 </First>
18 <Target One='z' Two='x' Three = 'y' />
19</Top>";
20 var expected = "<Top One='f'><First Two='t'><Target One='*' Two='*' Three='c' /></First><Target One='*' Two='*' Three='y' /></Top>";
21 var ignored = new Dictionary<string, List<string>> { { "Target", new List<string> { "One", "Two", "Missing" } } };
22 Assert.Equal(expected, original.GetTestXml(ignored));
23 }
24
25 [Fact]
26 public void OutputsSingleQuotesSinceDoubleQuotesInCsharpLiteralStringsArePainful()
27 {
28 var original = "<Test Simple=\"\" EscapedDoubleQuote=\"&quot;\" SingleQuoteValue=\"'test'\" Alternating='\"' AlternatingEscaped='&quot;' />";
29 var expected = "<Test Simple='' EscapedDoubleQuote='\"' SingleQuoteValue='&apos;test&apos;' Alternating='\"' AlternatingEscaped='\"' />";
30 Assert.Equal(expected, original.GetTestXml());
31 }
32
33 [Fact]
34 public void RemovesAllNamespacesToReduceTyping()
35 {
36 var original = "<Test xmlns='a'><Child xmlns:b='b'><Grandchild xmlns:c='c' /><Grandchild /></Child></Test>";
37 var expected = "<Test><Child><Grandchild /><Grandchild /></Child></Test>";
38 Assert.Equal(expected, original.GetTestXml());
39 }
40
41 [Fact]
42 public void RemovesUnnecessaryWhitespaceToAvoidLineEndingIssues()
43 {
44 var original = @"<Test>
45 <Child>
46 <Grandchild />
47 <Grandchild />
48 </Child>
49</Test>";
50 var expected = "<Test><Child><Grandchild /><Grandchild /></Child></Test>";
51 Assert.Equal(expected, original.GetTestXml());
52 }
53
54 [Fact]
55 public void RemovesXmlDeclarationToReduceTyping()
56 {
57 var original = "<?xml version='1.0'?><Test />";
58 var expected = "<Test />";
59 Assert.Equal(expected, original.GetTestXml());
60 }
61 }
62}
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 @@
1
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4namespace WixToolsetTest.CoreIntegration
5{
6 using System.Collections.Generic;
7 using WixToolset.Core;
8 using WixToolset.Data;
9 using WixToolset.Data.Bind;
10 using WixToolset.Extensibility.Services;
11 using Xunit;
12
13 public class VariableResolverFixture
14 {
15 [Fact]
16 public void CanRecursivelyResolveVariables()
17 {
18 var serviceProvider = WixToolsetServiceProviderFactory.CreateServiceProvider();
19 var variableResolver = serviceProvider.GetService<IVariableResolver>();
20
21 var variables = new Dictionary<string, BindVariable>()
22 {
23 { "ProductName", new BindVariable() { Id = "ProductName", Value = "Localized Product Name" } },
24 { "ProductNameEdition", new BindVariable() { Id = "ProductNameEdition", Value = "!(loc.ProductName) Enterprise Edition" } },
25 { "ProductNameEditionVersion", new BindVariable() { Id = "ProductNameEditionVersion", Value = "!(loc.ProductNameEdition) v1.2.3" } },
26 };
27
28 var localization = new Localization(0, null, "x-none", variables, new Dictionary<string,LocalizedControl>());
29
30 variableResolver.AddLocalization(localization);
31
32 var result = variableResolver.ResolveVariables(null, "These are not the loc strings you're looking for.");
33 Assert.Equal("These are not the loc strings you're looking for.", result.Value);
34 Assert.False(result.UpdatedValue);
35
36 result = variableResolver.ResolveVariables(null, "Welcome to !(loc.ProductName)");
37 Assert.Equal("Welcome to Localized Product Name", result.Value);
38 Assert.True(result.UpdatedValue);
39
40 result = variableResolver.ResolveVariables(null, "Welcome to !(loc.ProductNameEdition)");
41 Assert.Equal("Welcome to Localized Product Name Enterprise Edition", result.Value);
42 Assert.True(result.UpdatedValue);
43
44 result = variableResolver.ResolveVariables(null, "Welcome to !(loc.ProductNameEditionVersion)");
45 Assert.Equal("Welcome to Localized Product Name Enterprise Edition v1.2.3", result.Value);
46 Assert.True(result.UpdatedValue);
47
48 result = variableResolver.ResolveVariables(null, "Welcome to !(bind.property.ProductVersion)");
49 Assert.Equal("Welcome to !(bind.property.ProductVersion)", result.Value);
50 Assert.False(result.UpdatedValue);
51 Assert.True(result.DelayedResolve);
52
53 var withUnknownLocString = "Welcome to !(loc.UnknownLocalizationVariable)";
54 Assert.Throws<WixException>(() => variableResolver.ResolveVariables(null, withUnknownLocString));
55
56 result = variableResolver.ResolveVariables(null, withUnknownLocString, errorOnUnknown: false);
57 Assert.Equal(withUnknownLocString, result.Value);
58 Assert.False(result.UpdatedValue);
59
60 result = variableResolver.ResolveVariables(null, "Welcome to !!(loc.UnknownLocalizationVariable)");
61 Assert.Equal("Welcome to !(loc.UnknownLocalizationVariable)", result.Value);
62 Assert.True(result.UpdatedValue);
63
64 result = variableResolver.ResolveVariables(null, "Welcome to !!(loc.UnknownLocalizationVariable) v!(bind.property.ProductVersion)");
65 Assert.Equal("Welcome to !(loc.UnknownLocalizationVariable) v!(bind.property.ProductVersion)", result.Value);
66 Assert.True(result.UpdatedValue);
67 Assert.True(result.DelayedResolve);
68
69 result = variableResolver.ResolveVariables(null, "Welcome to !(loc.ProductNameEditionVersion) !!(loc.UnknownLocalizationVariable) v!(bind.property.ProductVersion)");
70 Assert.Equal("Welcome to Localized Product Name Enterprise Edition v1.2.3 !(loc.UnknownLocalizationVariable) v!(bind.property.ProductVersion)", result.Value);
71 Assert.True(result.UpdatedValue);
72 Assert.True(result.DelayedResolve);
73 }
74 }
75}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System.IO;
6 using WixBuildTools.TestSupport;
7 using WixToolset.Core.TestPackage;
8 using WixToolset.Data;
9 using Xunit;
10
11 public class WarningFixture
12 {
13 [Fact]
14 public void SuppressedWarningsWithWarningAsErrorsAreNotErrors()
15 {
16 var folder = TestData.Get(@"TestData\Payload");
17
18 using (var fs = new DisposableFileSystem())
19 {
20 var baseFolder = fs.GetFolder();
21 var intermediateFolder = Path.Combine(baseFolder, "obj");
22 var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib");
23
24 var result = WixRunner.Execute(warningsAsErrors: true, new[]
25 {
26 "build",
27 "-sw1152",
28 Path.Combine(folder, "CanonicalizeName.wxs"),
29 "-intermediateFolder", intermediateFolder,
30 "-o", wixlibPath,
31 });
32
33 result.AssertSuccess();
34 }
35 }
36
37 [Fact]
38 public void WarningsAsErrorsTreatsWarningsAsErrors()
39 {
40 var folder = TestData.Get(@"TestData\Payload");
41
42 using (var fs = new DisposableFileSystem())
43 {
44 var baseFolder = fs.GetFolder();
45 var intermediateFolder = Path.Combine(baseFolder, "obj");
46 var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib");
47
48 var result = WixRunner.Execute(warningsAsErrors: true, new[]
49 {
50 "build",
51 Path.Combine(folder, "CanonicalizeName.wxs"),
52 "-intermediateFolder", intermediateFolder,
53 "-o", wixlibPath,
54 });
55
56 Assert.Equal((int)WarningMessages.Ids.PathCanonicalized, result.ExitCode);
57
58 var message = Assert.Single(result.Messages);
59 Assert.Equal(MessageLevel.Warning, message.Level); // TODO: is this right?
60 }
61 }
62 }
63}
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 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project Sdk="Microsoft.NET.Sdk">
5 <PropertyGroup>
6 <TargetFramework>netcoreapp3.1</TargetFramework>
7 <IsPackable>false</IsPackable>
8 <DebugType>embedded</DebugType>
9 </PropertyGroup>
10
11 <ItemGroup>
12 <Content Include="TestData\**" CopyToOutputDirectory="PreserveNewest" />
13 </ItemGroup>
14
15 <ItemGroup>
16 <ProjectReference Include="..\..\WixToolset.Core\WixToolset.Core.csproj" />
17 <ProjectReference Include="..\..\WixToolset.Core.Burn\WixToolset.Core.Burn.csproj" />
18 <ProjectReference Include="..\..\WixToolset.Core.WindowsInstaller\WixToolset.Core.WindowsInstaller.csproj" />
19 <ProjectReference Include="..\..\WixToolset.Core.TestPackage\WixToolset.Core.TestPackage.csproj" />
20 <ProjectReference Include="..\Example.Extension\Example.Extension.csproj" />
21 </ItemGroup>
22
23 <ItemGroup>
24 <PackageReference Include="WixBuildTools.TestSupport" Version="4.0.*" />
25 </ItemGroup>
26
27 <ItemGroup>
28 <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.8.0" />
29 <PackageReference Include="xunit" Version="2.4.1" />
30 <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" PrivateAssets="All" />
31 </ItemGroup>
32</Project>
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System;
6 using System.IO;
7 using System.Linq;
8 using WixBuildTools.TestSupport;
9 using WixToolset.Core.TestPackage;
10 using WixToolset.Data;
11 using WixToolset.Data.Symbols;
12 using Example.Extension;
13 using Xunit;
14
15 public class WixiplFixture
16 {
17 [Fact]
18 public void CanBuildSingleFile()
19 {
20 var folder = TestData.Get(@"TestData\SingleFile");
21
22 using (var fs = new DisposableFileSystem())
23 {
24 var baseFolder = fs.GetFolder();
25 var intermediateFolder = Path.Combine(baseFolder, "obj");
26 var wixiplPath = Path.Combine(intermediateFolder, @"test.wixipl");
27
28 var result = WixRunner.Execute(new[]
29 {
30 "build",
31 Path.Combine(folder, "Package.wxs"),
32 Path.Combine(folder, "PackageComponents.wxs"),
33 "-intermediateFolder", intermediateFolder,
34 "-o", wixiplPath,
35 });
36
37 result.AssertSuccess();
38
39 var intermediate = Intermediate.Load(wixiplPath);
40
41 Assert.False(intermediate.HasLevel(IntermediateLevels.Compiled));
42 Assert.True(intermediate.HasLevel(IntermediateLevels.Linked));
43 Assert.False(intermediate.HasLevel(IntermediateLevels.Resolved));
44
45 result = WixRunner.Execute(new[]
46 {
47 "build",
48 Path.Combine(intermediateFolder, @"test.wixipl"),
49 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
50 "-bindpath", Path.Combine(folder, "data"),
51 "-intermediateFolder", intermediateFolder,
52 "-o", Path.Combine(baseFolder, @"bin\test.msi")
53 });
54
55 result.AssertSuccess();
56
57 intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb"));
58
59 Assert.False(intermediate.HasLevel(IntermediateLevels.Compiled));
60 Assert.True(intermediate.HasLevel(IntermediateLevels.Linked));
61 Assert.True(intermediate.HasLevel(IntermediateLevels.Resolved));
62
63 var section = intermediate.Sections.Single();
64
65 var fileSymbol = section.Symbols.OfType<FileSymbol>().First();
66 Assert.Equal(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path);
67 Assert.Equal(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path);
68 }
69 }
70
71 [Fact]
72 public void CannotBuildWithSourceFileAndWixipl()
73 {
74 var folder = TestData.Get(@"TestData\SingleFile");
75
76 using (var fs = new DisposableFileSystem())
77 {
78 var baseFolder = fs.GetFolder();
79 var intermediateFolder = Path.Combine(baseFolder, "obj");
80
81 var result = WixRunner.Execute(new[]
82 {
83 "build",
84 Path.Combine(folder, "Package.wxs"),
85 Path.Combine(folder, "PackageComponents.wxs"),
86 "-intermediateFolder", intermediateFolder,
87 "-o", Path.Combine(intermediateFolder, @"test.wixipl")
88 });
89
90 result.AssertSuccess();
91
92 result = WixRunner.Execute(new[]
93 {
94 "build",
95 Path.Combine(folder, "Package.wxs"),
96 Path.Combine(intermediateFolder, @"test.wixipl"),
97 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
98 "-bindpath", Path.Combine(folder, "data"),
99 "-intermediateFolder", intermediateFolder,
100 "-o", Path.Combine(baseFolder, @"bin\test.msi")
101 });
102 Assert.Equal((int)ErrorMessages.Ids.WixiplSourceFileIsExclusive, result.ExitCode);
103 }
104 }
105
106 [Fact]
107 public void CanBuildMsiUsingExtensionLibrary()
108 {
109 var folder = TestData.Get(@"TestData\Wixipl");
110 var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath);
111
112 using (var fs = new DisposableFileSystem())
113 {
114 var baseFolder = fs.GetFolder();
115 var intermediateFolder = Path.Combine(baseFolder, "obj");
116
117 var result = WixRunner.Execute(new[]
118 {
119 "build",
120 "-ext", extensionPath,
121 Path.Combine(folder, "Package.wxs"),
122 Path.Combine(folder, "PackageComponents.wxs"),
123 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
124 "-bindpath", Path.Combine(folder, "data"),
125 "-intermediateFolder", intermediateFolder,
126 "-o", Path.Combine(baseFolder, @"bin\test.msi"),
127 });
128
129 result.AssertSuccess();
130
131 var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb"));
132 var section = intermediate.Sections.Single();
133
134 {
135 var fileSymbol = section.Symbols.OfType<FileSymbol>().Single();
136 Assert.Equal(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path);
137 Assert.Equal(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path);
138 }
139
140 {
141 var binary = section.Symbols.OfType<BinarySymbol>().Single();
142 var path = binary[BinarySymbolFields.Data].AsPath().Path;
143 Assert.StartsWith(Path.Combine(baseFolder, @"obj\Example.Extension"), path);
144 Assert.EndsWith(@"wix-ir\example.txt", path);
145 Assert.Equal(@"BinFromWir", binary.Id.Id);
146 }
147 }
148 }
149
150 [Fact]
151 public void CanBuildWixiplUsingExtensionLibrary()
152 {
153 var folder = TestData.Get(@"TestData\Wixipl");
154 var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath);
155
156 using (var fs = new DisposableFileSystem())
157 {
158 var baseFolder = fs.GetFolder();
159 var intermediateFolder = Path.Combine(baseFolder, "obj");
160
161 var result = WixRunner.Execute(new[]
162 {
163 "build",
164 "-ext", extensionPath,
165 Path.Combine(folder, "Package.wxs"),
166 Path.Combine(folder, "PackageComponents.wxs"),
167 "-intermediateFolder", intermediateFolder,
168 "-o", Path.Combine(intermediateFolder, @"test.wixipl"),
169 });
170
171 result.AssertSuccess();
172
173 result = WixRunner.Execute(new[]
174 {
175 "build",
176 Path.Combine(intermediateFolder, @"test.wixipl"),
177 "-ext", extensionPath,
178 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
179 "-bindpath", Path.Combine(folder, "data"),
180 "-intermediateFolder", intermediateFolder,
181 "-o", Path.Combine(baseFolder, @"bin\test.msi"),
182 });
183
184 result.AssertSuccess();
185
186 var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb"));
187 var section = intermediate.Sections.Single();
188
189 {
190 var fileSymbol = section.Symbols.OfType<FileSymbol>().Single();
191 Assert.Equal(Path.Combine(folder, @"data\test.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path);
192 Assert.Equal(@"test.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path);
193 }
194
195 {
196 var binary = section.Symbols.OfType<BinarySymbol>().Single();
197 var path = binary[BinarySymbolFields.Data].AsPath().Path;
198 Assert.StartsWith(Path.Combine(baseFolder, @"obj\test"), path);
199 Assert.EndsWith(@"wix-ir\example.txt", path);
200 Assert.Equal(@"BinFromWir", binary.Id.Id);
201 }
202 }
203 }
204 }
205}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System;
6 using System.IO;
7 using System.Linq;
8 using Example.Extension;
9 using WixBuildTools.TestSupport;
10 using WixToolset.Core.TestPackage;
11 using WixToolset.Data;
12 using WixToolset.Data.Symbols;
13 using Xunit;
14
15 public class WixlibFixture
16 {
17 [Fact]
18 public void CanBuildSimpleBundleUsingWixlib()
19 {
20 var folder = TestData.Get(@"TestData\SimpleBundle");
21
22 using (var fs = new DisposableFileSystem())
23 {
24 var baseFolder = fs.GetFolder();
25 var intermediateFolder = Path.Combine(baseFolder, "obj");
26
27 var result = WixRunner.Execute(new[]
28 {
29 "build",
30 Path.Combine(folder, "MultiFileBootstrapperApplication.wxs"),
31 "-intermediateFolder", intermediateFolder,
32 "-o", Path.Combine(intermediateFolder, @"test.wixlib")
33 });
34
35 result.AssertSuccess();
36
37 result = WixRunner.Execute(new[]
38 {
39 "build",
40 Path.Combine(folder, "MultiFileBundle.wxs"),
41 "-loc", Path.Combine(folder, "Bundle.en-us.wxl"),
42 "-lib", Path.Combine(intermediateFolder, @"test.wixlib"),
43 "-bindpath", Path.Combine(folder, "data"),
44 "-intermediateFolder", intermediateFolder,
45 "-o", Path.Combine(baseFolder, @"bin\test.exe")
46 });
47
48 result.AssertSuccess();
49
50 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.exe")));
51 Assert.True(File.Exists(Path.Combine(baseFolder, @"bin\test.wixpdb")));
52 }
53 }
54
55 [Fact]
56 public void CanBuildWixlibWithBinariesFromNamedBindPaths()
57 {
58 var folder = TestData.Get(@"TestData\WixlibWithBinaries");
59
60 using (var fs = new DisposableFileSystem())
61 {
62 var baseFolder = fs.GetFolder();
63 var intermediateFolder = Path.Combine(baseFolder, "obj");
64 var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib");
65
66 var result = WixRunner.Execute(new[]
67 {
68 "build",
69 Path.Combine(folder, "PackageComponents.wxs"),
70 "-bf",
71 "-bindpath", Path.Combine(folder, "data"),
72 // Use names that aren't excluded in default .gitignores.
73 "-bindpath", $"AlphaBits={Path.Combine(folder, "data", "alpha")}",
74 "-bindpath", $"MipsBits={Path.Combine(folder, "data", "mips")}",
75 "-bindpath", $"PowerBits={Path.Combine(folder, "data", "powerpc")}",
76 "-intermediateFolder", intermediateFolder,
77 "-o", wixlibPath,
78 });
79
80 result.AssertSuccess();
81
82 var wixlib = Intermediate.Load(wixlibPath);
83 var binarySymbols = wixlib.Sections.SelectMany(s => s.Symbols).OfType<BinarySymbol>().ToList();
84 Assert.Equal(3, binarySymbols.Count);
85 Assert.Single(binarySymbols.Where(t => t.Data.Path == "wix-ir/foo.dll"));
86 Assert.Single(binarySymbols.Where(t => t.Data.Path == "wix-ir/foo.dll-1"));
87 Assert.Single(binarySymbols.Where(t => t.Data.Path == "wix-ir/foo.dll-2"));
88 }
89 }
90
91 [Fact]
92 public void CanBuildSingleFileUsingWixlib()
93 {
94 var folder = TestData.Get(@"TestData\SingleFile");
95
96 using (var fs = new DisposableFileSystem())
97 {
98 var baseFolder = fs.GetFolder();
99 var intermediateFolder = Path.Combine(baseFolder, "obj");
100 var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib");
101
102 var result = WixRunner.Execute(new[]
103 {
104 "build",
105 Path.Combine(folder, "PackageComponents.wxs"),
106 "-intermediateFolder", intermediateFolder,
107 "-o", wixlibPath,
108 });
109
110 result.AssertSuccess();
111
112 var wixlib = Intermediate.Load(wixlibPath);
113
114 Assert.True(wixlib.HasLevel(IntermediateLevels.Compiled));
115 Assert.True(wixlib.HasLevel(IntermediateLevels.Combined));
116 Assert.False(wixlib.HasLevel(IntermediateLevels.Linked));
117 Assert.False(wixlib.HasLevel(IntermediateLevels.Resolved));
118
119 result = WixRunner.Execute(new[]
120 {
121 "build",
122 Path.Combine(folder, "Package.wxs"),
123 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
124 "-lib", Path.Combine(intermediateFolder, @"test.wixlib"),
125 "-bindpath", Path.Combine(folder, "data"),
126 "-intermediateFolder", intermediateFolder,
127 "-o", Path.Combine(baseFolder, @"bin\test.msi")
128 });
129
130 result.AssertSuccess();
131
132 var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb"));
133
134 Assert.False(intermediate.HasLevel(IntermediateLevels.Compiled));
135 Assert.False(intermediate.HasLevel(IntermediateLevels.Combined));
136 Assert.True(intermediate.HasLevel(IntermediateLevels.Linked));
137 Assert.True(intermediate.HasLevel(IntermediateLevels.Resolved));
138
139 var section = intermediate.Sections.Single();
140
141 var wixFile = section.Symbols.OfType<FileSymbol>().First();
142 Assert.Equal(Path.Combine(folder, @"data\test.txt"), wixFile[FileSymbolFields.Source].AsPath().Path);
143 Assert.Equal(@"test.txt", wixFile[FileSymbolFields.Source].PreviousValue.AsPath().Path);
144 }
145 }
146
147 [Fact]
148 public void CanOverridePathWixVariable()
149 {
150 var folder = TestData.Get(@"TestData\WixVariableOverride");
151
152 using (var fs = new DisposableFileSystem())
153 {
154 var baseFolder = fs.GetFolder();
155 var intermediateFolder = Path.Combine(baseFolder, "obj");
156 var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib");
157
158 var result = WixRunner.Execute(new[]
159 {
160 "build",
161 Path.Combine(folder, "PackageComponents.wxs"),
162 "-bf",
163 "-bindpath", Path.Combine(folder, "data"),
164 "-intermediateFolder", intermediateFolder,
165 "-o", wixlibPath,
166 });
167
168 result.AssertSuccess();
169
170 var wixlib = Intermediate.Load(wixlibPath);
171
172 Assert.True(wixlib.HasLevel(IntermediateLevels.Compiled));
173 Assert.True(wixlib.HasLevel(IntermediateLevels.Combined));
174 Assert.False(wixlib.HasLevel(IntermediateLevels.Linked));
175 Assert.False(wixlib.HasLevel(IntermediateLevels.Resolved));
176
177 result = WixRunner.Execute(new[]
178 {
179 "build",
180 Path.Combine(folder, "Package.wxs"),
181 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
182 "-lib", Path.Combine(intermediateFolder, @"test.wixlib"),
183 "-bindpath", Path.Combine(folder, "data"),
184 "-intermediateFolder", intermediateFolder,
185 "-o", Path.Combine(baseFolder, @"bin\test.msi")
186 });
187
188 result.AssertSuccess();
189
190 var intermediate = Intermediate.Load(Path.Combine(baseFolder, @"bin\test.wixpdb"));
191
192 Assert.False(intermediate.HasLevel(IntermediateLevels.Compiled));
193 Assert.False(intermediate.HasLevel(IntermediateLevels.Combined));
194 Assert.True(intermediate.HasLevel(IntermediateLevels.Linked));
195 Assert.True(intermediate.HasLevel(IntermediateLevels.Resolved));
196
197 var section = intermediate.Sections.Single();
198
199 var wixFile = section.Symbols.OfType<BinarySymbol>().First();
200 Assert.Equal(Path.Combine(folder, @"data\test2.txt"), wixFile.Data.Path);
201 }
202 }
203
204 [Fact]
205 public void CanBuildWithExtensionUsingWixlib()
206 {
207 var folder = TestData.Get(@"TestData\ExampleExtension");
208 var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath);
209
210 using (var fs = new DisposableFileSystem())
211 {
212 var baseFolder = fs.GetFolder();
213 var intermediateFolder = Path.Combine(baseFolder, "obj");
214
215 var result = WixRunner.Execute(new[]
216 {
217 "build",
218 Path.Combine(folder, "PackageComponents.wxs"),
219 "-ext", extensionPath,
220 "-intermediateFolder", intermediateFolder,
221 "-o", Path.Combine(intermediateFolder, @"test.wixlib")
222 });
223
224 result.AssertSuccess();
225
226 result = WixRunner.Execute(new[]
227 {
228 "build",
229 Path.Combine(folder, "Package.wxs"),
230 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
231 "-lib", Path.Combine(intermediateFolder, @"test.wixlib"),
232 "-ext", extensionPath,
233 "-bindpath", Path.Combine(folder, "data"),
234 "-intermediateFolder", intermediateFolder,
235 "-o", Path.Combine(intermediateFolder, @"bin\test.msi")
236 });
237
238 result.AssertSuccess();
239
240 var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb"));
241 var section = intermediate.Sections.Single();
242
243 var fileSymbol = section.Symbols.OfType<FileSymbol>().Single();
244 Assert.Equal(Path.Combine(folder, @"data\example.txt"), fileSymbol[FileSymbolFields.Source].AsPath().Path);
245 Assert.Equal(@"example.txt", fileSymbol[FileSymbolFields.Source].PreviousValue.AsPath().Path);
246
247 var example = section.Symbols.Where(t => t.Definition.Type == SymbolDefinitionType.MustBeFromAnExtension).Single();
248 Assert.Equal("Foo", example.Id?.Id);
249 Assert.Equal("Bar", example[0].AsString());
250 }
251 }
252
253 [Fact]
254 public void CanBuildWithExtensionUsingMultipleWixlibs()
255 {
256 var folder = TestData.Get(@"TestData\ComplexExampleExtension");
257 var extensionPath = Path.GetFullPath(new Uri(typeof(ExampleExtensionFactory).Assembly.CodeBase).LocalPath);
258
259 using (var fs = new DisposableFileSystem())
260 {
261 var baseFolder = fs.GetFolder();
262 var intermediateFolder = Path.Combine(baseFolder, "obj");
263
264 var result = WixRunner.Execute(new[]
265 {
266 "build",
267 Path.Combine(folder, "PackageComponents.wxs"),
268 "-ext", extensionPath,
269 "-intermediateFolder", intermediateFolder,
270 "-o", Path.Combine(intermediateFolder, @"components.wixlib")
271 });
272
273 result.AssertSuccess();
274
275 result = WixRunner.Execute(new[]
276 {
277 "build",
278 Path.Combine(folder, "OtherComponents.wxs"),
279 "-ext", extensionPath,
280 "-intermediateFolder", intermediateFolder,
281 "-o", Path.Combine(intermediateFolder, @"other.wixlib")
282 });
283
284 result.AssertSuccess();
285
286 result = WixRunner.Execute(new[]
287 {
288 "build",
289 Path.Combine(folder, "Package.wxs"),
290 "-loc", Path.Combine(folder, "Package.en-us.wxl"),
291 "-lib", Path.Combine(intermediateFolder, @"components.wixlib"),
292 "-lib", Path.Combine(intermediateFolder, @"other.wixlib"),
293 "-ext", extensionPath,
294 "-bindpath", Path.Combine(folder, "data"),
295 "-intermediateFolder", intermediateFolder,
296 "-o", Path.Combine(intermediateFolder, @"bin\test.msi")
297 });
298
299 result.AssertSuccess();
300
301 var intermediate = Intermediate.Load(Path.Combine(intermediateFolder, @"bin\test.wixpdb"));
302 var section = intermediate.Sections.Single();
303
304 var fileSymbols = section.Symbols.OfType<FileSymbol>().OrderBy(t => Path.GetFileName(t.Source.Path)).ToArray();
305 Assert.Equal(Path.Combine(folder, @"data\example.txt"), fileSymbols[0][FileSymbolFields.Source].AsPath().Path);
306 Assert.Equal(@"example.txt", fileSymbols[0][FileSymbolFields.Source].PreviousValue.AsPath().Path);
307 Assert.Equal(Path.Combine(folder, @"data\other.txt"), fileSymbols[1][FileSymbolFields.Source].AsPath().Path);
308 Assert.Equal(@"other.txt", fileSymbols[1][FileSymbolFields.Source].PreviousValue.AsPath().Path);
309
310 var examples = section.Symbols.Where(t => t.Definition.Type == SymbolDefinitionType.MustBeFromAnExtension).ToArray();
311 Assert.Equal(new string[] { "Foo", "Other" }, examples.Select(t => t.Id?.Id).ToArray());
312 Assert.Equal(new[] { "Bar", "Value" }, examples.Select(t => t[0].AsString()).ToArray());
313 }
314 }
315 }
316}
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 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolsetTest.CoreIntegration
4{
5 using System.IO;
6 using System.Linq;
7 using WixBuildTools.TestSupport;
8 using WixToolset.Core.TestPackage;
9 using WixToolset.Data;
10 using WixToolset.Data.Symbols;
11 using Xunit;
12
13 public class WixlibQueryFixture
14 {
15 [Fact]
16 public void UpgradeProducesReferenceToRemoveExistingProducts()
17 {
18 var folder = TestData.Get(@"TestData\Upgrade");
19
20 using (var fs = new DisposableFileSystem())
21 {
22 var baseFolder = fs.GetFolder();
23 var intermediateFolder = Path.Combine(baseFolder, "obj");
24 var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib");
25
26 var result = WixRunner.Execute(new[]
27 {
28 "build",
29 Path.Combine(folder, "DetectOnly.wxs"),
30 "-intermediateFolder", intermediateFolder,
31 "-o", wixlibPath,
32 });
33
34 result.AssertSuccess();
35
36 var intermediate = Intermediate.Load(wixlibPath);
37 var allSymbols = intermediate.Sections.SelectMany(s => s.Symbols);
38 var wixSimpleRefSymbols = allSymbols.OfType<WixSimpleReferenceSymbol>();
39 var repRef = wixSimpleRefSymbols.Where(t => t.Table == "WixAction" &&
40 t.PrimaryKeys == "InstallExecuteSequence/RemoveExistingProducts")
41 .SingleOrDefault();
42 Assert.NotNull(repRef);
43 }
44 }
45
46 [Fact]
47 public void TypeLibLanguageAsStringReturnsZero()
48 {
49 var folder = TestData.Get(@"TestData\TypeLib");
50
51 using (var fs = new DisposableFileSystem())
52 {
53 var baseFolder = fs.GetFolder();
54 var intermediateFolder = Path.Combine(baseFolder, "obj");
55 var wixlibPath = Path.Combine(intermediateFolder, @"test.wixlib");
56
57 var result = WixRunner.Execute(new[]
58 {
59 "build",
60 Path.Combine(folder, "Language0.wxs"),
61 "-intermediateFolder", intermediateFolder,
62 "-o", wixlibPath
63 });
64
65 result.AssertSuccess();
66
67 var intermediate = Intermediate.Load(wixlibPath);
68 var allSymbols = intermediate.Sections.SelectMany(s => s.Symbols);
69 var typeLibSymbol = allSymbols.OfType<TypeLibSymbol>()
70 .SingleOrDefault();
71 Assert.NotNull(typeLibSymbol);
72
73 var fields = typeLibSymbol.Fields.Select(field => field?.Type == IntermediateFieldType.Bool
74 ? field.AsNullableNumber()?.ToString()
75 : field?.AsString())
76 .ToList();
77 Assert.Equal("0", fields[1]);
78 }
79 }
80 }
81}