aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2017-09-17 15:35:20 -0700
committerRob Mensching <rob@firegiant.com>2017-09-17 16:00:11 -0700
commitd3d3649a68cb1fa589fdd987a6690dbd5d671f0d (patch)
tree44fe37ee352b7e3a355cc1e08b1d7d5988c14cc0 /src
parenta62610d23d6feb98be3b1e529a4e81b19d77d9d8 (diff)
downloadwix-d3d3649a68cb1fa589fdd987a6690dbd5d671f0d.tar.gz
wix-d3d3649a68cb1fa589fdd987a6690dbd5d671f0d.tar.bz2
wix-d3d3649a68cb1fa589fdd987a6690dbd5d671f0d.zip
Initial code commit
Diffstat (limited to 'src')
-rw-r--r--src/Directory.Build.props20
-rw-r--r--src/WixToolset.BuildTasks/AssemblyInfo.cs7
-rw-r--r--src/WixToolset.BuildTasks/BuildException.cs26
-rw-r--r--src/WixToolset.BuildTasks/Candle.cs199
-rw-r--r--src/WixToolset.BuildTasks/Common.cs41
-rw-r--r--src/WixToolset.BuildTasks/ConvertReferences.cs93
-rw-r--r--src/WixToolset.BuildTasks/CreateItemAvoidingInference.cs60
-rw-r--r--src/WixToolset.BuildTasks/CreateProjectReferenceDefineConstants.cs270
-rw-r--r--src/WixToolset.BuildTasks/DoIt-Compile.cs192
-rw-r--r--src/WixToolset.BuildTasks/DoIt.cs233
-rw-r--r--src/WixToolset.BuildTasks/FileSearchHelperMethods.cs60
-rw-r--r--src/WixToolset.BuildTasks/GenerateCompileWithObjectPath.cs146
-rw-r--r--src/WixToolset.BuildTasks/GetCabList.cs87
-rw-r--r--src/WixToolset.BuildTasks/GetLooseFileList.cs230
-rw-r--r--src/WixToolset.BuildTasks/GlobalSuppressions.cs8
-rw-r--r--src/WixToolset.BuildTasks/Insignia.cs118
-rw-r--r--src/WixToolset.BuildTasks/Light.cs488
-rw-r--r--src/WixToolset.BuildTasks/Lit.cs178
-rw-r--r--src/WixToolset.BuildTasks/Pyro.cs140
-rw-r--r--src/WixToolset.BuildTasks/RefreshBundleGeneratedFile.cs132
-rw-r--r--src/WixToolset.BuildTasks/RefreshGeneratedFile.cs118
-rw-r--r--src/WixToolset.BuildTasks/ReplaceString.cs54
-rw-r--r--src/WixToolset.BuildTasks/ResolveWixReferences.cs212
-rw-r--r--src/WixToolset.BuildTasks/TaskBase.cs65
-rw-r--r--src/WixToolset.BuildTasks/Torch.cs159
-rw-r--r--src/WixToolset.BuildTasks/WixAssignCulture.cs231
-rw-r--r--src/WixToolset.BuildTasks/WixCommandLineBuilder.cs180
-rw-r--r--src/WixToolset.BuildTasks/WixToolTask.cs406
-rw-r--r--src/WixToolset.BuildTasks/WixToolset.BuildTasks.csproj38
-rw-r--r--src/WixToolset.BuildTasks/heatdirectory.cs103
-rw-r--r--src/WixToolset.BuildTasks/heatfile.cs95
-rw-r--r--src/WixToolset.BuildTasks/heatproject.cs108
-rw-r--r--src/WixToolset.BuildTasks/heattask.cs121
-rw-r--r--src/WixToolset.BuildTasks/redirect.wix.ca.targets11
-rw-r--r--src/WixToolset.BuildTasks/redirect.wix.targets11
-rw-r--r--src/WixToolset.BuildTasks/wix.ca.targets123
-rw-r--r--src/WixToolset.BuildTasks/wix.harvest.targets511
-rw-r--r--src/WixToolset.BuildTasks/wix.signing.targets378
-rw-r--r--src/WixToolset.BuildTasks/wix.targets1353
-rw-r--r--src/WixToolset.Core/AppCommon.cs145
-rw-r--r--src/WixToolset.Core/AssemblyInfo.cs9
-rw-r--r--src/WixToolset.Core/Bind/BindBundleCommand.cs905
-rw-r--r--src/WixToolset.Core/Bind/BindDatabaseCommand.cs1311
-rw-r--r--src/WixToolset.Core/Bind/BindTransformCommand.cs473
-rw-r--r--src/WixToolset.Core/Bind/Bundles/AutomaticallySlipstreamPatchesCommand.cs112
-rw-r--r--src/WixToolset.Core/Bind/Bundles/BurnCommon.cs378
-rw-r--r--src/WixToolset.Core/Bind/Bundles/BurnReader.cs210
-rw-r--r--src/WixToolset.Core/Bind/Bundles/BurnWriter.cs239
-rw-r--r--src/WixToolset.Core/Bind/Bundles/CreateBootstrapperApplicationManifestCommand.cs241
-rw-r--r--src/WixToolset.Core/Bind/Bundles/CreateBurnManifestCommand.cs686
-rw-r--r--src/WixToolset.Core/Bind/Bundles/CreateContainerCommand.cs68
-rw-r--r--src/WixToolset.Core/Bind/Bundles/GetPackageFacadesCommand.cs62
-rw-r--r--src/WixToolset.Core/Bind/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs145
-rw-r--r--src/WixToolset.Core/Bind/Bundles/PackageFacade.cs58
-rw-r--r--src/WixToolset.Core/Bind/Bundles/ProcessExePackageCommand.cs33
-rw-r--r--src/WixToolset.Core/Bind/Bundles/ProcessMsiPackageCommand.cs560
-rw-r--r--src/WixToolset.Core/Bind/Bundles/ProcessMspPackageCommand.cs189
-rw-r--r--src/WixToolset.Core/Bind/Bundles/ProcessMsuPackageCommand.cs30
-rw-r--r--src/WixToolset.Core/Bind/Bundles/ProcessPayloadsCommand.cs159
-rw-r--r--src/WixToolset.Core/Bind/Bundles/VerifyPayloadsWithCatalogCommand.cs148
-rw-r--r--src/WixToolset.Core/Bind/Databases/AssignMediaCommand.cs314
-rw-r--r--src/WixToolset.Core/Bind/Databases/BindSummaryInfoCommand.cs135
-rw-r--r--src/WixToolset.Core/Bind/Databases/CabinetBuilder.cs176
-rw-r--r--src/WixToolset.Core/Bind/Databases/CabinetWorkItem.cs78
-rw-r--r--src/WixToolset.Core/Bind/Databases/ConfigurationCallback.cs91
-rw-r--r--src/WixToolset.Core/Bind/Databases/CopyTransformDataCommand.cs606
-rw-r--r--src/WixToolset.Core/Bind/Databases/CreateCabinetsCommand.cs489
-rw-r--r--src/WixToolset.Core/Bind/Databases/CreateDeltaPatchesCommand.cs86
-rw-r--r--src/WixToolset.Core/Bind/Databases/CreateSpecialPropertiesCommand.cs68
-rw-r--r--src/WixToolset.Core/Bind/Databases/ExtractMergeModuleFilesCommand.cs225
-rw-r--r--src/WixToolset.Core/Bind/Databases/FileFacade.cs44
-rw-r--r--src/WixToolset.Core/Bind/Databases/GetFileFacadesCommand.cs148
-rw-r--r--src/WixToolset.Core/Bind/Databases/MergeModulesCommand.cs350
-rw-r--r--src/WixToolset.Core/Bind/Databases/ProcessUncompressedFilesCommand.cs115
-rw-r--r--src/WixToolset.Core/Bind/Databases/UpdateControlTextCommand.cs80
-rw-r--r--src/WixToolset.Core/Bind/Databases/UpdateFileFacadesCommand.cs532
-rw-r--r--src/WixToolset.Core/Bind/DelayedField.cs38
-rw-r--r--src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs83
-rw-r--r--src/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs53
-rw-r--r--src/WixToolset.Core/Bind/FileTransfer.cs113
-rw-r--r--src/WixToolset.Core/Bind/GenerateDatabaseCommand.cs335
-rw-r--r--src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs121
-rw-r--r--src/WixToolset.Core/Bind/ResolveFieldsCommand.cs215
-rw-r--r--src/WixToolset.Core/Bind/ResolvedDirectory.cs31
-rw-r--r--src/WixToolset.Core/Bind/TransferFilesCommand.cs214
-rw-r--r--src/WixToolset.Core/Binder.cs686
-rw-r--r--src/WixToolset.Core/BinderCore.cs58
-rw-r--r--src/WixToolset.Core/BinderFileManager.cs370
-rw-r--r--src/WixToolset.Core/BinderFileManagerCore.cs108
-rw-r--r--src/WixToolset.Core/CLR/Interop/CLRInterop.cs147
-rw-r--r--src/WixToolset.Core/Cab/CabinetFileInfo.cs64
-rw-r--r--src/WixToolset.Core/Cab/Interop/CabInterop.cs316
-rw-r--r--src/WixToolset.Core/Cab/WixCreateCab.cs249
-rw-r--r--src/WixToolset.Core/Cab/WixEnumerateCab.cs89
-rw-r--r--src/WixToolset.Core/Cab/WixExtractCab.cs76
-rw-r--r--src/WixToolset.Core/CommandLine/BuildCommand.cs100
-rw-r--r--src/WixToolset.Core/CommandLine/CommandLine.cs592
-rw-r--r--src/WixToolset.Core/CommandLine/CommandLineHelper.cs255
-rw-r--r--src/WixToolset.Core/CommandLine/CommandLineOption.cs27
-rw-r--r--src/WixToolset.Core/CommandLine/CommandLineResponseFile.cs137
-rw-r--r--src/WixToolset.Core/CommandLine/CompileCommand.cs39
-rw-r--r--src/WixToolset.Core/CommandLine/HelpCommand.cs30
-rw-r--r--src/WixToolset.Core/CommandLine/ICommand.cs9
-rw-r--r--src/WixToolset.Core/CommandLine/VersionCommand.cs17
-rw-r--r--src/WixToolset.Core/Common.cs665
-rw-r--r--src/WixToolset.Core/Compiler.cs20667
-rw-r--r--src/WixToolset.Core/CompilerCore.cs1932
-rw-r--r--src/WixToolset.Core/Converter.cs614
-rw-r--r--src/WixToolset.Core/Data/messages.xml4010
-rw-r--r--src/WixToolset.Core/Decompiler.cs9357
-rw-r--r--src/WixToolset.Core/DecompilerCore.cs152
-rw-r--r--src/WixToolset.Core/Differ.cs621
-rw-r--r--src/WixToolset.Core/Exceptions/WixFileNotFoundException.cs52
-rw-r--r--src/WixToolset.Core/Extensibility/AssemblyDefaultHeatExtensionAttribute.cs33
-rw-r--r--src/WixToolset.Core/Extensibility/HarvesterExtension.cs26
-rw-r--r--src/WixToolset.Core/Extensibility/HeatExtension.cs204
-rw-r--r--src/WixToolset.Core/Extensibility/IHarvesterCore.cs62
-rw-r--r--src/WixToolset.Core/Extensibility/IHeatCore.cs36
-rw-r--r--src/WixToolset.Core/Extensibility/MutatorExtension.cs198
-rw-r--r--src/WixToolset.Core/Extensibility/ValidatorExtension.cs299
-rw-r--r--src/WixToolset.Core/ExtensionManager.cs110
-rw-r--r--src/WixToolset.Core/GlobalSuppressions.cs24
-rw-r--r--src/WixToolset.Core/Harvester.cs80
-rw-r--r--src/WixToolset.Core/HarvesterCore.cs103
-rw-r--r--src/WixToolset.Core/HeatCore.cs65
-rw-r--r--src/WixToolset.Core/ICommand.cs9
-rw-r--r--src/WixToolset.Core/IfDefEventHandler.cs46
-rw-r--r--src/WixToolset.Core/IncludedFileEventHandler.cs52
-rw-r--r--src/WixToolset.Core/Inscriber.cs457
-rw-r--r--src/WixToolset.Core/InspectorCore.cs32
-rw-r--r--src/WixToolset.Core/Librarian.cs95
-rw-r--r--src/WixToolset.Core/Link/ConnectToFeature.cs95
-rw-r--r--src/WixToolset.Core/Link/ConnectToFeatureCollection.cs92
-rw-r--r--src/WixToolset.Core/Link/ConnectToModule.cs54
-rw-r--r--src/WixToolset.Core/Link/ConnectToModuleCollection.cs92
-rw-r--r--src/WixToolset.Core/Link/FindEntrySectionAndLoadSymbolsCommand.cs109
-rw-r--r--src/WixToolset.Core/Link/ReportConflictingSymbolsCommand.cs49
-rw-r--r--src/WixToolset.Core/Link/ResolveReferencesCommand.cs178
-rw-r--r--src/WixToolset.Core/Link/WixGroupingOrdering.cs726
-rw-r--r--src/WixToolset.Core/Linker.cs2434
-rw-r--r--src/WixToolset.Core/Localizer.cs384
-rw-r--r--src/WixToolset.Core/Melter.cs398
-rw-r--r--src/WixToolset.Core/MelterCore.cs30
-rw-r--r--src/WixToolset.Core/MergeMod/NativeMethods.cs508
-rw-r--r--src/WixToolset.Core/Msi/Database.cs303
-rw-r--r--src/WixToolset.Core/Msi/Installer.cs484
-rw-r--r--src/WixToolset.Core/Msi/Interop/MsiInterop.cs697
-rw-r--r--src/WixToolset.Core/Msi/MsiException.cs78
-rw-r--r--src/WixToolset.Core/Msi/MsiHandle.cs116
-rw-r--r--src/WixToolset.Core/Msi/Record.cs182
-rw-r--r--src/WixToolset.Core/Msi/Session.cs45
-rw-r--r--src/WixToolset.Core/Msi/SummaryInformation.cs323
-rw-r--r--src/WixToolset.Core/Msi/View.cs189
-rw-r--r--src/WixToolset.Core/Mutator.cs115
-rw-r--r--src/WixToolset.Core/Ole32/Storage.cs437
-rw-r--r--src/WixToolset.Core/Patch.cs1284
-rw-r--r--src/WixToolset.Core/PatchAPI/PatchInterop.cs1002
-rw-r--r--src/WixToolset.Core/PatchTransform.cs274
-rw-r--r--src/WixToolset.Core/Preprocess/IfContext.cs110
-rw-r--r--src/WixToolset.Core/Preprocessor.cs1598
-rw-r--r--src/WixToolset.Core/PreprocessorCore.cs560
-rw-r--r--src/WixToolset.Core/ProcessedStreamEventHandler.cs43
-rw-r--r--src/WixToolset.Core/ProvidesDependency.cs108
-rw-r--r--src/WixToolset.Core/ProvidesDependencyCollection.cs64
-rw-r--r--src/WixToolset.Core/ResolvedVariableEventHandler.cs39
-rw-r--r--src/WixToolset.Core/SourceFile.cs21
-rw-r--r--src/WixToolset.Core/Unbinder.cs1491
-rw-r--r--src/WixToolset.Core/Util.cs17
-rw-r--r--src/WixToolset.Core/Uuid.cs89
-rw-r--r--src/WixToolset.Core/Validator.cs401
-rw-r--r--src/WixToolset.Core/VerifyInterop.cs68
-rw-r--r--src/WixToolset.Core/WixComponentSearchInfo.cs64
-rw-r--r--src/WixToolset.Core/WixDistribution.cs109
-rw-r--r--src/WixToolset.Core/WixDistributionSpecificStrings.Designer.cs77
-rw-r--r--src/WixToolset.Core/WixDistributionSpecificStrings.resx130
-rw-r--r--src/WixToolset.Core/WixFileSearchInfo.cs54
-rw-r--r--src/WixToolset.Core/WixGenericMessageEventArgs.cs45
-rw-r--r--src/WixToolset.Core/WixProductSearchInfo.cs67
-rw-r--r--src/WixToolset.Core/WixRegistrySearchInfo.cs92
-rw-r--r--src/WixToolset.Core/WixSearchInfo.cs53
-rw-r--r--src/WixToolset.Core/WixStrings.Designer.cs442
-rw-r--r--src/WixToolset.Core/WixStrings.resx249
-rw-r--r--src/WixToolset.Core/WixToolset.Core.csproj46
-rw-r--r--src/WixToolset.Core/WixVariableResolver.cs337
-rw-r--r--src/candle/App.icobin0 -> 1078 bytes
-rw-r--r--src/candle/AssemblyInfo.cs11
-rw-r--r--src/candle/CandleCommandLine.cs343
-rw-r--r--src/candle/CandleStrings.Designer.cs81
-rw-r--r--src/candle/CandleStrings.resx142
-rw-r--r--src/candle/CompileFile.cs20
-rw-r--r--src/candle/app.config9
-rw-r--r--src/candle/candle.cs200
-rw-r--r--src/candle/candle.csproj62
-rw-r--r--src/candle/candle.exe.manifest9
-rw-r--r--src/candle/candle.rc10
-rw-r--r--src/test/WixToolsetTest.BuildTasks/WixToolsetTest.BuildTasks.csproj21
-rw-r--r--src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/MsiPackage/MsiPackage.wixproj51
-rw-r--r--src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/MsiPackage/Package.en-us.wxl11
-rw-r--r--src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/MsiPackage/Package.wxs21
-rw-r--r--src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/MsiPackage/PackageComponents.wxs10
-rw-r--r--src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/MsiPackage/i.txtbin0 -> 1670 bytes
-rw-r--r--src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/SimpleMsiPackage.sln31
-rw-r--r--src/wix/Program.cs499
-rw-r--r--src/wix/X_CommandLine.cs394
-rw-r--r--src/wix/wix.csproj31
205 files changed, 80284 insertions, 0 deletions
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
new file mode 100644
index 00000000..25cb6d36
--- /dev/null
+++ b/src/Directory.Build.props
@@ -0,0 +1,20 @@
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>
5 <PropertyGroup>
6 <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
7 <BaseIntermediateOutputPath>$(MSBuildThisFileDirectory)..\build\obj\$(MSBuildProjectName)\</BaseIntermediateOutputPath>
8 <OutputPath>$(MSBuildThisFileDirectory)..\build\$(Configuration)\</OutputPath>
9
10 <Authors>WiX Toolset Team</Authors>
11 <Company>WiX Toolset</Company>
12 <Copyright>Copyright (c) .NET Foundation and contributors. All rights reserved.</Copyright>
13 </PropertyGroup>
14
15 <PropertyGroup>
16 <WixToolsetRootFolder>$(MSBuildThisFileDirectory)..\..\</WixToolsetRootFolder>
17 </PropertyGroup>
18
19 <Import Project="Custom.Build.props" Condition=" Exists('Custom.Build.props') " />
20</Project>
diff --git a/src/WixToolset.BuildTasks/AssemblyInfo.cs b/src/WixToolset.BuildTasks/AssemblyInfo.cs
new file mode 100644
index 00000000..ae52fce8
--- /dev/null
+++ b/src/WixToolset.BuildTasks/AssemblyInfo.cs
@@ -0,0 +1,7 @@
1// Copyright (c) .NET Foundation 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.Reflection;
4using System.Runtime.InteropServices;
5
6[assembly: AssemblyCulture("")]
7[assembly: ComVisible(false)]
diff --git a/src/WixToolset.BuildTasks/BuildException.cs b/src/WixToolset.BuildTasks/BuildException.cs
new file mode 100644
index 00000000..953134ba
--- /dev/null
+++ b/src/WixToolset.BuildTasks/BuildException.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.BuildTasks
4{
5 using System;
6 using System.Globalization;
7
8 class BuildException : Exception
9 {
10 public BuildException()
11 {
12 }
13
14 public BuildException(string message) : base(message)
15 {
16 }
17
18 public BuildException(string message, Exception innerException) : base(message, innerException)
19 {
20 }
21
22 public BuildException(string format, params string[] args) : this(String.Format(CultureInfo.CurrentCulture, format, args))
23 {
24 }
25 }
26}
diff --git a/src/WixToolset.BuildTasks/Candle.cs b/src/WixToolset.BuildTasks/Candle.cs
new file mode 100644
index 00000000..82b15838
--- /dev/null
+++ b/src/WixToolset.BuildTasks/Candle.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.BuildTasks
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.Globalization;
9 using System.IO;
10 using System.Text;
11
12 using Microsoft.Build.Framework;
13 using Microsoft.Build.Utilities;
14
15 /// <summary>
16 /// An MSBuild task to run the WiX compiler.
17 /// </summary>
18 public sealed class CandleOld : WixToolTask
19 {
20 private const string CandleToolName = "candle.exe";
21
22 private string[] defineConstants;
23 private ITaskItem[] extensions;
24 private string[] includeSearchPaths;
25 private ITaskItem outputFile;
26 private bool pedantic;
27 private string installerPlatform;
28 private string preprocessToFile;
29 private bool preprocessToStdOut;
30 private ITaskItem[] sourceFiles;
31 private string extensionDirectory;
32 private string[] referencePaths;
33
34 public string[] DefineConstants
35 {
36 get { return this.defineConstants; }
37 set { this.defineConstants = value; }
38 }
39
40 public ITaskItem[] Extensions
41 {
42 get { return this.extensions; }
43 set { this.extensions = value; }
44 }
45
46 public string[] IncludeSearchPaths
47 {
48 get { return this.includeSearchPaths; }
49 set { this.includeSearchPaths = value; }
50 }
51
52 public string InstallerPlatform
53 {
54 get { return this.installerPlatform; }
55 set { this.installerPlatform = value; }
56 }
57
58 [Output]
59 [Required]
60 public ITaskItem OutputFile
61 {
62 get { return this.outputFile; }
63 set { this.outputFile = value; }
64 }
65
66 public bool Pedantic
67 {
68 get { return this.pedantic; }
69 set { this.pedantic = value; }
70 }
71
72 public string PreprocessToFile
73 {
74 get { return this.preprocessToFile; }
75 set { this.preprocessToFile = value; }
76 }
77
78 public bool PreprocessToStdOut
79 {
80 get { return this.preprocessToStdOut; }
81 set { this.preprocessToStdOut = value; }
82 }
83
84 [Required]
85 public ITaskItem[] SourceFiles
86 {
87 get { return this.sourceFiles; }
88 set { this.sourceFiles = value; }
89 }
90
91 public string ExtensionDirectory
92 {
93 get { return this.extensionDirectory; }
94 set { this.extensionDirectory = value; }
95 }
96
97 public string[] ReferencePaths
98 {
99 get { return this.referencePaths; }
100 set { this.referencePaths = value; }
101 }
102
103 /// <summary>
104 /// Get the name of the executable.
105 /// </summary>
106 /// <remarks>The ToolName is used with the ToolPath to get the location of candle.exe.</remarks>
107 /// <value>The name of the executable.</value>
108 protected override string ToolName
109 {
110 get { return CandleToolName; }
111 }
112
113 /// <summary>
114 /// Get the path to the executable.
115 /// </summary>
116 /// <remarks>GetFullPathToTool is only called when the ToolPath property is not set (see the ToolName remarks above).</remarks>
117 /// <returns>The full path to the executable or simply candle.exe if it's expected to be in the system path.</returns>
118 protected override string GenerateFullPathToTool()
119 {
120 // If there's not a ToolPath specified, it has to be in the system path.
121 if (String.IsNullOrEmpty(this.ToolPath))
122 {
123 return CandleToolName;
124 }
125
126 return Path.Combine(Path.GetFullPath(this.ToolPath), CandleToolName);
127 }
128
129 /// <summary>
130 /// Builds a command line from options in this task.
131 /// </summary>
132 protected override void BuildCommandLine(WixCommandLineBuilder commandLineBuilder)
133 {
134 base.BuildCommandLine(commandLineBuilder);
135
136 commandLineBuilder.AppendIfTrue("-p", this.PreprocessToStdOut);
137 commandLineBuilder.AppendSwitchIfNotNull("-p", this.PreprocessToFile);
138 commandLineBuilder.AppendSwitchIfNotNull("-out ", this.OutputFile);
139 commandLineBuilder.AppendArrayIfNotNull("-d", this.DefineConstants);
140 commandLineBuilder.AppendArrayIfNotNull("-I", this.IncludeSearchPaths);
141 commandLineBuilder.AppendIfTrue("-pedantic", this.Pedantic);
142 commandLineBuilder.AppendSwitchIfNotNull("-arch ", this.InstallerPlatform);
143 commandLineBuilder.AppendExtensions(this.Extensions, this.ExtensionDirectory, this.referencePaths);
144 commandLineBuilder.AppendTextIfNotNull(this.AdditionalOptions);
145
146 // Support per-source-file output by looking at the SourceFiles items to
147 // see if there is any "CandleOutput" metadata. If there is, we do our own
148 // appending, otherwise we fall back to the built-in "append file names" code.
149 // Note also that the wix.targets "Compile" target does *not* automagically
150 // fix the "@(CompileObjOutput)" list to include these new output names.
151 // If you really want to use this, you're going to have to clone the target
152 // in your own .targets file and create the output list yourself.
153 bool usePerSourceOutput = false;
154 if (this.SourceFiles != null)
155 {
156 foreach (ITaskItem item in this.SourceFiles)
157 {
158 if (!String.IsNullOrEmpty(item.GetMetadata("CandleOutput")))
159 {
160 usePerSourceOutput = true;
161 break;
162 }
163 }
164 }
165
166 if (usePerSourceOutput)
167 {
168 string[] newSourceNames = new string[this.SourceFiles.Length];
169 for (int iSource = 0; iSource < this.SourceFiles.Length; ++iSource)
170 {
171 ITaskItem item = this.SourceFiles[iSource];
172 if (null == item)
173 {
174 newSourceNames[iSource] = null;
175 }
176 else
177 {
178 string output = item.GetMetadata("CandleOutput");
179
180 if (!String.IsNullOrEmpty(output))
181 {
182 newSourceNames[iSource] = String.Concat(item.ItemSpec, ";", output);
183 }
184 else
185 {
186 newSourceNames[iSource] = item.ItemSpec;
187 }
188 }
189 }
190
191 commandLineBuilder.AppendFileNamesIfNotNull(newSourceNames, " ");
192 }
193 else
194 {
195 commandLineBuilder.AppendFileNamesIfNotNull(this.SourceFiles, " ");
196 }
197 }
198 }
199}
diff --git a/src/WixToolset.BuildTasks/Common.cs b/src/WixToolset.BuildTasks/Common.cs
new file mode 100644
index 00000000..803e9d14
--- /dev/null
+++ b/src/WixToolset.BuildTasks/Common.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
4{
5 using System;
6 using System.Globalization;
7 using System.Text;
8 using System.Text.RegularExpressions;
9
10 /// <summary>
11 /// Common WixTasks utility methods and types.
12 /// </summary>
13 internal static class Common
14 {
15 /// <summary>Metadata key name to turn off harvesting of project references.</summary>
16 public const string DoNotHarvest = "DoNotHarvest";
17
18 private static readonly Regex AddPrefix = new Regex(@"^[^a-zA-Z_]", RegexOptions.Compiled);
19 private static readonly Regex IllegalIdentifierCharacters = new Regex(@"[^A-Za-z0-9_\.]|\.{2,}", RegexOptions.Compiled); // non 'words' and assorted valid characters
20
21 /// <summary>
22 /// Return an identifier based on passed file/directory name
23 /// </summary>
24 /// <param name="name">File/directory name to generate identifer from</param>
25 /// <returns>A version of the name that is a legal identifier.</returns>
26 /// <remarks>This is duplicated from WiX's Common class.</remarks>
27 internal static string GetIdentifierFromName(string name)
28 {
29 string result = IllegalIdentifierCharacters.Replace(name, "_"); // replace illegal characters with "_".
30
31 // MSI identifiers must begin with an alphabetic character or an
32 // underscore. Prefix all other values with an underscore.
33 if (AddPrefix.IsMatch(name))
34 {
35 result = String.Concat("_", result);
36 }
37
38 return result;
39 }
40 }
41}
diff --git a/src/WixToolset.BuildTasks/ConvertReferences.cs b/src/WixToolset.BuildTasks/ConvertReferences.cs
new file mode 100644
index 00000000..fe137633
--- /dev/null
+++ b/src/WixToolset.BuildTasks/ConvertReferences.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.BuildTasks
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;
11 using Microsoft.Build.Framework;
12 using Microsoft.Build.Utilities;
13
14 /// <summary>
15 /// This task assigns Culture metadata to files based on the value of the Culture attribute on the
16 /// WixLocalization element inside the file.
17 /// </summary>
18 public class ConvertReferences : Task
19 {
20 private string projectOutputGroups;
21 private ITaskItem[] projectReferences;
22 private ITaskItem[] harvestItems;
23
24 /// <summary>
25 /// The total list of cabs in this database
26 /// </summary>
27 [Output]
28 public ITaskItem[] HarvestItems
29 {
30 get { return this.harvestItems; }
31 }
32
33 /// <summary>
34 /// The project output groups to harvest.
35 /// </summary>
36 [Required]
37 public string ProjectOutputGroups
38 {
39 get { return this.projectOutputGroups; }
40 set { this.projectOutputGroups = value; }
41 }
42
43 /// <summary>
44 /// All the project references in the project.
45 /// </summary>
46 [Required]
47 public ITaskItem[] ProjectReferences
48 {
49 get { return this.projectReferences; }
50 set { this.projectReferences = value; }
51 }
52
53 /// <summary>
54 /// Gets a complete list of external cabs referenced by the given installer database file.
55 /// </summary>
56 /// <returns>True upon completion of the task execution.</returns>
57 public override bool Execute()
58 {
59 List<ITaskItem> newItems = new List<ITaskItem>();
60
61 foreach(ITaskItem item in this.ProjectReferences)
62 {
63 Dictionary<string, string> newItemMetadeta = new Dictionary<string, string>();
64
65 if (!String.IsNullOrEmpty(item.GetMetadata(Common.DoNotHarvest)))
66 {
67 continue;
68 }
69
70 string refTargetDir = item.GetMetadata("RefTargetDir");
71 if (!String.IsNullOrEmpty(refTargetDir))
72 {
73 newItemMetadeta.Add("DirectoryIds", refTargetDir);
74 }
75
76 string refName = item.GetMetadata("Name");
77 if (!String.IsNullOrEmpty(refName))
78 {
79 newItemMetadeta.Add("ProjectName", refName);
80 }
81
82 newItemMetadeta.Add("ProjectOutputGroups", this.ProjectOutputGroups);
83
84 ITaskItem newItem = new TaskItem(item.ItemSpec, newItemMetadeta);
85 newItems.Add(newItem);
86 }
87
88 this.harvestItems = newItems.ToArray();
89
90 return true;
91 }
92 }
93}
diff --git a/src/WixToolset.BuildTasks/CreateItemAvoidingInference.cs b/src/WixToolset.BuildTasks/CreateItemAvoidingInference.cs
new file mode 100644
index 00000000..84816cac
--- /dev/null
+++ b/src/WixToolset.BuildTasks/CreateItemAvoidingInference.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.BuildTasks
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;
11 using Microsoft.Build.Framework;
12 using Microsoft.Build.Utilities;
13
14 /// <summary>
15 /// This task assigns Culture metadata to files based on the value of the Culture attribute on the
16 /// WixLocalization element inside the file.
17 /// </summary>
18 public class CreateItemAvoidingInference : Task
19 {
20 private string inputProperties;
21 private ITaskItem[] outputItems;
22
23 /// <summary>
24 /// The output items.
25 /// </summary>
26 [Output]
27 public ITaskItem[] OuputItems
28 {
29 get { return this.outputItems; }
30 }
31
32 /// <summary>
33 /// The properties to converty to items.
34 /// </summary>
35 [Required]
36 public string InputProperties
37 {
38 get { return this.inputProperties; }
39 set { this.inputProperties = value; }
40 }
41
42 /// <summary>
43 /// Gets a complete list of external cabs referenced by the given installer database file.
44 /// </summary>
45 /// <returns>True upon completion of the task execution.</returns>
46 public override bool Execute()
47 {
48 List<ITaskItem> newItems = new List<ITaskItem>();
49
50 foreach (string property in this.inputProperties.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
51 {
52 newItems.Add(new TaskItem(property));
53 }
54
55 this.outputItems = newItems.ToArray();
56
57 return true;
58 }
59 }
60}
diff --git a/src/WixToolset.BuildTasks/CreateProjectReferenceDefineConstants.cs b/src/WixToolset.BuildTasks/CreateProjectReferenceDefineConstants.cs
new file mode 100644
index 00000000..5eae0850
--- /dev/null
+++ b/src/WixToolset.BuildTasks/CreateProjectReferenceDefineConstants.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.BuildTasks
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.IO;
9 using Microsoft.Build.Framework;
10 using Microsoft.Build.Utilities;
11
12 /// <summary>
13 /// An MSBuild task to create a list of preprocessor defines to be passed to candle from the
14 /// list of referenced projects.
15 /// </summary>
16 public sealed class CreateProjectReferenceDefineConstants : Task
17 {
18 private ITaskItem[] defineConstants;
19 private ITaskItem[] projectConfigurations;
20 private ITaskItem[] projectReferencePaths;
21
22 [Output]
23 public ITaskItem[] DefineConstants
24 {
25 get { return this.defineConstants; }
26 }
27
28 [Required]
29 public ITaskItem[] ProjectReferencePaths
30 {
31 get { return this.projectReferencePaths; }
32 set { this.projectReferencePaths = value; }
33 }
34
35 public ITaskItem[] ProjectConfigurations
36 {
37 get { return this.projectConfigurations; }
38 set { this.projectConfigurations = value; }
39 }
40
41 public override bool Execute()
42 {
43 List<ITaskItem> outputItems = new List<ITaskItem>();
44 Dictionary<string, string> defineConstants = new Dictionary<string, string>();
45
46 for (int i = 0; i < this.ProjectReferencePaths.Length; i++)
47 {
48 ITaskItem item = this.ProjectReferencePaths[i];
49
50 string configuration = item.GetMetadata("Configuration");
51 string fullConfiguration = item.GetMetadata("FullConfiguration");
52 string platform = item.GetMetadata("Platform");
53
54 string projectPath = CreateProjectReferenceDefineConstants.GetProjectPath(this.ProjectReferencePaths, i);
55 string projectDir = Path.GetDirectoryName(projectPath) + Path.DirectorySeparatorChar;
56 string projectExt = Path.GetExtension(projectPath);
57 string projectFileName = Path.GetFileName(projectPath);
58 string projectName = Path.GetFileNameWithoutExtension(projectPath);
59
60 string referenceName = CreateProjectReferenceDefineConstants.GetReferenceName(item, projectName);
61
62 string targetPath = item.GetMetadata("FullPath");
63 string targetDir = Path.GetDirectoryName(targetPath) + Path.DirectorySeparatorChar;
64 string targetExt = Path.GetExtension(targetPath);
65 string targetFileName = Path.GetFileName(targetPath);
66 string targetName = Path.GetFileNameWithoutExtension(targetPath);
67
68 // If there is no configuration metadata on the project reference task item,
69 // check for any additional configuration data provided in the optional task property.
70 if (String.IsNullOrEmpty(fullConfiguration))
71 {
72 fullConfiguration = this.FindProjectConfiguration(projectName);
73 if (!String.IsNullOrEmpty(fullConfiguration))
74 {
75 string[] typeAndPlatform = fullConfiguration.Split('|');
76 configuration = typeAndPlatform[0];
77 platform = (typeAndPlatform.Length > 1 ? typeAndPlatform[1] : String.Empty);
78 }
79 }
80
81 // write out the platform/configuration defines
82 defineConstants[String.Format(CultureInfo.InvariantCulture, "{0}.Configuration", referenceName)] = configuration;
83 defineConstants[String.Format(CultureInfo.InvariantCulture, "{0}.FullConfiguration", referenceName)] = fullConfiguration;
84 defineConstants[String.Format(CultureInfo.InvariantCulture, "{0}.Platform", referenceName)] = platform;
85
86 // write out the ProjectX defines
87 defineConstants[String.Format(CultureInfo.InvariantCulture, "{0}.ProjectDir", referenceName)] = projectDir;
88 defineConstants[String.Format(CultureInfo.InvariantCulture, "{0}.ProjectExt", referenceName)] = projectExt;
89 defineConstants[String.Format(CultureInfo.InvariantCulture, "{0}.ProjectFileName", referenceName)] = projectFileName;
90 defineConstants[String.Format(CultureInfo.InvariantCulture, "{0}.ProjectName", referenceName)] = projectName;
91 defineConstants[String.Format(CultureInfo.InvariantCulture, "{0}.ProjectPath", referenceName)] = projectPath;
92
93 // write out the TargetX defines
94 string targetDirDefine = String.Format(CultureInfo.InvariantCulture, "{0}.TargetDir", referenceName);
95 if (defineConstants.ContainsKey(targetDirDefine))
96 {
97 //if target dir was already defined, redefine it as the common root shared by multiple references from the same project
98 string commonDir = FindCommonRoot(targetDir, defineConstants[targetDirDefine]);
99 if (!String.IsNullOrEmpty(commonDir))
100 {
101 targetDir = commonDir;
102 }
103 }
104 defineConstants[targetDirDefine] = CreateProjectReferenceDefineConstants.EnsureEndsWithBackslash(targetDir);
105
106 defineConstants[String.Format(CultureInfo.InvariantCulture, "{0}.TargetExt", referenceName)] = targetExt;
107 defineConstants[String.Format(CultureInfo.InvariantCulture, "{0}.TargetFileName", referenceName)] = targetFileName;
108 defineConstants[String.Format(CultureInfo.InvariantCulture, "{0}.TargetName", referenceName)] = targetName;
109
110 //if target path was already defined, append to it creating a list of multiple references from the same project
111 string targetPathDefine = String.Format(CultureInfo.InvariantCulture, "{0}.TargetPath", referenceName);
112 if (defineConstants.ContainsKey(targetPathDefine))
113 {
114 string oldTargetPath = defineConstants[targetPathDefine];
115 if (!targetPath.Equals(oldTargetPath, StringComparison.OrdinalIgnoreCase))
116 {
117 defineConstants[targetPathDefine] += ";" + targetPath;
118 }
119
120 //If there was only one targetpath we need to create its culture specific define
121 if (!oldTargetPath.Contains(";"))
122 {
123 string oldSubFolder = FindSubfolder(oldTargetPath, targetDir, targetFileName);
124 if (!String.IsNullOrEmpty(oldSubFolder))
125 {
126 defineConstants[String.Format(CultureInfo.InvariantCulture, "{0}.{1}.TargetPath", referenceName, oldSubFolder.Replace('\\', '_'))] = oldTargetPath;
127 }
128 }
129
130 // Create a culture specific define
131 string subFolder = FindSubfolder(targetPath, targetDir, targetFileName);
132 if (!String.IsNullOrEmpty(subFolder))
133 {
134 defineConstants[String.Format(CultureInfo.InvariantCulture, "{0}.{1}.TargetPath", referenceName, subFolder.Replace('\\', '_'))] = targetPath;
135 }
136
137 }
138 else
139 {
140 defineConstants[targetPathDefine] = targetPath;
141 }
142 }
143
144 foreach (KeyValuePair<string, string> define in defineConstants)
145 {
146 outputItems.Add(new TaskItem(String.Format(CultureInfo.InvariantCulture, "{0}={1}", define.Key, define.Value)));
147 }
148 this.defineConstants = outputItems.ToArray();
149
150 return true;
151 }
152
153 public static string GetProjectPath(ITaskItem[] projectReferencePaths, int i)
154 {
155 return projectReferencePaths[i].GetMetadata("MSBuildSourceProjectFile");
156 }
157
158 public static string GetReferenceName(ITaskItem item, string projectName)
159 {
160 string referenceName = item.GetMetadata("Name");
161 if (String.IsNullOrEmpty(referenceName))
162 {
163 referenceName = projectName;
164 }
165
166 // We cannot have an equals sign in the variable name because it
167 // messes with the preprocessor definitions on the command line.
168 referenceName = referenceName.Replace('=', '_');
169
170 // We cannot have a double quote on the command line because it
171 // there is no way to escape it on the command line.
172 referenceName = referenceName.Replace('\"', '_');
173
174 // We cannot have parens in the variable name because the WiX
175 // preprocessor will not be able to parse it.
176 referenceName = referenceName.Replace('(', '_');
177 referenceName = referenceName.Replace(')', '_');
178
179 return referenceName;
180 }
181
182 /// <summary>
183 /// Look through the configuration data in the ProjectConfigurations property
184 /// to find the configuration for a project, if available.
185 /// </summary>
186 /// <param name="projectName">Name of the project that is being searched for.</param>
187 /// <returns>Full configuration spec, for example "Release|Win32".</returns>
188 private string FindProjectConfiguration(string projectName)
189 {
190 string configuration = String.Empty;
191
192 if (this.ProjectConfigurations != null)
193 {
194 foreach (ITaskItem configItem in this.ProjectConfigurations)
195 {
196 string configProject = configItem.ItemSpec;
197 if (configProject.Length > projectName.Length &&
198 configProject.StartsWith(projectName) &&
199 configProject[projectName.Length] == '=')
200 {
201 configuration = configProject.Substring(projectName.Length + 1);
202 break;
203 }
204 }
205 }
206
207 return configuration;
208 }
209
210 /// <summary>
211 /// Finds the common root between two paths
212 /// </summary>
213 /// <param name="path1"></param>
214 /// <param name="path2"></param>
215 /// <returns>common root on success, empty string on failure</returns>
216 private static string FindCommonRoot(string path1, string path2)
217 {
218 path1 = path1.TrimEnd(Path.DirectorySeparatorChar);
219 path2 = path2.TrimEnd(Path.DirectorySeparatorChar);
220
221 while (!String.IsNullOrEmpty(path1))
222 {
223 for (string searchPath = path2; !String.IsNullOrEmpty(searchPath); searchPath = Path.GetDirectoryName(searchPath))
224 {
225 if (path1.Equals(searchPath, StringComparison.OrdinalIgnoreCase))
226 {
227 return searchPath;
228 }
229 }
230
231 path1 = Path.GetDirectoryName(path1);
232 }
233
234 return path1;
235 }
236
237 /// <summary>
238 /// Finds the subfolder of a path, excluding a root and filename.
239 /// </summary>
240 /// <param name="path">Path to examine</param>
241 /// <param name="rootPath">Root that must be present </param>
242 /// <param name="fileName"></param>
243 /// <returns></returns>
244 private static string FindSubfolder(string path, string rootPath, string fileName)
245 {
246 if (Path.GetFileName(path).Equals(fileName, StringComparison.OrdinalIgnoreCase))
247 {
248 path = Path.GetDirectoryName(path);
249 }
250
251 if (path.StartsWith(rootPath, StringComparison.OrdinalIgnoreCase))
252 {
253 // cut out the root and return the subpath
254 return path.Substring(rootPath.Length).Trim(Path.DirectorySeparatorChar);
255 }
256
257 return String.Empty;
258 }
259
260 private static string EnsureEndsWithBackslash(string dir)
261 {
262 if (dir[dir.Length - 1] != Path.DirectorySeparatorChar)
263 {
264 dir += Path.DirectorySeparatorChar;
265 }
266
267 return dir;
268 }
269 }
270}
diff --git a/src/WixToolset.BuildTasks/DoIt-Compile.cs b/src/WixToolset.BuildTasks/DoIt-Compile.cs
new file mode 100644
index 00000000..f89078fe
--- /dev/null
+++ b/src/WixToolset.BuildTasks/DoIt-Compile.cs
@@ -0,0 +1,192 @@
1// Copyright (c) .NET Foundation 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 false
4namespace WixToolset.BuildTasks
5{
6 using System;
7 using System.Collections.Generic;
8 using System.IO;
9 using Microsoft.Build.Framework;
10 using WixToolset.Data;
11
12 /// <summary>
13 /// An MSBuild task to run the WiX compiler.
14 /// </summary>
15 public sealed class Candle : TaskBase
16 {
17 public string[] DefineConstants { get; set; }
18
19 public ITaskItem[] Extensions { get; set; }
20
21 public string[] IncludeSearchPaths { get; set; }
22
23 public string InstallerPlatform { get; set; }
24
25 [Output]
26 [Required]
27 public ITaskItem OutputFile { get; set; }
28
29 public bool Pedantic { get; set; }
30
31 public string PreprocessToFile { get; set; }
32
33 public bool PreprocessToStdOut { get; set; }
34
35 [Required]
36 public ITaskItem IntermediateDirectory { get; set; }
37
38 [Required]
39 public ITaskItem[] SourceFiles { get; set; }
40
41 public string ExtensionDirectory { get; set; }
42
43 public string[] ReferencePaths { get; set; }
44
45 protected override void ExecuteCore()
46 {
47 Messaging.Instance.InitializeAppName("WIX", "wix.exe");
48
49 Messaging.Instance.Display += this.DisplayMessage;
50
51 var preprocessor = new Preprocessor();
52
53 var compiler = new Compiler();
54
55 var sourceFiles = this.GatherSourceFiles();
56
57 var preprocessorVariables = this.GatherPreprocessorVariables();
58
59 foreach (var sourceFile in sourceFiles)
60 {
61 var document = preprocessor.Process(sourceFile.SourcePath, preprocessorVariables);
62
63 var intermediate = compiler.Compile(document);
64
65 intermediate.Save(sourceFile.OutputPath);
66 }
67 }
68
69 private void DisplayMessage(object sender, DisplayEventArgs e)
70 {
71 this.Log.LogMessageFromText(e.Message, MessageImportance.Normal);
72 }
73
74 private IEnumerable<SourceFile> GatherSourceFiles()
75 {
76 var files = new List<SourceFile>();
77
78 foreach (var item in this.SourceFiles)
79 {
80 var sourcePath = item.ItemSpec;
81 var outputPath = item.GetMetadata("CandleOutput") ?? this.OutputFile?.ItemSpec;
82
83 if (String.IsNullOrEmpty(outputPath))
84 {
85 outputPath = Path.Combine(this.IntermediateDirectory.ItemSpec, Path.GetFileNameWithoutExtension(sourcePath) + ".wir");
86 }
87
88 files.Add(new SourceFile(sourcePath, outputPath));
89 }
90
91 return files;
92 }
93
94 private IDictionary<string, string> GatherPreprocessorVariables()
95 {
96 var variables = new Dictionary<string, string>();
97
98 foreach (var pair in this.DefineConstants)
99 {
100 string[] value = pair.Split(new[] { '=' }, 2);
101
102 if (variables.ContainsKey(value[0]))
103 {
104 //Messaging.Instance.OnMessage(WixErrors.DuplicateVariableDefinition(value[0], (1 == value.Length) ? String.Empty : value[1], this.PreprocessorVariables[value[0]]));
105 break;
106 }
107
108 if (1 == value.Length)
109 {
110 variables.Add(value[0], String.Empty);
111 }
112 else
113 {
114 variables.Add(value[0], value[1]);
115 }
116 }
117
118 return variables;
119 }
120
121 ///// <summary>
122 ///// Builds a command line from options in this task.
123 ///// </summary>
124 //protected override void BuildCommandLine(WixCommandLineBuilder commandLineBuilder)
125 //{
126 // base.BuildCommandLine(commandLineBuilder);
127
128 // commandLineBuilder.AppendIfTrue("-p", this.PreprocessToStdOut);
129 // commandLineBuilder.AppendSwitchIfNotNull("-p", this.PreprocessToFile);
130 // commandLineBuilder.AppendSwitchIfNotNull("-out ", this.OutputFile);
131 // commandLineBuilder.AppendArrayIfNotNull("-d", this.DefineConstants);
132 // commandLineBuilder.AppendArrayIfNotNull("-I", this.IncludeSearchPaths);
133 // commandLineBuilder.AppendIfTrue("-pedantic", this.Pedantic);
134 // commandLineBuilder.AppendSwitchIfNotNull("-arch ", this.InstallerPlatform);
135 // commandLineBuilder.AppendExtensions(this.Extensions, this.ExtensionDirectory, this.referencePaths);
136 // commandLineBuilder.AppendTextIfNotNull(this.AdditionalOptions);
137
138 // // Support per-source-file output by looking at the SourceFiles items to
139 // // see if there is any "CandleOutput" metadata. If there is, we do our own
140 // // appending, otherwise we fall back to the built-in "append file names" code.
141 // // Note also that the wix.targets "Compile" target does *not* automagically
142 // // fix the "@(CompileObjOutput)" list to include these new output names.
143 // // If you really want to use this, you're going to have to clone the target
144 // // in your own .targets file and create the output list yourself.
145 // bool usePerSourceOutput = false;
146 // if (this.SourceFiles != null)
147 // {
148 // foreach (ITaskItem item in this.SourceFiles)
149 // {
150 // if (!String.IsNullOrEmpty(item.GetMetadata("CandleOutput")))
151 // {
152 // usePerSourceOutput = true;
153 // break;
154 // }
155 // }
156 // }
157
158 // if (usePerSourceOutput)
159 // {
160 // string[] newSourceNames = new string[this.SourceFiles.Length];
161 // for (int iSource = 0; iSource < this.SourceFiles.Length; ++iSource)
162 // {
163 // ITaskItem item = this.SourceFiles[iSource];
164 // if (null == item)
165 // {
166 // newSourceNames[iSource] = null;
167 // }
168 // else
169 // {
170 // string output = item.GetMetadata("CandleOutput");
171
172 // if (!String.IsNullOrEmpty(output))
173 // {
174 // newSourceNames[iSource] = String.Concat(item.ItemSpec, ";", output);
175 // }
176 // else
177 // {
178 // newSourceNames[iSource] = item.ItemSpec;
179 // }
180 // }
181 // }
182
183 // commandLineBuilder.AppendFileNamesIfNotNull(newSourceNames, " ");
184 // }
185 // else
186 // {
187 // commandLineBuilder.AppendFileNamesIfNotNull(this.SourceFiles, " ");
188 // }
189 //}
190 }
191}
192#endif
diff --git a/src/WixToolset.BuildTasks/DoIt.cs b/src/WixToolset.BuildTasks/DoIt.cs
new file mode 100644
index 00000000..7688342c
--- /dev/null
+++ b/src/WixToolset.BuildTasks/DoIt.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.BuildTasks
4{
5 using Microsoft.Build.Framework;
6 using Microsoft.Build.Utilities;
7 using WixToolset.Core;
8 using WixToolset.Data;
9
10 /// <summary>
11 /// An MSBuild task to run the WiX compiler.
12 /// </summary>
13 public sealed class DoIt : Task
14 {
15 public DoIt()
16 {
17 Messaging.Instance.InitializeAppName("WIX", "wix.exe");
18
19 Messaging.Instance.Display += this.DisplayMessage;
20 }
21
22 public string AdditionalOptions { get; set; }
23
24 public string Cultures { get; set; }
25
26 public string[] DefineConstants { get; set; }
27
28 public ITaskItem[] Extensions { get; set; }
29
30 public string ExtensionDirectory { get; set; }
31
32 public string[] IncludeSearchPaths { get; set; }
33
34 public string InstallerPlatform { get; set; }
35
36 [Required]
37 public ITaskItem IntermediateDirectory { get; set; }
38
39 public ITaskItem[] LocalizationFiles { get; set; }
40
41 public bool NoLogo { get; set; }
42
43 public ITaskItem[] ObjectFiles { get; set; }
44
45 [Output]
46 [Required]
47 public ITaskItem OutputFile { get; set; }
48
49 public string PdbOutputFile { get; set; }
50
51 public bool Pedantic { get; set; }
52
53 [Required]
54 public ITaskItem[] SourceFiles { get; set; }
55
56 public string[] ReferencePaths { get; set; }
57
58
59 /// <summary>
60 /// Gets or sets whether all warnings should be suppressed.
61 /// </summary>
62 public bool SuppressAllWarnings { get; set; }
63
64 /// <summary>
65 /// Gets or sets a list of specific warnings to be suppressed.
66 /// </summary>
67 public string[] SuppressSpecificWarnings { get; set; }
68
69 /// <summary>
70 /// Gets or sets whether all warnings should be treated as errors.
71 /// </summary>
72 public bool TreatWarningsAsErrors { get; set; }
73
74 /// <summary>
75 /// Gets or sets a list of specific warnings to treat as errors.
76 /// </summary>
77 public string[] TreatSpecificWarningsAsErrors { get; set; }
78
79 /// <summary>
80 /// Gets or sets whether to display verbose output.
81 /// </summary>
82 public bool VerboseOutput { get; set; }
83
84
85 public ITaskItem[] BindInputPaths { get; set; }
86 public bool BindFiles { get; set; }
87 public ITaskItem BindContentsFile{ get; set; }
88 public ITaskItem BindOutputsFile { get; set; }
89 public ITaskItem BindBuiltOutputsFile { get; set; }
90
91 public string CabinetCachePath { get; set; }
92 public int CabinetCreationThreadCount { get; set; }
93 public string DefaultCompressionLevel { get; set; }
94
95 [Output]
96 public ITaskItem UnreferencedSymbolsFile { get; set; }
97
98 public ITaskItem WixProjectFile { get; set; }
99 public string[] WixVariables { get; set; }
100
101 public bool SuppressValidation { get; set; }
102 public string[] SuppressIces { get; set; }
103 public string AdditionalCub { get; set; }
104
105
106
107 public override bool Execute()
108 {
109 try
110 {
111 this.ExecuteCore();
112 }
113 catch (BuildException e)
114 {
115 this.Log.LogErrorFromException(e);
116 }
117 catch (WixException e)
118 {
119 this.Log.LogErrorFromException(e);
120 }
121
122 return !this.Log.HasLoggedErrors;
123 }
124
125 private void ExecuteCore()
126 {
127 var commandLineBuilder = new WixCommandLineBuilder();
128
129 commandLineBuilder.AppendTextUnquoted("build");
130
131 commandLineBuilder.AppendSwitchIfNotNull("-out ", this.OutputFile);
132 commandLineBuilder.AppendSwitchIfNotNull("-cultures ", this.Cultures);
133 commandLineBuilder.AppendArrayIfNotNull("-d ", this.DefineConstants);
134 commandLineBuilder.AppendArrayIfNotNull("-I ", this.IncludeSearchPaths);
135 commandLineBuilder.AppendExtensions(this.Extensions, this.ExtensionDirectory, this.ReferencePaths);
136 commandLineBuilder.AppendIfTrue("-nologo", this.NoLogo);
137 commandLineBuilder.AppendIfTrue("-sval", this.SuppressValidation);
138 commandLineBuilder.AppendArrayIfNotNull("-sice ", this.SuppressIces);
139 commandLineBuilder.AppendSwitchIfNotNull("-usf ", this.UnreferencedSymbolsFile);
140 commandLineBuilder.AppendSwitchIfNotNull("-cc ", this.CabinetCachePath);
141 commandLineBuilder.AppendSwitchIfNotNull("-contentsfile ", this.BindContentsFile);
142 commandLineBuilder.AppendSwitchIfNotNull("-outputsfile ", this.BindOutputsFile);
143 commandLineBuilder.AppendSwitchIfNotNull("-builtoutputsfile ", this.BindBuiltOutputsFile);
144 commandLineBuilder.AppendSwitchIfNotNull("-wixprojectfile ", this.WixProjectFile);
145 commandLineBuilder.AppendTextIfNotWhitespace(this.AdditionalOptions);
146
147 commandLineBuilder.AppendArrayIfNotNull("-loc ", this.LocalizationFiles);
148 commandLineBuilder.AppendFileNamesIfNotNull(this.SourceFiles, " ");
149
150 var commandLineString = commandLineBuilder.ToString();
151
152 this.Log.LogMessage(MessageImportance.Normal, commandLineString);
153
154 var command = CommandLine.ParseStandardCommandLine(commandLineString);
155 command?.Execute();
156 }
157
158 private void DisplayMessage(object sender, DisplayEventArgs e)
159 {
160 this.Log.LogMessageFromText(e.Message, MessageImportance.Normal);
161 }
162
163 ///// <summary>
164 ///// Builds a command line from options in this task.
165 ///// </summary>
166 //protected override void BuildCommandLine(WixCommandLineBuilder commandLineBuilder)
167 //{
168 // base.BuildCommandLine(commandLineBuilder);
169
170 // commandLineBuilder.AppendIfTrue("-p", this.PreprocessToStdOut);
171 // commandLineBuilder.AppendSwitchIfNotNull("-p", this.PreprocessToFile);
172 // commandLineBuilder.AppendSwitchIfNotNull("-out ", this.OutputFile);
173 // commandLineBuilder.AppendArrayIfNotNull("-d", this.DefineConstants);
174 // commandLineBuilder.AppendArrayIfNotNull("-I", this.IncludeSearchPaths);
175 // commandLineBuilder.AppendIfTrue("-pedantic", this.Pedantic);
176 // commandLineBuilder.AppendSwitchIfNotNull("-arch ", this.InstallerPlatform);
177 // commandLineBuilder.AppendExtensions(this.Extensions, this.ExtensionDirectory, this.referencePaths);
178 // commandLineBuilder.AppendTextIfNotNull(this.AdditionalOptions);
179
180 // // Support per-source-file output by looking at the SourceFiles items to
181 // // see if there is any "CandleOutput" metadata. If there is, we do our own
182 // // appending, otherwise we fall back to the built-in "append file names" code.
183 // // Note also that the wix.targets "Compile" target does *not* automagically
184 // // fix the "@(CompileObjOutput)" list to include these new output names.
185 // // If you really want to use this, you're going to have to clone the target
186 // // in your own .targets file and create the output list yourself.
187 // bool usePerSourceOutput = false;
188 // if (this.SourceFiles != null)
189 // {
190 // foreach (ITaskItem item in this.SourceFiles)
191 // {
192 // if (!String.IsNullOrEmpty(item.GetMetadata("CandleOutput")))
193 // {
194 // usePerSourceOutput = true;
195 // break;
196 // }
197 // }
198 // }
199
200 // if (usePerSourceOutput)
201 // {
202 // string[] newSourceNames = new string[this.SourceFiles.Length];
203 // for (int iSource = 0; iSource < this.SourceFiles.Length; ++iSource)
204 // {
205 // ITaskItem item = this.SourceFiles[iSource];
206 // if (null == item)
207 // {
208 // newSourceNames[iSource] = null;
209 // }
210 // else
211 // {
212 // string output = item.GetMetadata("CandleOutput");
213
214 // if (!String.IsNullOrEmpty(output))
215 // {
216 // newSourceNames[iSource] = String.Concat(item.ItemSpec, ";", output);
217 // }
218 // else
219 // {
220 // newSourceNames[iSource] = item.ItemSpec;
221 // }
222 // }
223 // }
224
225 // commandLineBuilder.AppendFileNamesIfNotNull(newSourceNames, " ");
226 // }
227 // else
228 // {
229 // commandLineBuilder.AppendFileNamesIfNotNull(this.SourceFiles, " ");
230 // }
231 //}
232 }
233}
diff --git a/src/WixToolset.BuildTasks/FileSearchHelperMethods.cs b/src/WixToolset.BuildTasks/FileSearchHelperMethods.cs
new file mode 100644
index 00000000..6cc804eb
--- /dev/null
+++ b/src/WixToolset.BuildTasks/FileSearchHelperMethods.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.BuildTasks
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics.CodeAnalysis;
8 using System.IO;
9 using System.Text;
10 using Microsoft.Build.Framework;
11
12 /// <summary>
13 /// Contains helper methods on searching for files
14 /// </summary>
15 public static class FileSearchHelperMethods
16 {
17 /// <summary>
18 /// Searches for the existence of a file in multiple directories.
19 /// Search is satisfied if default file path is valid and exists. If not,
20 /// file name is extracted from default path and combined with each of the directories
21 /// looking to see if it exists. If not found, input default path is returned.
22 /// </summary>
23 /// <param name="directories">Array of directories to look in, without filenames in them</param>
24 /// <param name="defaultFullPath">Default path - to use if not found</param>
25 /// <returns>File path if file found. Empty string if not found</returns>
26 public static string SearchFilePaths(string[] directories, string defaultFullPath)
27 {
28 if (String.IsNullOrEmpty(defaultFullPath))
29 {
30 return String.Empty;
31 }
32
33 if (File.Exists(defaultFullPath))
34 {
35 return defaultFullPath;
36 }
37
38 if (directories == null)
39 {
40 return string.Empty;
41 }
42
43 string fileName = Path.GetFileName(defaultFullPath);
44 foreach (string currentPath in directories)
45 {
46 if (String.IsNullOrEmpty(currentPath) || String.IsNullOrEmpty(currentPath.Trim()))
47 {
48 continue;
49 }
50
51 if (File.Exists(Path.Combine(currentPath, fileName)))
52 {
53 return Path.Combine(currentPath, fileName);
54 }
55 }
56
57 return String.Empty;
58 }
59 }
60}
diff --git a/src/WixToolset.BuildTasks/GenerateCompileWithObjectPath.cs b/src/WixToolset.BuildTasks/GenerateCompileWithObjectPath.cs
new file mode 100644
index 00000000..06c8b98a
--- /dev/null
+++ b/src/WixToolset.BuildTasks/GenerateCompileWithObjectPath.cs
@@ -0,0 +1,146 @@
1// Copyright (c) .NET Foundation and 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.BuildTasks
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics.CodeAnalysis;
8 using System.Globalization;
9 using System.IO;
10 using System.Security.Cryptography;
11 using System.Text;
12 using Microsoft.Build.Framework;
13 using Microsoft.Build.Utilities;
14
15 /// <summary>
16 /// This task generates metadata on the for compile output objects.
17 /// </summary>
18 public class GenerateCompileWithObjectPath : Task
19 {
20 /// <summary>
21 /// The list of files to generate outputs for.
22 /// </summary>
23 [Required]
24 public ITaskItem[] Compile
25 {
26 get;
27 set;
28 }
29
30 /// <summary>
31 /// The list of files with ObjectPath metadata.
32 /// </summary>
33 [Output]
34 public ITaskItem[] CompileWithObjectPath
35 {
36 get;
37 private set;
38 }
39
40 /// <summary>
41 /// The folder under which all ObjectPaths should reside.
42 /// </summary>
43 [Required]
44 public string IntermediateOutputPath
45 {
46 get;
47 set;
48 }
49
50 /// <summary>
51 /// Generate an identifier by hashing data from the row.
52 /// </summary>
53 /// <param name="prefix">Three letter or less prefix for generated row identifier.</param>
54 /// <param name="args">Information to hash.</param>
55 /// <returns>The generated identifier.</returns>
56 [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "System.InvalidOperationException.#ctor(System.String)")]
57 public static string GenerateIdentifier(string prefix, params string[] args)
58 {
59 string stringData = String.Join("|", args);
60 byte[] data = Encoding.Unicode.GetBytes(stringData);
61
62 // hash the data
63 byte[] hash;
64
65 using (MD5 md5 = new MD5CryptoServiceProvider())
66 {
67 hash = md5.ComputeHash(data);
68 }
69
70 // build up the identifier
71 StringBuilder identifier = new StringBuilder(35, 35);
72 identifier.Append(prefix);
73
74 // hard coded to 16 as that is the most bytes that can be used to meet the length requirements. SHA1 is 20 bytes.
75 for (int i = 0; i < 16; i++)
76 {
77 identifier.Append(hash[i].ToString("X2", CultureInfo.InvariantCulture.NumberFormat));
78 }
79
80 return identifier.ToString();
81 }
82
83 /// <summary>
84 /// Gets the full path of the directory in which the file is found.
85 /// </summary>
86 /// <param name='file'>The file from which to extract the directory.</param>
87 /// <returns>The generated identifier.</returns>
88 private static string GetDirectory(ITaskItem file)
89 {
90 return file.GetMetadata("RootDir") + file.GetMetadata("Directory");
91 }
92
93 /// <summary>
94 /// Sets the object path to use for the file.
95 /// </summary>
96 /// <param name='file'>The file on which to set the ObjectPath metadata.</param>
97 /// <remarks>
98 /// For the same input path it will return the same ObjectPath. Case is not ignored, however that isn't a problem.
99 /// </remarks>
100 private void SetObjectPath(ITaskItem file)
101 {
102 // If the source file is in the project directory or in the intermediate directory, use the intermediate directory.
103 if (string.IsNullOrEmpty(file.GetMetadata("RelativeDir")) || string.Compare(file.GetMetadata("RelativeDir"), this.IntermediateOutputPath, StringComparison.OrdinalIgnoreCase) == 0)
104 {
105 file.SetMetadata("ObjectPath", this.IntermediateOutputPath);
106 }
107 // Otherwise use a subdirectory of the intermediate directory. The subfolder's name is based on the full path of the folder containing the source file.
108 else
109 {
110 file.SetMetadata("ObjectPath", Path.Combine(this.IntermediateOutputPath, GenerateIdentifier("pth", GetDirectory(file))) + Path.DirectorySeparatorChar);
111 }
112 }
113
114 /// <summary>
115 /// Gets a complete list of external cabs referenced by the given installer database file.
116 /// </summary>
117 /// <returns>True upon completion of the task execution.</returns>
118 public override bool Execute()
119 {
120 if (string.IsNullOrEmpty(this.IntermediateOutputPath))
121 {
122 this.Log.LogError("IntermediateOutputPath parameter is required and cannot be empty");
123 return false;
124 }
125
126 if (this.Compile == null || this.Compile.Length == 0)
127 {
128 return true;
129 }
130
131 this.CompileWithObjectPath = new ITaskItem[this.Compile.Length];
132 for (int i = 0; i < this.Compile.Length; ++i)
133 {
134 this.CompileWithObjectPath[i] = new TaskItem(this.Compile[i].ItemSpec, this.Compile[i].CloneCustomMetadata());
135
136 // Do not overwrite the ObjectPath metadata if it already was set.
137 if (string.IsNullOrEmpty(this.CompileWithObjectPath[i].GetMetadata("ObjectPath")))
138 {
139 SetObjectPath(this.CompileWithObjectPath[i]);
140 }
141 }
142
143 return true;
144 }
145 }
146}
diff --git a/src/WixToolset.BuildTasks/GetCabList.cs b/src/WixToolset.BuildTasks/GetCabList.cs
new file mode 100644
index 00000000..e97538af
--- /dev/null
+++ b/src/WixToolset.BuildTasks/GetCabList.cs
@@ -0,0 +1,87 @@
1// Copyright (c) .NET Foundation and 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.BuildTasks
4{
5 using System;
6 using System.Collections;
7 using System.Collections.Generic;
8 using System.Diagnostics;
9 using System.IO;
10 using System.Reflection;
11 using System.Xml;
12 using Microsoft.Build.Framework;
13 using Microsoft.Build.Utilities;
14 using WixToolset.Dtf.WindowsInstaller;
15 using Microsoft.Win32;
16
17 /// <summary>
18 /// This task assigns Culture metadata to files based on the value of the Culture attribute on the
19 /// WixLocalization element inside the file.
20 /// </summary>
21 public class GetCabList : Task
22 {
23 private ITaskItem database;
24 private ITaskItem[] cabList;
25
26 /// <summary>
27 /// The list of database files to find cabs in
28 /// </summary>
29 [Required]
30 public ITaskItem Database
31 {
32 get { return this.database; }
33 set { this.database = value; }
34 }
35
36 /// <summary>
37 /// The total list of cabs in this database
38 /// </summary>
39 [Output]
40 public ITaskItem[] CabList
41 {
42 get { return this.cabList; }
43 }
44
45 /// <summary>
46 /// Gets a complete list of external cabs referenced by the given installer database file.
47 /// </summary>
48 /// <returns>True upon completion of the task execution.</returns>
49 public override bool Execute()
50 {
51 string databaseFile = this.database.ItemSpec;
52 Object []args = { };
53 System.Collections.Generic.List<ITaskItem> cabNames = new System.Collections.Generic.List<ITaskItem>();
54
55 // If the file doesn't exist, no cabs to return, so exit now
56 if (!File.Exists(databaseFile))
57 {
58 return true;
59 }
60
61 using (Database database = new Database(databaseFile))
62 {
63 // If the media table doesn't exist, no cabs to return, so exit now
64 if (null == database.Tables["Media"])
65 {
66 return true;
67 }
68
69 System.Collections.IList records = database.ExecuteQuery("SELECT `Cabinet` FROM `Media`", args);
70
71 foreach (string cabName in records)
72 {
73 if (String.IsNullOrEmpty(cabName) || cabName.StartsWith("#", StringComparison.Ordinal))
74 {
75 continue;
76 }
77
78 cabNames.Add(new TaskItem(Path.Combine(Path.GetDirectoryName(databaseFile), cabName)));
79 }
80 }
81
82 this.cabList = cabNames.ToArray();
83
84 return true;
85 }
86 }
87}
diff --git a/src/WixToolset.BuildTasks/GetLooseFileList.cs b/src/WixToolset.BuildTasks/GetLooseFileList.cs
new file mode 100644
index 00000000..bd403426
--- /dev/null
+++ b/src/WixToolset.BuildTasks/GetLooseFileList.cs
@@ -0,0 +1,230 @@
1// Copyright (c) .NET Foundation and 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.BuildTasks
4{
5 using System;
6 using System.Collections;
7 using System.Collections.Generic;
8 using System.Diagnostics;
9 using System.IO;
10 using System.Reflection;
11 using System.Xml;
12 using Microsoft.Build.Framework;
13 using Microsoft.Build.Utilities;
14 using WixToolset.Dtf.WindowsInstaller;
15 using Microsoft.Win32;
16
17 /// <summary>
18 /// This task assigns Culture metadata to files based on the value of the Culture attribute on the
19 /// WixLocalization element inside the file.
20 /// </summary>
21 public class GetLooseFileList : Task
22 {
23 private ITaskItem database;
24 private ITaskItem[] looseFileList;
25
26 internal const int MsidbFileAttributesNoncompressed = 8192;
27 internal const int MsidbFileAttributesCompressed = 16384;
28
29 /// <summary>
30 /// The list of database files to find Loose Files in
31 /// </summary>
32 [Required]
33 public ITaskItem Database
34 {
35 get { return this.database; }
36 set { this.database = value; }
37 }
38
39 /// <summary>
40 /// The total list of Loose Files in this database
41 /// </summary>
42 [Output]
43 public ITaskItem[] LooseFileList
44 {
45 get { return this.looseFileList; }
46 }
47
48 /// <summary>
49 /// Takes the "defaultDir" column
50 /// </summary>
51 /// <returns>Returns the corresponding sourceDir.</returns>
52 public string SourceDirFromDefaultDir(string defaultDir)
53 {
54 string sourceDir;
55
56 string[] splitted = defaultDir.Split(':');
57
58 if (1 == splitted.Length)
59 {
60 sourceDir = splitted[0];
61 }
62 else
63 {
64 sourceDir = splitted[1];
65 }
66
67 splitted = sourceDir.Split('|');
68
69 if (1 == splitted.Length)
70 {
71 sourceDir = splitted[0];
72 }
73 else
74 {
75 sourceDir = splitted[1];
76 }
77
78 return sourceDir;
79 }
80
81 /// <summary>
82 /// Takes the "FileName" column
83 /// </summary>
84 /// <returns>Returns the corresponding source file name.</returns>
85 public string SourceFileFromFileName(string fileName)
86 {
87 string sourceFile;
88
89 string[] splitted = fileName.Split('|');
90
91 if (1 == splitted.Length)
92 {
93 sourceFile = splitted[0];
94 }
95 else
96 {
97 sourceFile = splitted[1];
98 }
99
100 return sourceFile;
101 }
102
103 /// <summary>
104 /// Gets a complete list of external Loose Files referenced by the given installer database file.
105 /// </summary>
106 /// <returns>True upon completion of the task execution.</returns>
107 public override bool Execute()
108 {
109 string databaseFile = this.database.ItemSpec;
110 Object []emptyArgs = { };
111 System.Collections.Generic.List<ITaskItem> looseFileNames = new System.Collections.Generic.List<ITaskItem>();
112 Dictionary<string, string> ComponentFullDirectory = new Dictionary<string, string>();
113 Dictionary<string, string> DirectoryIdDefaultDir = new Dictionary<string, string>();
114 Dictionary<string, string> DirectoryIdParent = new Dictionary<string, string>();
115 Dictionary<string, string> DirectoryIdFullSource = new Dictionary<string, string>();
116 int i;
117 string databaseDir = Path.GetDirectoryName(databaseFile);
118
119 // If the file doesn't exist, no Loose Files to return, so exit now
120 if (!File.Exists(databaseFile))
121 {
122 return true;
123 }
124
125 using (Database database = new Database(databaseFile))
126 {
127 bool compressed = false;
128 if (2 == (database.SummaryInfo.WordCount & 2))
129 {
130 compressed = true;
131 }
132
133 // If the media table doesn't exist, no Loose Files to return, so exit now
134 if (null == database.Tables["File"])
135 {
136 return true;
137 }
138
139 // Only setup all these helpful indexes if the database is marked as uncompressed. If it's marked as compressed, files are stored at the root,
140 // so none of these indexes will be used
141 if (!compressed)
142 {
143 if (null == database.Tables["Directory"] || null == database.Tables["Component"])
144 {
145 return true;
146 }
147
148 System.Collections.IList directoryRecords = database.ExecuteQuery("SELECT `Directory`,`Directory_Parent`,`DefaultDir` FROM `Directory`", emptyArgs);
149
150 // First setup a simple index from DirectoryId to DefaultDir
151 for (i = 0; i < directoryRecords.Count; i += 3)
152 {
153 string directoryId = (string)(directoryRecords[i]);
154 string directoryParent = (string)(directoryRecords[i + 1]);
155 string defaultDir = (string)(directoryRecords[i + 2]);
156
157 string sourceDir = SourceDirFromDefaultDir(defaultDir);
158
159 DirectoryIdDefaultDir[directoryId] = sourceDir;
160 DirectoryIdParent[directoryId] = directoryParent;
161 }
162
163 // Setup an index from directory Id to the full source path
164 for (i = 0; i < directoryRecords.Count; i += 3)
165 {
166 string directoryId = (string)(directoryRecords[i]);
167 string directoryParent = (string)(directoryRecords[i + 1]);
168 string defaultDir = (string)(directoryRecords[i + 2]);
169
170 string sourceDir = DirectoryIdDefaultDir[directoryId];
171
172 // The TARGETDIR case
173 if (String.IsNullOrEmpty(directoryParent))
174 {
175 DirectoryIdFullSource[directoryId] = databaseDir;
176 }
177 else
178 {
179 string tempDirectoryParent = directoryParent;
180
181 while (!String.IsNullOrEmpty(tempDirectoryParent) && !String.IsNullOrEmpty(DirectoryIdParent[tempDirectoryParent]))
182 {
183 sourceDir = Path.Combine(DirectoryIdDefaultDir[tempDirectoryParent], sourceDir);
184
185 tempDirectoryParent = DirectoryIdParent[tempDirectoryParent];
186 }
187
188 DirectoryIdFullSource[directoryId] = Path.Combine(databaseDir, sourceDir);
189 }
190 }
191
192 // Setup an index from component Id to full directory path
193 System.Collections.IList componentRecords = database.ExecuteQuery("SELECT `Component`,`Directory_` FROM `Component`", emptyArgs);
194
195 for (i = 0; i < componentRecords.Count; i += 2)
196 {
197 string componentId = (string)(componentRecords[i]);
198 string componentDir = (string)(componentRecords[i + 1]);
199
200 ComponentFullDirectory[componentId] = DirectoryIdFullSource[componentDir];
201 }
202 }
203
204 System.Collections.IList fileRecords = database.ExecuteQuery("SELECT `Component_`,`FileName`,`Attributes` FROM `File`", emptyArgs);
205
206 for (i = 0; i < fileRecords.Count; i += 3)
207 {
208 string componentId = (string)(fileRecords[i]);
209 string fileName = SourceFileFromFileName((string)(fileRecords[i + 1]));
210 int attributes = (int)(fileRecords[i + 2]);
211
212 // If the whole database is marked uncompressed, use the directory layout made above
213 if ((!compressed && MsidbFileAttributesCompressed != (attributes & MsidbFileAttributesCompressed)))
214 {
215 looseFileNames.Add(new TaskItem(Path.GetFullPath(Path.Combine(ComponentFullDirectory[componentId], fileName))));
216 }
217 // If the database is marked as compressed, put files at the root
218 else if (compressed && (MsidbFileAttributesNoncompressed == (attributes & MsidbFileAttributesNoncompressed)))
219 {
220 looseFileNames.Add(new TaskItem(Path.GetFullPath(Path.Combine(databaseDir, fileName))));
221 }
222 }
223 }
224
225 this.looseFileList = looseFileNames.ToArray();
226
227 return true;
228 }
229 }
230}
diff --git a/src/WixToolset.BuildTasks/GlobalSuppressions.cs b/src/WixToolset.BuildTasks/GlobalSuppressions.cs
new file mode 100644
index 00000000..65c34c13
--- /dev/null
+++ b/src/WixToolset.BuildTasks/GlobalSuppressions.cs
@@ -0,0 +1,8 @@
1// Copyright (c) .NET Foundation 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[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "WixToolset")]
4
5[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Msi", Scope = "namespace", Target = "WixToolset.BuildTasks")]
6[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "wix")]
7[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Wix")]
8[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "wix")]
diff --git a/src/WixToolset.BuildTasks/Insignia.cs b/src/WixToolset.BuildTasks/Insignia.cs
new file mode 100644
index 00000000..ba30963a
--- /dev/null
+++ b/src/WixToolset.BuildTasks/Insignia.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.BuildTasks
4{
5 using System;
6 using System.Diagnostics;
7 using System.Globalization;
8 using System.IO;
9 using System.Text;
10
11 using Microsoft.Build.Framework;
12 using Microsoft.Build.Utilities;
13
14 /// <summary>
15 /// An MSBuild task to run the WiX transform generator.
16 /// </summary>
17 public sealed class Insignia : WixToolTask
18 {
19 private const string InsigniaToolName = "insignia.exe";
20
21 /// <summary>
22 /// Gets or sets the path to the database to inscribe.
23 /// </summary>
24 public ITaskItem DatabaseFile { get; set; }
25
26 /// <summary>
27 /// Gets or sets the path to the bundle to inscribe.
28 /// </summary>
29 public ITaskItem BundleFile { get; set; }
30
31 /// <summary>
32 /// Gets or sets the path to the original bundle that contains the attached container.
33 /// </summary>
34 public ITaskItem OriginalBundleFile { get; set; }
35
36 /// <summary>
37 /// Gets or sets the path to output the inscribed result.
38 /// </summary>
39 [Required]
40 public ITaskItem OutputFile { get; set; }
41
42 /// <summary>
43 /// Gets or sets the output. Only set if insignia does work.
44 /// </summary>
45 [Output]
46 public ITaskItem Output { get; set; }
47
48 /// <summary>
49 /// Get the name of the executable.
50 /// </summary>
51 /// <remarks>The ToolName is used with the ToolPath to get the location of Insignia.exe.</remarks>
52 /// <value>The name of the executable.</value>
53 protected override string ToolName
54 {
55 get { return InsigniaToolName; }
56 }
57
58 /// <summary>
59 /// Get the path to the executable.
60 /// </summary>
61 /// <remarks>GetFullPathToTool is only called when the ToolPath property is not set (see the ToolName remarks above).</remarks>
62 /// <returns>The full path to the executable or simply Insignia.exe if it's expected to be in the system path.</returns>
63 protected override string GenerateFullPathToTool()
64 {
65 // If there's not a ToolPath specified, it has to be in the system path.
66 if (String.IsNullOrEmpty(this.ToolPath))
67 {
68 return InsigniaToolName;
69 }
70
71 return Path.Combine(Path.GetFullPath(this.ToolPath), InsigniaToolName);
72 }
73
74 /// <summary>
75 /// Builds a command line from options in this task.
76 /// </summary>
77 protected override void BuildCommandLine(WixCommandLineBuilder commandLineBuilder)
78 {
79 base.BuildCommandLine(commandLineBuilder);
80
81 commandLineBuilder.AppendSwitchIfNotNull("-im ", this.DatabaseFile);
82 if (null != this.OriginalBundleFile)
83 {
84 commandLineBuilder.AppendSwitchIfNotNull("-ab ", this.BundleFile);
85 commandLineBuilder.AppendFileNameIfNotNull(this.OriginalBundleFile);
86 }
87 else
88 {
89 commandLineBuilder.AppendSwitchIfNotNull("-ib ", this.BundleFile);
90 }
91
92 commandLineBuilder.AppendSwitchIfNotNull("-out ", this.OutputFile);
93 commandLineBuilder.AppendTextIfNotNull(this.AdditionalOptions);
94 }
95
96 /// <summary>
97 /// Executes a tool in-process by loading the tool assembly and invoking its entrypoint.
98 /// </summary>
99 /// <param name="pathToTool">Path to the tool to be executed; must be a managed executable.</param>
100 /// <param name="responseFileCommands">Commands to be written to a response file.</param>
101 /// <param name="commandLineCommands">Commands to be passed directly on the command-line.</param>
102 /// <returns>The tool exit code.</returns>
103 protected override int ExecuteTool(string pathToTool, string responseFileCommands, string commandLineCommands)
104 {
105 int returnCode = base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands);
106 if (0 == returnCode) // successfully did work.
107 {
108 this.Output = this.OutputFile;
109 }
110 else if (-1 == returnCode) // no work done.
111 {
112 returnCode = 0;
113 }
114
115 return returnCode;
116 }
117 }
118}
diff --git a/src/WixToolset.BuildTasks/Light.cs b/src/WixToolset.BuildTasks/Light.cs
new file mode 100644
index 00000000..b7d0b4f7
--- /dev/null
+++ b/src/WixToolset.BuildTasks/Light.cs
@@ -0,0 +1,488 @@
1// Copyright (c) .NET Foundation and 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.BuildTasks
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.Diagnostics.CodeAnalysis;
9 using System.Globalization;
10 using System.IO;
11 using System.Text;
12
13 using Microsoft.Build.Framework;
14 using Microsoft.Build.Utilities;
15
16 /// <summary>
17 /// An MSBuild task to run the WiX linker.
18 /// </summary>
19 public sealed class Light : WixToolTask
20 {
21 private const string LightToolName = "Light.exe";
22
23 private string additionalCub;
24 private bool allowIdenticalRows;
25 private bool allowUnresolvedReferences;
26 private string[] baseInputPaths;
27 private ITaskItem[] bindInputPaths;
28 private bool backwardsCompatibleGuidGeneration;
29 private bool bindFiles;
30 private ITaskItem builtOutputsFile;
31 private string cabinetCachePath;
32 private int cabinetCreationThreadCount = WixCommandLineBuilder.Unspecified;
33 private ITaskItem contentsFile;
34 private string cultures;
35 private string customBinder;
36 private string defaultCompressionLevel;
37 private ITaskItem[] extensions;
38 private string[] ices;
39 private bool leaveTemporaryFiles;
40 private ITaskItem[] localizationFiles;
41 private ITaskItem[] objectFiles;
42 private bool outputAsXml;
43 private ITaskItem outputsFile;
44 private ITaskItem outputFile;
45 private ITaskItem pdbOutputFile;
46 private ITaskItem wixProjectFile;
47 private bool pedantic;
48 private bool reuseCabinetCache;
49 private bool suppressAclReset;
50 private bool suppressAssemblies;
51 private bool suppressDefaultAdminSequenceActions;
52 private bool suppressDefaultAdvSequenceActions;
53 private bool suppressDefaultUISequenceActions;
54 private bool dropUnrealTables;
55 private bool exactAssemblyVersions;
56 private bool suppressFileHashAndInfo;
57 private bool suppressFiles;
58 private bool suppressIntermediateFileVersionMatching;
59 private string[] suppressIces;
60 private bool suppressLayout;
61 private bool suppressLocalization;
62 private bool suppressMsiAssemblyTableProcessing;
63 private bool suppressPdbOutput;
64 private bool suppressSchemaValidation;
65 private bool suppressValidation;
66 private bool suppressTagSectionIdAttributeOnTuples;
67 private ITaskItem unreferencedSymbolsFile;
68 private string[] wixVariables;
69 private string extensionDirectory;
70 private string[] referencePaths;
71
72 /// <summary>
73 /// Creates a new light task.
74 /// </summary>
75 /// <remarks>
76 /// Defaults to running the task as a separate process, instead of in-proc
77 /// which is the default for WixToolTasks. This allows the Win32 manifest file
78 /// embedded in light.exe to enable reg-free COM interop with mergemod.dll.
79 /// </remarks>
80 public Light()
81 {
82 }
83
84 public string AdditionalCub
85 {
86 get { return this.additionalCub; }
87 set { this.additionalCub = value; }
88 }
89
90 public bool AllowIdenticalRows
91 {
92 get { return this.allowIdenticalRows; }
93 set { this.allowIdenticalRows = value; }
94 }
95
96 public bool AllowUnresolvedReferences
97 {
98 get { return this.allowUnresolvedReferences; }
99 set { this.allowUnresolvedReferences = value; }
100 }
101
102 // TODO: remove this property entirely in v4.0
103 [Obsolete("Use BindInputPaths instead of BaseInputPaths.")]
104 public string[] BaseInputPaths
105 {
106 get { return this.baseInputPaths; }
107 set { this.baseInputPaths = value; }
108 }
109
110 public ITaskItem[] BindInputPaths
111 {
112 get { return this.bindInputPaths; }
113 set { this.bindInputPaths = value; }
114 }
115
116 public bool BackwardsCompatibleGuidGeneration
117 {
118 get { return this.backwardsCompatibleGuidGeneration; }
119 set { this.backwardsCompatibleGuidGeneration = value; }
120 }
121
122 public bool BindFiles
123 {
124 get { return this.bindFiles; }
125 set { this.bindFiles = value; }
126 }
127
128 public string CabinetCachePath
129 {
130 get { return this.cabinetCachePath; }
131 set { this.cabinetCachePath = value; }
132 }
133
134 public int CabinetCreationThreadCount
135 {
136 get { return this.cabinetCreationThreadCount; }
137 set { this.cabinetCreationThreadCount = value; }
138 }
139
140 public ITaskItem BindBuiltOutputsFile
141 {
142 get { return this.builtOutputsFile; }
143 set { this.builtOutputsFile = value; }
144 }
145
146 public ITaskItem BindContentsFile
147 {
148 get { return this.contentsFile; }
149 set { this.contentsFile = value; }
150 }
151
152 public ITaskItem BindOutputsFile
153 {
154 get { return this.outputsFile; }
155 set { this.outputsFile = value; }
156 }
157
158 public string Cultures
159 {
160 get { return this.cultures; }
161 set { this.cultures = value; }
162 }
163
164 public string CustomBinder
165 {
166 get { return this.customBinder; }
167 set { this.customBinder = value; }
168 }
169
170 public string DefaultCompressionLevel
171 {
172 get { return this.defaultCompressionLevel; }
173 set { this.defaultCompressionLevel = value; }
174 }
175
176 public bool DropUnrealTables
177 {
178 get { return this.dropUnrealTables; }
179 set { this.dropUnrealTables = value; }
180 }
181
182 public bool ExactAssemblyVersions
183 {
184 get { return this.exactAssemblyVersions; }
185 set { this.exactAssemblyVersions = value; }
186 }
187
188 public ITaskItem[] Extensions
189 {
190 get { return this.extensions; }
191 set { this.extensions = value; }
192 }
193
194 public string[] Ices
195 {
196 get { return this.ices; }
197 set { this.ices = value; }
198 }
199
200 public bool LeaveTemporaryFiles
201 {
202 get { return this.leaveTemporaryFiles; }
203 set { this.leaveTemporaryFiles = value; }
204 }
205
206 public ITaskItem[] LocalizationFiles
207 {
208 get { return this.localizationFiles; }
209 set { this.localizationFiles = value; }
210 }
211
212 [Required]
213 public ITaskItem[] ObjectFiles
214 {
215 get { return this.objectFiles; }
216 set { this.objectFiles = value; }
217 }
218
219 public bool OutputAsXml
220 {
221 get { return this.outputAsXml; }
222 set { this.outputAsXml = value; }
223 }
224
225 [Required]
226 [Output]
227 public ITaskItem OutputFile
228 {
229 get { return this.outputFile; }
230 set { this.outputFile = value; }
231 }
232
233 [Output]
234 public ITaskItem PdbOutputFile
235 {
236 get { return this.pdbOutputFile; }
237 set { this.pdbOutputFile = value; }
238 }
239
240 public bool Pedantic
241 {
242 get { return this.pedantic; }
243 set { this.pedantic = value; }
244 }
245
246 public bool ReuseCabinetCache
247 {
248 get { return this.reuseCabinetCache; }
249 set { this.reuseCabinetCache = value; }
250 }
251
252 public bool SuppressAclReset
253 {
254 get { return this.suppressAclReset; }
255 set { this.suppressAclReset = value; }
256 }
257
258 public bool SuppressAssemblies
259 {
260 get { return this.suppressAssemblies; }
261 set { this.suppressAssemblies = value; }
262 }
263
264 public bool SuppressDefaultAdminSequenceActions
265 {
266 get { return this.suppressDefaultAdminSequenceActions; }
267 set { this.suppressDefaultAdminSequenceActions = value; }
268 }
269
270 public bool SuppressDefaultAdvSequenceActions
271 {
272 get { return this.suppressDefaultAdvSequenceActions; }
273 set { this.suppressDefaultAdvSequenceActions = value; }
274 }
275
276 public bool SuppressDefaultUISequenceActions
277 {
278 get { return this.suppressDefaultUISequenceActions; }
279 set { this.suppressDefaultUISequenceActions = value; }
280 }
281
282 public bool SuppressFileHashAndInfo
283 {
284 get { return this.suppressFileHashAndInfo; }
285 set { this.suppressFileHashAndInfo = value; }
286 }
287
288 public bool SuppressFiles
289 {
290 get { return this.suppressFiles; }
291 set { this.suppressFiles = value; }
292 }
293
294 public bool SuppressIntermediateFileVersionMatching
295 {
296 get { return this.suppressIntermediateFileVersionMatching; }
297 set { this.suppressIntermediateFileVersionMatching = value; }
298 }
299
300 public string[] SuppressIces
301 {
302 get { return this.suppressIces; }
303 set { this.suppressIces = value; }
304 }
305
306 public bool SuppressLayout
307 {
308 get { return this.suppressLayout; }
309 set { this.suppressLayout = value; }
310 }
311
312 public bool SuppressLocalization
313 {
314 get { return this.suppressLocalization; }
315 set { this.suppressLocalization = value; }
316 }
317
318 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")]
319 public bool SuppressMsiAssemblyTableProcessing
320 {
321 get { return this.suppressMsiAssemblyTableProcessing; }
322 set { this.suppressMsiAssemblyTableProcessing = value; }
323 }
324
325 public bool SuppressPdbOutput
326 {
327 get { return this.suppressPdbOutput; }
328 set { this.suppressPdbOutput = value; }
329 }
330
331 public bool SuppressSchemaValidation
332 {
333 get { return this.suppressSchemaValidation; }
334 set { this.suppressSchemaValidation = value; }
335 }
336
337 public bool SuppressValidation
338 {
339 get { return this.suppressValidation; }
340 set { this.suppressValidation = value; }
341 }
342
343 public bool SuppressTagSectionIdAttributeOnTuples
344 {
345 get { return this.suppressTagSectionIdAttributeOnTuples; }
346 set { this.suppressTagSectionIdAttributeOnTuples = value; }
347 }
348
349 [Output]
350 public ITaskItem UnreferencedSymbolsFile
351 {
352 get { return this.unreferencedSymbolsFile; }
353 set { this.unreferencedSymbolsFile = value; }
354 }
355
356 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")]
357 public ITaskItem WixProjectFile
358 {
359 get { return this.wixProjectFile; }
360 set { this.wixProjectFile = value; }
361 }
362
363 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")]
364 public string[] WixVariables
365 {
366 get { return this.wixVariables; }
367 set { this.wixVariables = value; }
368 }
369
370 public string ExtensionDirectory
371 {
372 get { return this.extensionDirectory; }
373 set { this.extensionDirectory = value; }
374 }
375
376 public string[] ReferencePaths
377 {
378 get { return this.referencePaths; }
379 set { this.referencePaths = value; }
380 }
381
382 /// <summary>
383 /// Get the name of the executable.
384 /// </summary>
385 /// <remarks>The ToolName is used with the ToolPath to get the location of light.exe.</remarks>
386 /// <value>The name of the executable.</value>
387 protected override string ToolName
388 {
389 get { return LightToolName; }
390 }
391
392 /// <summary>
393 /// Get the path to the executable.
394 /// </summary>
395 /// <remarks>GetFullPathToTool is only called when the ToolPath property is not set (see the ToolName remarks above).</remarks>
396 /// <returns>The full path to the executable or simply light.exe if it's expected to be in the system path.</returns>
397 protected override string GenerateFullPathToTool()
398 {
399 // If there's not a ToolPath specified, it has to be in the system path.
400 if (String.IsNullOrEmpty(this.ToolPath))
401 {
402 return LightToolName;
403 }
404
405 return Path.Combine(Path.GetFullPath(this.ToolPath), LightToolName);
406 }
407
408 /// <summary>
409 /// Builds a command line from options in this task.
410 /// </summary>
411 protected override void BuildCommandLine(WixCommandLineBuilder commandLineBuilder)
412 {
413 // Always put the output first so it is easy to find in the log.
414 commandLineBuilder.AppendSwitchIfNotNull("-out ", this.OutputFile);
415 commandLineBuilder.AppendSwitchIfNotNull("-pdbout ", this.PdbOutputFile);
416
417 base.BuildCommandLine(commandLineBuilder);
418
419 commandLineBuilder.AppendIfTrue("-ai", this.AllowIdenticalRows);
420 commandLineBuilder.AppendIfTrue("-au", this.AllowUnresolvedReferences);
421 commandLineBuilder.AppendArrayIfNotNull("-b ", this.baseInputPaths);
422
423 if (null != this.BindInputPaths)
424 {
425 Queue<String> formattedBindInputPaths = new Queue<String>();
426 foreach (ITaskItem item in this.BindInputPaths)
427 {
428 String formattedPath = string.Empty;
429 String bindName = item.GetMetadata("BindName");
430 if (!String.IsNullOrEmpty(bindName))
431 {
432 formattedPath = String.Concat(bindName, "=", item.GetMetadata("FullPath"));
433 }
434 else
435 {
436 formattedPath = item.GetMetadata("FullPath");
437 }
438 formattedBindInputPaths.Enqueue(formattedPath);
439 }
440 commandLineBuilder.AppendArrayIfNotNull("-b ", formattedBindInputPaths.ToArray());
441 }
442
443 commandLineBuilder.AppendIfTrue("-bcgg", this.BackwardsCompatibleGuidGeneration);
444 commandLineBuilder.AppendIfTrue("-bf", this.BindFiles);
445 commandLineBuilder.AppendSwitchIfNotNull("-cc ", this.CabinetCachePath);
446 commandLineBuilder.AppendIfSpecified("-ct ", this.CabinetCreationThreadCount);
447 commandLineBuilder.AppendSwitchIfNotNull("-cub ", this.AdditionalCub);
448 commandLineBuilder.AppendSwitchIfNotNull("-cultures:", this.Cultures);
449 commandLineBuilder.AppendSwitchIfNotNull("-binder ", this.CustomBinder);
450 commandLineBuilder.AppendArrayIfNotNull("-d", this.WixVariables);
451 commandLineBuilder.AppendSwitchIfNotNull("-dcl:", this.DefaultCompressionLevel);
452 commandLineBuilder.AppendIfTrue("-dut", this.DropUnrealTables);
453 commandLineBuilder.AppendIfTrue("-eav", this.ExactAssemblyVersions);
454 commandLineBuilder.AppendExtensions(this.Extensions, this.ExtensionDirectory, this.referencePaths);
455 commandLineBuilder.AppendArrayIfNotNull("-ice:", this.Ices);
456 commandLineBuilder.AppendArrayIfNotNull("-loc ", this.LocalizationFiles);
457 commandLineBuilder.AppendIfTrue("-notidy", this.LeaveTemporaryFiles);
458 commandLineBuilder.AppendIfTrue("-pedantic", this.Pedantic);
459 commandLineBuilder.AppendIfTrue("-reusecab", this.ReuseCabinetCache);
460 commandLineBuilder.AppendIfTrue("-sa", this.SuppressAssemblies);
461 commandLineBuilder.AppendIfTrue("-sacl", this.SuppressAclReset);
462 commandLineBuilder.AppendIfTrue("-sadmin", this.SuppressDefaultAdminSequenceActions);
463 commandLineBuilder.AppendIfTrue("-sadv", this.SuppressDefaultAdvSequenceActions);
464 commandLineBuilder.AppendArrayIfNotNull("-sice:", this.SuppressIces);
465 commandLineBuilder.AppendIfTrue("-sma", this.SuppressMsiAssemblyTableProcessing);
466 commandLineBuilder.AppendIfTrue("-sf", this.SuppressFiles);
467 commandLineBuilder.AppendIfTrue("-sh", this.SuppressFileHashAndInfo);
468 commandLineBuilder.AppendIfTrue("-sl", this.SuppressLayout);
469 commandLineBuilder.AppendIfTrue("-sloc", this.SuppressLocalization);
470 commandLineBuilder.AppendIfTrue("-spdb", this.SuppressPdbOutput);
471 commandLineBuilder.AppendIfTrue("-ss", this.SuppressSchemaValidation);
472 commandLineBuilder.AppendIfTrue("-sts", this.SuppressTagSectionIdAttributeOnTuples);
473 commandLineBuilder.AppendIfTrue("-sui", this.SuppressDefaultUISequenceActions);
474 commandLineBuilder.AppendIfTrue("-sv", this.SuppressIntermediateFileVersionMatching);
475 commandLineBuilder.AppendIfTrue("-sval", this.SuppressValidation);
476 commandLineBuilder.AppendSwitchIfNotNull("-usf ", this.UnreferencedSymbolsFile);
477 commandLineBuilder.AppendIfTrue("-xo", this.OutputAsXml);
478 commandLineBuilder.AppendSwitchIfNotNull("-contentsfile ", this.BindContentsFile);
479 commandLineBuilder.AppendSwitchIfNotNull("-outputsfile ", this.BindOutputsFile);
480 commandLineBuilder.AppendSwitchIfNotNull("-builtoutputsfile ", this.BindBuiltOutputsFile);
481 commandLineBuilder.AppendSwitchIfNotNull("-wixprojectfile ", this.WixProjectFile);
482 commandLineBuilder.AppendTextIfNotNull(this.AdditionalOptions);
483
484 List<string> objectFilePaths = AdjustFilePaths(this.objectFiles, this.ReferencePaths);
485 commandLineBuilder.AppendFileNamesIfNotNull(objectFilePaths.ToArray(), " ");
486 }
487 }
488}
diff --git a/src/WixToolset.BuildTasks/Lit.cs b/src/WixToolset.BuildTasks/Lit.cs
new file mode 100644
index 00000000..1df964ae
--- /dev/null
+++ b/src/WixToolset.BuildTasks/Lit.cs
@@ -0,0 +1,178 @@
1// Copyright (c) .NET Foundation and 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.BuildTasks
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.Globalization;
9 using System.IO;
10 using System.Text;
11
12 using Microsoft.Build.Framework;
13 using Microsoft.Build.Utilities;
14
15 /// <summary>
16 /// An MSBuild task to run the WiX lib tool.
17 /// </summary>
18 public sealed class Lit : WixToolTask
19 {
20 private const string LitToolName = "lit.exe";
21
22 private string[] baseInputPaths;
23 private ITaskItem[] bindInputPaths;
24 private bool bindFiles;
25 private ITaskItem[] extensions;
26 private ITaskItem[] localizationFiles;
27 private ITaskItem[] objectFiles;
28 private ITaskItem outputFile;
29 private bool pedantic;
30 private bool suppressIntermediateFileVersionMatching;
31 private bool suppressSchemaValidation;
32 private string extensionDirectory;
33 private string[] referencePaths;
34
35 // TODO: remove this property entirely in v4.0
36 [Obsolete("Use BindInputPaths instead of BaseInputPaths.")]
37 public string[] BaseInputPaths
38 {
39 get { return this.baseInputPaths; }
40 set { this.baseInputPaths = value; }
41 }
42
43 public ITaskItem[] BindInputPaths
44 {
45 get { return this.bindInputPaths; }
46 set { this.bindInputPaths = value; }
47 }
48
49 public bool BindFiles
50 {
51 get { return this.bindFiles; }
52 set { this.bindFiles = value; }
53 }
54
55 public ITaskItem[] Extensions
56 {
57 get { return this.extensions; }
58 set { this.extensions = value; }
59 }
60
61 public ITaskItem[] LocalizationFiles
62 {
63 get { return this.localizationFiles; }
64 set { this.localizationFiles = value; }
65 }
66
67 [Required]
68 public ITaskItem[] ObjectFiles
69 {
70 get { return this.objectFiles; }
71 set { this.objectFiles = value; }
72 }
73
74 [Required]
75 [Output]
76 public ITaskItem OutputFile
77 {
78 get { return this.outputFile; }
79 set { this.outputFile = value; }
80 }
81
82 public bool Pedantic
83 {
84 get { return this.pedantic; }
85 set { this.pedantic = value; }
86 }
87
88 public bool SuppressIntermediateFileVersionMatching
89 {
90 get { return this.suppressIntermediateFileVersionMatching; }
91 set { this.suppressIntermediateFileVersionMatching = value; }
92 }
93
94 public bool SuppressSchemaValidation
95 {
96 get { return this.suppressSchemaValidation; }
97 set { this.suppressSchemaValidation = value; }
98 }
99
100 public string ExtensionDirectory
101 {
102 get { return this.extensionDirectory; }
103 set { this.extensionDirectory = value; }
104 }
105
106 public string[] ReferencePaths
107 {
108 get { return this.referencePaths; }
109 set { this.referencePaths = value; }
110 }
111
112 /// <summary>
113 /// Get the name of the executable.
114 /// </summary>
115 /// <remarks>The ToolName is used with the ToolPath to get the location of lit.exe</remarks>
116 /// <value>The name of the executable.</value>
117 protected override string ToolName
118 {
119 get { return LitToolName; }
120 }
121
122 /// <summary>
123 /// Get the path to the executable.
124 /// </summary>
125 /// <remarks>GetFullPathToTool is only called when the ToolPath property is not set (see the ToolName remarks above).</remarks>
126 /// <returns>The full path to the executable or simply lit.exe if it's expected to be in the system path.</returns>
127 protected override string GenerateFullPathToTool()
128 {
129 // If there's not a ToolPath specified, it has to be in the system path.
130 if (String.IsNullOrEmpty(this.ToolPath))
131 {
132 return LitToolName;
133 }
134
135 return Path.Combine(Path.GetFullPath(this.ToolPath), LitToolName);
136 }
137
138 /// <summary>
139 /// Builds a command line from options in this task.
140 /// </summary>
141 protected override void BuildCommandLine(WixCommandLineBuilder commandLineBuilder)
142 {
143 base.BuildCommandLine(commandLineBuilder);
144
145 commandLineBuilder.AppendSwitchIfNotNull("-out ", this.OutputFile);
146 commandLineBuilder.AppendArrayIfNotNull("-b ", this.baseInputPaths);
147 if (null != this.BindInputPaths)
148 {
149 Queue<String> formattedBindInputPaths = new Queue<String>();
150 foreach (ITaskItem item in this.BindInputPaths)
151 {
152 String formattedPath = string.Empty;
153 String bindName = item.GetMetadata("BindName");
154 if (!String.IsNullOrEmpty(item.GetMetadata("BindName")))
155 {
156 formattedPath = String.Concat(bindName, "=", item.GetMetadata("FullPath"));
157 }
158 else
159 {
160 formattedPath = item.GetMetadata("FullPath");
161 }
162 formattedBindInputPaths.Enqueue(formattedPath);
163 }
164 commandLineBuilder.AppendArrayIfNotNull("-b ", formattedBindInputPaths.ToArray());
165 }
166 commandLineBuilder.AppendIfTrue("-bf", this.BindFiles);
167 commandLineBuilder.AppendExtensions(this.extensions, this.ExtensionDirectory, this.referencePaths);
168 commandLineBuilder.AppendArrayIfNotNull("-loc ", this.LocalizationFiles);
169 commandLineBuilder.AppendIfTrue("-pedantic", this.Pedantic);
170 commandLineBuilder.AppendIfTrue("-ss", this.SuppressSchemaValidation);
171 commandLineBuilder.AppendIfTrue("-sv", this.SuppressIntermediateFileVersionMatching);
172 commandLineBuilder.AppendTextIfNotNull(this.AdditionalOptions);
173
174 List<string> objectFilePaths = AdjustFilePaths(this.objectFiles, this.ReferencePaths);
175 commandLineBuilder.AppendFileNamesIfNotNull(objectFilePaths.ToArray(), " ");
176 }
177 }
178}
diff --git a/src/WixToolset.BuildTasks/Pyro.cs b/src/WixToolset.BuildTasks/Pyro.cs
new file mode 100644
index 00000000..f6b069da
--- /dev/null
+++ b/src/WixToolset.BuildTasks/Pyro.cs
@@ -0,0 +1,140 @@
1// Copyright (c) .NET Foundation and 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.BuildTasks
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using Microsoft.Build.Framework;
9
10 /// <summary>
11 /// An MSBuild task to run the WiX patch builder.
12 /// </summary>
13 public sealed class Pyro : WixToolTask
14 {
15 private const string PyroToolName = "pyro.exe";
16
17 public bool BinaryDeltaPatch { get; set; }
18 public string CabinetCachePath { get; set; }
19 public string ExtensionDirectory { get; set; }
20 public ITaskItem[] Extensions { get; set; }
21 public bool LeaveTemporaryFiles { get; set; }
22 public string[] ReferencePaths { get; set; }
23 public bool ReuseCabinetCache { get; set; }
24 public bool SuppressAssemblies { get; set; }
25 public bool SuppressFiles { get; set; }
26 public bool SuppressFileHashAndInfo { get; set; }
27 public bool SuppressPdbOutput { get; set; }
28
29 [Required]
30 public string DefaultBaselineId { get; set; }
31
32 public ITaskItem[] BindInputPathsForTarget { get; set; }
33 public ITaskItem[] BindInputPathsForUpdated { get; set; }
34
35 [Required]
36 public ITaskItem InputFile { get; set; }
37
38 [Required]
39 [Output]
40 public ITaskItem OutputFile { get; set; }
41
42 [Output]
43 public ITaskItem PdbOutputFile { get; set; }
44
45 [Required]
46 public ITaskItem[] Transforms { get; set; }
47
48 /// <summary>
49 /// Get the name of the executable.
50 /// </summary>
51 /// <remarks>The ToolName is used with the ToolPath to get the location of pyro.exe.</remarks>
52 /// <value>The name of the executable.</value>
53 protected override string ToolName
54 {
55 get { return PyroToolName; }
56 }
57
58 /// <summary>
59 /// Get the path to the executable.
60 /// </summary>
61 /// <remarks>GetFullPathToTool is only called when the ToolPath property is not set (see the ToolName remarks above).</remarks>
62 /// <returns>The full path to the executable or simply torch.exe if it's expected to be in the system path.</returns>
63 protected override string GenerateFullPathToTool()
64 {
65 // If there's not a ToolPath specified, it has to be in the system path.
66 if (String.IsNullOrEmpty(this.ToolPath))
67 {
68 return PyroToolName;
69 }
70
71 return Path.Combine(Path.GetFullPath(this.ToolPath), PyroToolName);
72 }
73
74 /// <summary>
75 /// Builds a command line for bind-input paths (-bt and -bu switches).
76 /// </summary>
77 private void AppendBindInputPaths(WixCommandLineBuilder commandLineBuilder, IEnumerable<ITaskItem> bindInputPaths, string switchName)
78 {
79 if (null != bindInputPaths)
80 {
81 Queue<String> formattedBindInputPaths = new Queue<String>();
82 foreach (ITaskItem item in bindInputPaths)
83 {
84 String formattedPath = string.Empty;
85 String bindName = item.GetMetadata("BindName");
86 if (!String.IsNullOrEmpty(bindName))
87 {
88 formattedPath = String.Concat(bindName, "=", item.GetMetadata("FullPath"));
89 }
90 else
91 {
92 formattedPath = item.GetMetadata("FullPath");
93 }
94 formattedBindInputPaths.Enqueue(formattedPath);
95 }
96
97 commandLineBuilder.AppendArrayIfNotNull(switchName, formattedBindInputPaths.ToArray());
98 }
99 }
100
101 /// <summary>
102 /// Builds a command line from options in this task.
103 /// </summary>
104 protected override void BuildCommandLine(WixCommandLineBuilder commandLineBuilder)
105 {
106 // Always put the output first so it is easy to find in the log.
107 commandLineBuilder.AppendSwitchIfNotNull("-out ", this.OutputFile);
108 commandLineBuilder.AppendSwitchIfNotNull("-pdbout ", this.PdbOutputFile);
109
110 base.BuildCommandLine(commandLineBuilder);
111
112 this.AppendBindInputPaths(commandLineBuilder, this.BindInputPathsForTarget, "-bt ");
113 this.AppendBindInputPaths(commandLineBuilder, this.BindInputPathsForUpdated, "-bu ");
114
115 commandLineBuilder.AppendFileNameIfNotNull(this.InputFile);
116 commandLineBuilder.AppendSwitchIfNotNull("-cc ", this.CabinetCachePath);
117 commandLineBuilder.AppendIfTrue("-delta", this.BinaryDeltaPatch);
118 commandLineBuilder.AppendExtensions(this.Extensions, this.ExtensionDirectory, this.ReferencePaths);
119 commandLineBuilder.AppendIfTrue("-notidy", this.LeaveTemporaryFiles);
120 commandLineBuilder.AppendIfTrue("-reusecab", this.ReuseCabinetCache);
121 commandLineBuilder.AppendIfTrue("-sa", this.SuppressAssemblies);
122 commandLineBuilder.AppendIfTrue("-sf", this.SuppressFiles);
123 commandLineBuilder.AppendIfTrue("-sh", this.SuppressFileHashAndInfo);
124 commandLineBuilder.AppendIfTrue("-spdb", this.SuppressPdbOutput);
125 foreach (ITaskItem transform in this.Transforms)
126 {
127 string transformPath = transform.ItemSpec;
128 string baselineId = transform.GetMetadata("OverrideBaselineId");
129 if (String.IsNullOrEmpty(baselineId))
130 {
131 baselineId = this.DefaultBaselineId;
132 }
133
134 commandLineBuilder.AppendTextIfNotNull(String.Format("-t {0} {1}", baselineId, transformPath));
135 }
136
137 commandLineBuilder.AppendTextIfNotNull(this.AdditionalOptions);
138 }
139 }
140}
diff --git a/src/WixToolset.BuildTasks/RefreshBundleGeneratedFile.cs b/src/WixToolset.BuildTasks/RefreshBundleGeneratedFile.cs
new file mode 100644
index 00000000..5445e0cd
--- /dev/null
+++ b/src/WixToolset.BuildTasks/RefreshBundleGeneratedFile.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.BuildTasks
4{
5 using System;
6 using System.Collections;
7 using System.Globalization;
8 using System.IO;
9 using System.Text.RegularExpressions;
10 using System.Xml;
11 using Microsoft.Build.Framework;
12 using Microsoft.Build.Utilities;
13
14 /// <summary>
15 /// This task refreshes the generated file for bundle projects.
16 /// </summary>
17 public class RefreshBundleGeneratedFile : Task
18 {
19 private static readonly Regex AddPrefix = new Regex(@"^[^a-zA-Z_]", RegexOptions.Compiled);
20 private static readonly Regex IllegalIdentifierCharacters = new Regex(@"[^A-Za-z0-9_\.]|\.{2,}", RegexOptions.Compiled); // non 'words' and assorted valid characters
21
22 private ITaskItem[] generatedFiles;
23 private ITaskItem[] projectReferencePaths;
24
25 /// <summary>
26 /// The list of files to generate.
27 /// </summary>
28 [Required]
29 public ITaskItem[] GeneratedFiles
30 {
31 get { return this.generatedFiles; }
32 set { this.generatedFiles = value; }
33 }
34
35 /// <summary>
36 /// All the project references in the project.
37 /// </summary>
38 [Required]
39 public ITaskItem[] ProjectReferencePaths
40 {
41 get { return this.projectReferencePaths; }
42 set { this.projectReferencePaths = value; }
43 }
44
45 /// <summary>
46 /// Gets a complete list of external cabs referenced by the given installer database file.
47 /// </summary>
48 /// <returns>True upon completion of the task execution.</returns>
49 public override bool Execute()
50 {
51 ArrayList payloadGroupRefs = new ArrayList();
52 ArrayList packageGroupRefs = new ArrayList();
53 for (int i = 0; i < this.ProjectReferencePaths.Length; i++)
54 {
55 ITaskItem item = this.ProjectReferencePaths[i];
56
57 if (!String.IsNullOrEmpty(item.GetMetadata(Common.DoNotHarvest)))
58 {
59 continue;
60 }
61
62 string projectPath = CreateProjectReferenceDefineConstants.GetProjectPath(this.ProjectReferencePaths, i);
63 string projectName = Path.GetFileNameWithoutExtension(projectPath);
64 string referenceName = Common.GetIdentifierFromName(CreateProjectReferenceDefineConstants.GetReferenceName(item, projectName));
65
66 string[] pogs = item.GetMetadata("RefProjectOutputGroups").Split(';');
67 foreach (string pog in pogs)
68 {
69 if (!String.IsNullOrEmpty(pog))
70 {
71 // TODO: Add payload group references and package group references once heat is generating them
72 ////payloadGroupRefs.Add(String.Format(CultureInfo.InvariantCulture, "{0}.{1}", referenceName, pog));
73 packageGroupRefs.Add(String.Format(CultureInfo.InvariantCulture, "{0}.{1}", referenceName, pog));
74 }
75 }
76 }
77
78 XmlDocument doc = new XmlDocument();
79
80 XmlProcessingInstruction head = doc.CreateProcessingInstruction("xml", "version='1.0' encoding='UTF-8'");
81 doc.AppendChild(head);
82
83 XmlElement rootElement = doc.CreateElement("Wix");
84 rootElement.SetAttribute("xmlns", "http://wixtoolset.org/schemas/v4/wxs");
85 doc.AppendChild(rootElement);
86
87 XmlElement fragment = doc.CreateElement("Fragment");
88 rootElement.AppendChild(fragment);
89
90 XmlElement payloadGroup = doc.CreateElement("PayloadGroup");
91 payloadGroup.SetAttribute("Id", "Bundle.Generated.Payloads");
92 fragment.AppendChild(payloadGroup);
93
94 XmlElement packageGroup = doc.CreateElement("PackageGroup");
95 packageGroup.SetAttribute("Id", "Bundle.Generated.Packages");
96 fragment.AppendChild(packageGroup);
97
98 foreach (string payloadGroupRef in payloadGroupRefs)
99 {
100 XmlElement payloadGroupRefElement = doc.CreateElement("PayloadGroupRef");
101 payloadGroupRefElement.SetAttribute("Id", payloadGroupRef);
102 payloadGroup.AppendChild(payloadGroupRefElement);
103 }
104
105 foreach (string packageGroupRef in packageGroupRefs)
106 {
107 XmlElement packageGroupRefElement = doc.CreateElement("PackageGroupRef");
108 packageGroupRefElement.SetAttribute("Id", packageGroupRef);
109 packageGroup.AppendChild(packageGroupRefElement);
110 }
111
112 foreach (ITaskItem item in this.GeneratedFiles)
113 {
114 string fullPath = item.GetMetadata("FullPath");
115
116 payloadGroup.SetAttribute("Id", Path.GetFileNameWithoutExtension(fullPath) + ".Payloads");
117 packageGroup.SetAttribute("Id", Path.GetFileNameWithoutExtension(fullPath) + ".Packages");
118 try
119 {
120 doc.Save(fullPath);
121 }
122 catch (Exception e)
123 {
124 // e.Message will be something like: "Access to the path 'fullPath' is denied."
125 this.Log.LogMessage(MessageImportance.High, "Unable to save generated file to '{0}'. {1}", fullPath, e.Message);
126 }
127 }
128
129 return true;
130 }
131 }
132}
diff --git a/src/WixToolset.BuildTasks/RefreshGeneratedFile.cs b/src/WixToolset.BuildTasks/RefreshGeneratedFile.cs
new file mode 100644
index 00000000..fdfc4774
--- /dev/null
+++ b/src/WixToolset.BuildTasks/RefreshGeneratedFile.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.BuildTasks
4{
5 using System;
6 using System.Collections;
7 using System.Globalization;
8 using System.IO;
9 using System.Text.RegularExpressions;
10 using System.Xml;
11 using Microsoft.Build.Framework;
12 using Microsoft.Build.Utilities;
13
14 /// <summary>
15 /// This task refreshes the generated file that contains ComponentGroupRefs
16 /// to harvested output.
17 /// </summary>
18 public class RefreshGeneratedFile : Task
19 {
20 private static readonly Regex AddPrefix = new Regex(@"^[^a-zA-Z_]", RegexOptions.Compiled);
21 private static readonly Regex IllegalIdentifierCharacters = new Regex(@"[^A-Za-z0-9_\.]|\.{2,}", RegexOptions.Compiled); // non 'words' and assorted valid characters
22
23 private ITaskItem[] generatedFiles;
24 private ITaskItem[] projectReferencePaths;
25
26 /// <summary>
27 /// The list of files to generate.
28 /// </summary>
29 [Required]
30 public ITaskItem[] GeneratedFiles
31 {
32 get { return this.generatedFiles; }
33 set { this.generatedFiles = value; }
34 }
35
36 /// <summary>
37 /// All the project references in the project.
38 /// </summary>
39 [Required]
40 public ITaskItem[] ProjectReferencePaths
41 {
42 get { return this.projectReferencePaths; }
43 set { this.projectReferencePaths = value; }
44 }
45
46 /// <summary>
47 /// Gets a complete list of external cabs referenced by the given installer database file.
48 /// </summary>
49 /// <returns>True upon completion of the task execution.</returns>
50 public override bool Execute()
51 {
52 ArrayList componentGroupRefs = new ArrayList();
53 for (int i = 0; i < this.ProjectReferencePaths.Length; i++)
54 {
55 ITaskItem item = this.ProjectReferencePaths[i];
56
57 if (!String.IsNullOrEmpty(item.GetMetadata(Common.DoNotHarvest)))
58 {
59 continue;
60 }
61
62 string projectPath = CreateProjectReferenceDefineConstants.GetProjectPath(this.ProjectReferencePaths, i);
63 string projectName = Path.GetFileNameWithoutExtension(projectPath);
64 string referenceName = Common.GetIdentifierFromName(CreateProjectReferenceDefineConstants.GetReferenceName(item, projectName));
65
66 string[] pogs = item.GetMetadata("RefProjectOutputGroups").Split(';');
67 foreach (string pog in pogs)
68 {
69 if (!String.IsNullOrEmpty(pog))
70 {
71 componentGroupRefs.Add(String.Format(CultureInfo.InvariantCulture, "{0}.{1}", referenceName, pog));
72 }
73 }
74 }
75
76 XmlDocument doc = new XmlDocument();
77
78 XmlProcessingInstruction head = doc.CreateProcessingInstruction("xml", "version='1.0' encoding='UTF-8'");
79 doc.AppendChild(head);
80
81 XmlElement rootElement = doc.CreateElement("Wix");
82 rootElement.SetAttribute("xmlns", "http://wixtoolset.org/schemas/v4/wxs");
83 doc.AppendChild(rootElement);
84
85 XmlElement fragment = doc.CreateElement("Fragment");
86 rootElement.AppendChild(fragment);
87
88 XmlElement componentGroup = doc.CreateElement("ComponentGroup");
89 componentGroup.SetAttribute("Id", "Product.Generated");
90 fragment.AppendChild(componentGroup);
91
92 foreach (string componentGroupRef in componentGroupRefs)
93 {
94 XmlElement componentGroupRefElement = doc.CreateElement("ComponentGroupRef");
95 componentGroupRefElement.SetAttribute("Id", componentGroupRef);
96 componentGroup.AppendChild(componentGroupRefElement);
97 }
98
99 foreach (ITaskItem item in this.GeneratedFiles)
100 {
101 string fullPath = item.GetMetadata("FullPath");
102
103 componentGroup.SetAttribute("Id", Path.GetFileNameWithoutExtension(fullPath));
104 try
105 {
106 doc.Save(fullPath);
107 }
108 catch (Exception e)
109 {
110 // e.Message will be something like: "Access to the path 'fullPath' is denied."
111 this.Log.LogMessage(MessageImportance.High, "Unable to save generated file to '{0}'. {1}", fullPath, e.Message);
112 }
113 }
114
115 return true;
116 }
117 }
118}
diff --git a/src/WixToolset.BuildTasks/ReplaceString.cs b/src/WixToolset.BuildTasks/ReplaceString.cs
new file mode 100644
index 00000000..e5041923
--- /dev/null
+++ b/src/WixToolset.BuildTasks/ReplaceString.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.BuildTasks
4{
5 using System;
6 using Microsoft.Build.Framework;
7 using Microsoft.Build.Utilities;
8
9 /// <summary>
10 /// Replaces occurances of OldValues with NewValues in String.
11 /// </summary>
12 public class ReplaceString : Task
13 {
14 /// <summary>
15 /// Text to operate on.
16 /// </summary>
17 [Output]
18 [Required]
19 public string Text { get; set; }
20
21 /// <summary>
22 /// List of old values to replace.
23 /// </summary>
24 [Required]
25 public string OldValue { get; set; }
26
27 /// <summary>
28 /// List of new values to replace old values with. If not specified, occurances of OldValue will be removed.
29 /// </summary>
30 public string NewValue { get; set; }
31
32 /// <summary>
33 /// Does the string replacement.
34 /// </summary>
35 /// <returns></returns>
36 public override bool Execute()
37 {
38 if (String.IsNullOrEmpty(this.Text))
39 {
40 return true;
41 }
42
43 if (String.IsNullOrEmpty(this.OldValue))
44 {
45 Log.LogError("OldValue must be specified");
46 return false;
47 }
48
49 this.Text = this.Text.Replace(this.OldValue, this.NewValue);
50
51 return true;
52 }
53 }
54}
diff --git a/src/WixToolset.BuildTasks/ResolveWixReferences.cs b/src/WixToolset.BuildTasks/ResolveWixReferences.cs
new file mode 100644
index 00000000..9b8cfe6f
--- /dev/null
+++ b/src/WixToolset.BuildTasks/ResolveWixReferences.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.BuildTasks
4{
5 using System;
6 using System.Collections.Generic;
7 using Microsoft.Build.Utilities;
8 using Microsoft.Build.Framework;
9 using System.IO;
10
11 /// <summary>
12 /// This task searches for paths to references using the order specified in SearchPaths.
13 /// </summary>
14 public class ResolveWixReferences : Task
15 {
16 /// <summary>
17 /// Token value used in SearchPaths to indicate that the item's HintPath metadata should
18 /// be searched as a full file path to resolve the reference.
19 /// Must match wix.targets, case sensitive.
20 /// </summary>
21 private const string HintPathToken = "{HintPathFromItem}";
22
23 /// <summary>
24 /// Token value used in SearchPaths to indicate that the item's Identity should
25 /// be searched as a full file path to resolve the reference.
26 /// Must match wix.targets, case sensitive.
27 /// </summary>
28 private const string RawFileNameToken = "{RawFileName}";
29
30 /// <summary>
31 /// The list of references to resolve.
32 /// </summary>
33 [Required]
34 public ITaskItem[] WixReferences
35 {
36 get;
37 set;
38 }
39
40 /// <summary>
41 /// The directories or special locations that are searched to find the files
42 /// on disk that represent the references. The order in which the search paths are listed
43 /// is important. For each reference, the list of paths is searched from left to right.
44 /// When a file that represents the reference is found, that search stops and the search
45 /// for the next reference starts.
46 ///
47 /// This parameter accepts the following types of values:
48 /// A directory path.
49 /// {HintPathFromItem}: Specifies that the task will examine the HintPath metadata
50 /// of the base item.
51 /// TODO : {CandidateAssemblyFiles}: Specifies that the task will examine the files
52 /// passed in through the CandidateAssemblyFiles parameter.
53 /// TODO : {Registry:_AssemblyFoldersBase_, _RuntimeVersion_, _AssemblyFoldersSuffix_}:
54 /// TODO : {AssemblyFolders}: Specifies the task will use the Visual Studio.NET 2003
55 /// finding-assemblies-from-registry scheme.
56 /// TODO : {GAC}: Specifies the task will search in the GAC.
57 /// {RawFileName}: Specifies the task will consider the Include value of the item to be
58 /// an exact path and file name.
59 /// </summary>
60 public string[] SearchPaths
61 {
62 get;
63 set;
64 }
65
66 /// <summary>
67 /// The filename extension(s) to be checked when searching.
68 /// </summary>
69 public string[] SearchFilenameExtensions
70 {
71 get;
72 set;
73 }
74
75 /// <summary>
76 /// Output items that contain the same metadata as input references and have been resolved to full paths.
77 /// </summary>
78 [Output]
79 public ITaskItem[] ResolvedWixReferences
80 {
81 get;
82 private set;
83 }
84
85 /// <summary>
86 /// Resolves reference paths by searching for referenced items using the specified SearchPaths.
87 /// </summary>
88 /// <returns>True on success, or throws an exception on failure.</returns>
89 public override bool Execute()
90 {
91 List<ITaskItem> resolvedReferences = new List<ITaskItem>();
92
93 foreach (ITaskItem reference in this.WixReferences)
94 {
95 ITaskItem resolvedReference = ResolveWixReferences.ResolveReference(reference, this.SearchPaths, this.SearchFilenameExtensions, this.Log);
96
97 this.Log.LogMessage(MessageImportance.Low, "Resolved path {0}", resolvedReference.ItemSpec);
98 resolvedReferences.Add(resolvedReference);
99 }
100
101 this.ResolvedWixReferences = resolvedReferences.ToArray();
102 return true;
103 }
104
105 /// <summary>
106 /// Resolves a single reference item by searcheing for referenced items using the specified SearchPaths.
107 /// This method is made public so the resolution logic can be reused by other tasks.
108 /// </summary>
109 /// <param name="reference">The referenced item.</param>
110 /// <param name="searchPaths">The paths to search.</param>
111 /// <param name="searchFilenameExtensions">Filename extensions to check.</param>
112 /// <param name="log">Logging helper.</param>
113 /// <returns>The resolved reference item, or the original reference if it could not be resolved.</returns>
114 public static ITaskItem ResolveReference(ITaskItem reference, string[] searchPaths, string[] searchFilenameExtensions, TaskLoggingHelper log)
115 {
116 if (reference == null)
117 {
118 throw new ArgumentNullException("reference");
119 }
120
121 if (searchPaths == null)
122 {
123 // Nothing to search, so just return the original reference item.
124 return reference;
125 }
126
127 if (searchFilenameExtensions == null)
128 {
129 searchFilenameExtensions = new string[] { };
130 }
131
132 // Copy all the metadata from the source
133 TaskItem resolvedReference = new TaskItem(reference);
134 log.LogMessage(MessageImportance.Low, "WixReference: {0}", reference.ItemSpec);
135
136 // Now find the resolved path based on our order of precedence
137 foreach (string searchPath in searchPaths)
138 {
139 log.LogMessage(MessageImportance.Low, "Trying {0}", searchPath);
140 if (searchPath.Equals(HintPathToken, StringComparison.Ordinal))
141 {
142 string path = reference.GetMetadata("HintPath");
143 log.LogMessage(MessageImportance.Low, "Trying path {0}", path);
144 if (File.Exists(path))
145 {
146 resolvedReference.ItemSpec = path;
147 break;
148 }
149 }
150 else if (searchPath.Equals(RawFileNameToken, StringComparison.Ordinal))
151 {
152 log.LogMessage(MessageImportance.Low, "Trying path {0}", resolvedReference.ItemSpec);
153 if (File.Exists(resolvedReference.ItemSpec))
154 {
155 break;
156 }
157
158 if (ResolveWixReferences.ResolveFilenameExtensions(resolvedReference,
159 resolvedReference.ItemSpec, searchFilenameExtensions, log))
160 {
161 break;
162 }
163 }
164 else
165 {
166 string path = Path.Combine(searchPath, Path.GetFileName(reference.ItemSpec));
167 log.LogMessage(MessageImportance.Low, "Trying path {0}", path);
168 if (File.Exists(path))
169 {
170 resolvedReference.ItemSpec = path;
171 break;
172 }
173
174 if (ResolveWixReferences.ResolveFilenameExtensions(resolvedReference,
175 path, searchFilenameExtensions, log))
176 {
177 break;
178 }
179 }
180 }
181
182 // Normalize the item path
183 resolvedReference.ItemSpec = resolvedReference.GetMetadata("FullPath");
184
185 return resolvedReference;
186 }
187
188 /// <summary>
189 /// Helper method for checking filename extensions when resolving references.
190 /// </summary>
191 /// <param name="reference">The reference being resolved.</param>
192 /// <param name="basePath">Full filename path without extension.</param>
193 /// <param name="filenameExtensions">Filename extensions to check.</param>
194 /// <param name="log">Logging helper.</param>
195 /// <returns>True if the item was resolved, else false.</returns>
196 private static bool ResolveFilenameExtensions(ITaskItem reference, string basePath, string[] filenameExtensions, TaskLoggingHelper log)
197 {
198 foreach (string filenameExtension in filenameExtensions)
199 {
200 string path = basePath + filenameExtension;
201 log.LogMessage(MessageImportance.Low, "Trying path {0}", path);
202 if (File.Exists(path))
203 {
204 reference.ItemSpec = path;
205 return true;
206 }
207 }
208
209 return false;
210 }
211 }
212}
diff --git a/src/WixToolset.BuildTasks/TaskBase.cs b/src/WixToolset.BuildTasks/TaskBase.cs
new file mode 100644
index 00000000..3d58fc06
--- /dev/null
+++ b/src/WixToolset.BuildTasks/TaskBase.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.BuildTasks
4{
5 using Microsoft.Build.Utilities;
6
7 public abstract class TaskBase : Task
8 {
9 public string ToolPath { get; set; }
10
11 public string AdditionalOptions { get; set; }
12
13 public bool RunAsSeparateProcess { get; set; }
14
15 /// <summary>
16 /// Gets or sets whether all warnings should be suppressed.
17 /// </summary>
18 public bool SuppressAllWarnings { get; set; }
19
20 /// <summary>
21 /// Gets or sets a list of specific warnings to be suppressed.
22 /// </summary>
23 public string[] SuppressSpecificWarnings { get; set; }
24
25 /// <summary>
26 /// Gets or sets whether all warnings should be treated as errors.
27 /// </summary>
28 public bool TreatWarningsAsErrors { get; set; }
29
30 /// <summary>
31 /// Gets or sets a list of specific warnings to treat as errors.
32 /// </summary>
33 public string[] TreatSpecificWarningsAsErrors { get; set; }
34
35 /// <summary>
36 /// Gets or sets whether to display verbose output.
37 /// </summary>
38 public bool VerboseOutput { get; set; }
39
40 /// <summary>
41 /// Gets or sets whether to display the logo.
42 /// </summary>
43 public bool NoLogo { get; set; }
44
45 public override bool Execute()
46 {
47 try
48 {
49 this.ExecuteCore();
50 }
51 catch (BuildException e)
52 {
53 this.Log.LogErrorFromException(e);
54 }
55 catch (Data.WixException e)
56 {
57 this.Log.LogErrorFromException(e);
58 }
59
60 return !this.Log.HasLoggedErrors;
61 }
62
63 protected abstract void ExecuteCore();
64 }
65}
diff --git a/src/WixToolset.BuildTasks/Torch.cs b/src/WixToolset.BuildTasks/Torch.cs
new file mode 100644
index 00000000..e18ed315
--- /dev/null
+++ b/src/WixToolset.BuildTasks/Torch.cs
@@ -0,0 +1,159 @@
1// Copyright (c) .NET Foundation and 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.BuildTasks
4{
5 using System;
6 using System.Diagnostics;
7 using System.Globalization;
8 using System.IO;
9 using System.Text;
10
11 using Microsoft.Build.Framework;
12 using Microsoft.Build.Utilities;
13
14 /// <summary>
15 /// An MSBuild task to run the WiX transform generator.
16 /// </summary>
17 public sealed class Torch : WixToolTask
18 {
19 private const string TorchToolName = "Torch.exe";
20
21 private bool adminImage;
22 private ITaskItem baselineFile;
23 private string binaryExtractionPath;
24 private bool inputIsXml;
25 private bool leaveTemporaryFiles;
26 private bool outputAsXml;
27 private ITaskItem outputFile;
28 private bool preserveUnmodifiedContent;
29 private string suppressTransformErrorFlags;
30 private string transformValidationFlags;
31 private string transformValidationType;
32 private ITaskItem updateFile;
33
34 public bool AdminImage
35 {
36 get { return this.adminImage; }
37 set { this.adminImage = value; }
38 }
39
40
41 [Required]
42 public ITaskItem BaselineFile
43 {
44 get { return this.baselineFile; }
45 set { this.baselineFile = value; }
46 }
47
48 public string BinaryExtractionPath
49 {
50 get { return this.binaryExtractionPath; }
51 set { this.binaryExtractionPath = value; }
52 }
53
54 public bool LeaveTemporaryFiles
55 {
56 get { return this.leaveTemporaryFiles; }
57 set { this.leaveTemporaryFiles = value; }
58 }
59
60 public bool InputIsXml
61 {
62 get { return this.inputIsXml; }
63 set { this.inputIsXml = value; }
64 }
65
66 public bool OutputAsXml
67 {
68 get { return this.outputAsXml; }
69 set { this.outputAsXml = value; }
70 }
71
72 public bool PreserveUnmodifiedContent
73 {
74 get { return this.preserveUnmodifiedContent; }
75 set { this.preserveUnmodifiedContent = value; }
76 }
77
78 [Required]
79 [Output]
80 public ITaskItem OutputFile
81 {
82 get { return this.outputFile; }
83 set { this.outputFile = value; }
84 }
85
86 public string SuppressTransformErrorFlags
87 {
88 get { return this.suppressTransformErrorFlags; }
89 set { this.suppressTransformErrorFlags = value; }
90 }
91
92 public string TransformValidationType
93 {
94 get { return this.transformValidationType; }
95 set { this.transformValidationType = value; }
96 }
97
98 public string TransformValidationFlags
99 {
100 get { return this.transformValidationFlags; }
101 set { this.transformValidationFlags = value; }
102 }
103
104 [Required]
105 public ITaskItem UpdateFile
106 {
107 get { return this.updateFile; }
108 set { this.updateFile = value; }
109 }
110
111 /// <summary>
112 /// Get the name of the executable.
113 /// </summary>
114 /// <remarks>The ToolName is used with the ToolPath to get the location of torch.exe.</remarks>
115 /// <value>The name of the executable.</value>
116 protected override string ToolName
117 {
118 get { return TorchToolName; }
119 }
120
121 /// <summary>
122 /// Get the path to the executable.
123 /// </summary>
124 /// <remarks>GetFullPathToTool is only called when the ToolPath property is not set (see the ToolName remarks above).</remarks>
125 /// <returns>The full path to the executable or simply torch.exe if it's expected to be in the system path.</returns>
126 protected override string GenerateFullPathToTool()
127 {
128 // If there's not a ToolPath specified, it has to be in the system path.
129 if (String.IsNullOrEmpty(this.ToolPath))
130 {
131 return TorchToolName;
132 }
133
134 return Path.Combine(Path.GetFullPath(this.ToolPath), TorchToolName);
135 }
136
137 /// <summary>
138 /// Builds a command line from options in this task.
139 /// </summary>
140 protected override void BuildCommandLine(WixCommandLineBuilder commandLineBuilder)
141 {
142 base.BuildCommandLine(commandLineBuilder);
143
144 commandLineBuilder.AppendIfTrue("-notidy", this.LeaveTemporaryFiles);
145 commandLineBuilder.AppendIfTrue("-xo", this.OutputAsXml);
146 commandLineBuilder.AppendIfTrue("-xi", this.InputIsXml);
147 commandLineBuilder.AppendIfTrue("-p", this.PreserveUnmodifiedContent);
148 commandLineBuilder.AppendTextIfNotNull(this.AdditionalOptions);
149 commandLineBuilder.AppendFileNameIfNotNull(this.BaselineFile);
150 commandLineBuilder.AppendFileNameIfNotNull(this.UpdateFile);
151 commandLineBuilder.AppendSwitchIfNotNull("-out ", this.OutputFile);
152 commandLineBuilder.AppendIfTrue("-a", this.adminImage);
153 commandLineBuilder.AppendSwitchIfNotNull("-x ", this.BinaryExtractionPath);
154 commandLineBuilder.AppendSwitchIfNotNull("-serr ", this.SuppressTransformErrorFlags);
155 commandLineBuilder.AppendSwitchIfNotNull("-t ", this.TransformValidationType);
156 commandLineBuilder.AppendSwitchIfNotNull("-val ", this.TransformValidationFlags);
157 }
158 }
159}
diff --git a/src/WixToolset.BuildTasks/WixAssignCulture.cs b/src/WixToolset.BuildTasks/WixAssignCulture.cs
new file mode 100644
index 00000000..7a03dc47
--- /dev/null
+++ b/src/WixToolset.BuildTasks/WixAssignCulture.cs
@@ -0,0 +1,231 @@
1// Copyright (c) .NET Foundation and 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.BuildTasks
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.IO;
9 using System.Xml;
10 using Microsoft.Build.Framework;
11 using Microsoft.Build.Utilities;
12
13 /// <summary>
14 /// This task assigns Culture metadata to files based on the value of the Culture attribute on the
15 /// WixLocalization element inside the file.
16 /// </summary>
17 public class WixAssignCulture : Task
18 {
19 private const string CultureAttributeName = "Culture";
20 private const string OutputFolderMetadataName = "OutputFolder";
21 private const string InvariantCultureIdentifier = "neutral";
22 private const string NullCultureIdentifier = "null";
23
24 /// <summary>
25 /// The list of cultures to build. Cultures are specified in the following form:
26 /// primary culture,first fallback culture, second fallback culture;...
27 /// Culture groups are seperated by semi-colons
28 /// Culture precedence within a culture group is evaluated from left to right where fallback cultures are
29 /// separated with commas.
30 /// The first (primary) culture in a culture group will be used as the output sub-folder.
31 /// </summary>
32 public string Cultures { get; set; }
33
34 /// <summary>
35 /// The list of files to apply culture information to.
36 /// </summary>
37 [Required]
38 public ITaskItem[] Files
39 {
40 get;
41 set;
42 }
43
44 /// <summary>
45 /// The files that had culture information applied
46 /// </summary>
47 [Output]
48 public ITaskItem[] CultureGroups
49 {
50 get;
51 private set;
52 }
53
54 /// <summary>
55 /// Applies culture information to the files specified by the Files property.
56 /// This task intentionally does not validate that strings are valid Cultures so that we can support
57 /// psuedo-loc.
58 /// </summary>
59 /// <returns>True upon completion of the task execution.</returns>
60 public override bool Execute()
61 {
62 // First, process the culture group list the user specified in the cultures property
63 List<CultureGroup> cultureGroups = new List<CultureGroup>();
64
65 if (!String.IsNullOrEmpty(this.Cultures))
66 {
67 // Get rid of extra quotes
68 this.Cultures = this.Cultures.Trim('\"');
69
70 foreach (string cultureGroupString in this.Cultures.Split(';'))
71 {
72 if (0 == cultureGroupString.Length)
73 {
74 // MSBuild v2.0.50727 cannnot handle "" items
75 // for the invariant culture we require the neutral keyword
76 continue;
77 }
78 CultureGroup cultureGroup = new CultureGroup(cultureGroupString);
79 cultureGroups.Add(cultureGroup);
80 }
81 }
82 else
83 {
84 // Only process the EmbeddedResource items if cultures was unspecified
85 foreach (ITaskItem file in this.Files)
86 {
87 // Ignore non-wxls
88 if (!String.Equals(file.GetMetadata("Extension"), ".wxl", StringComparison.OrdinalIgnoreCase))
89 {
90 Log.LogError("Unable to retrieve the culture for EmbeddedResource {0}. The file type is not supported.", file.ItemSpec);
91 return false;
92 }
93 XmlDocument wxlFile = new XmlDocument();
94
95 try
96 {
97 wxlFile.Load(file.ItemSpec);
98 }
99 catch (FileNotFoundException)
100 {
101 Log.LogError("Unable to retrieve the culture for EmbeddedResource {0}. The file was not found.", file.ItemSpec);
102 return false;
103 }
104 catch (Exception e)
105 {
106 Log.LogError("Unable to retrieve the culture for EmbeddedResource {0}: {1}", file.ItemSpec, e.Message);
107 return false;
108 }
109
110 // Take the culture value and try using it to create a culture.
111 XmlAttribute cultureAttr = wxlFile.DocumentElement.Attributes[WixAssignCulture.CultureAttributeName];
112 string wxlCulture = null == cultureAttr ? String.Empty : cultureAttr.Value;
113 if (0 == wxlCulture.Length)
114 {
115 // We use a keyword for the invariant culture because MSBuild v2.0.50727 cannnot handle "" items
116 wxlCulture = InvariantCultureIdentifier;
117 }
118
119 // We found the culture for the WXL, we now need to determine if it maps to a culture group specified
120 // in the Cultures property or if we need to create a new one.
121 Log.LogMessage(MessageImportance.Low, "Culture \"{0}\" from EmbeddedResource {1}.", wxlCulture, file.ItemSpec);
122
123 bool cultureGroupExists = false;
124 foreach (CultureGroup cultureGroup in cultureGroups)
125 {
126 foreach (string culture in cultureGroup.Cultures)
127 {
128 if (String.Equals(wxlCulture, culture, StringComparison.OrdinalIgnoreCase))
129 {
130 cultureGroupExists = true;
131 break;
132 }
133 }
134 }
135
136 // The WXL didn't match a culture group we already have so create a new one.
137 if (!cultureGroupExists)
138 {
139 cultureGroups.Add(new CultureGroup(wxlCulture));
140 }
141 }
142 }
143
144 // If we didn't create any culture groups the culture was unspecificed and no WXLs were included
145 // Build an unlocalized target in the output folder
146 if (cultureGroups.Count == 0)
147 {
148 cultureGroups.Add(new CultureGroup());
149 }
150
151 List<TaskItem> cultureGroupItems = new List<TaskItem>();
152
153 if (1 == cultureGroups.Count && 0 == this.Files.Length)
154 {
155 // Maintain old behavior, if only one culturegroup is specified and no WXL, output to the default folder
156 TaskItem cultureGroupItem = new TaskItem(cultureGroups[0].ToString());
157 cultureGroupItem.SetMetadata(OutputFolderMetadataName, CultureGroup.DefaultFolder);
158 cultureGroupItems.Add(cultureGroupItem);
159 }
160 else
161 {
162 foreach (CultureGroup cultureGroup in cultureGroups)
163 {
164 TaskItem cultureGroupItem = new TaskItem(cultureGroup.ToString());
165 cultureGroupItem.SetMetadata(OutputFolderMetadataName, cultureGroup.OutputFolder);
166 cultureGroupItems.Add(cultureGroupItem);
167 Log.LogMessage("Culture: {0}", cultureGroup.ToString());
168 }
169 }
170
171 this.CultureGroups = cultureGroupItems.ToArray();
172 return true;
173 }
174
175 private class CultureGroup
176 {
177 private List<string> cultures = new List<string>();
178
179 /// <summary>
180 /// TargetPath already has a '\', do not double it!
181 /// </summary>
182 public const string DefaultFolder = "";
183
184 /// <summary>
185 /// Initialize a null culture group
186 /// </summary>
187 public CultureGroup()
188 {
189 }
190
191 public CultureGroup(string cultureGroupString)
192 {
193 Debug.Assert(!String.IsNullOrEmpty(cultureGroupString));
194 foreach (string cultureString in cultureGroupString.Split(','))
195 {
196 this.cultures.Add(cultureString);
197 }
198 }
199
200 public List<string> Cultures { get { return cultures; } }
201
202 public string OutputFolder
203 {
204 get
205 {
206 string result = DefaultFolder;
207 if (this.Cultures.Count > 0 &&
208 !this.Cultures[0].Equals(InvariantCultureIdentifier, StringComparison.OrdinalIgnoreCase))
209 {
210 result = this.Cultures[0] + "\\";
211 }
212
213 return result;
214 }
215 }
216
217 public override string ToString()
218 {
219 if (this.Cultures.Count > 0)
220 {
221 return String.Join(",", this.Cultures.ToArray());
222 }
223
224 // We use a keyword for a null culture because MSBuild cannnot handle "" items
225 // Null is different from neutral. For neutral we still want to do WXL
226 // filtering in Light.
227 return NullCultureIdentifier;
228 }
229 }
230 }
231}
diff --git a/src/WixToolset.BuildTasks/WixCommandLineBuilder.cs b/src/WixToolset.BuildTasks/WixCommandLineBuilder.cs
new file mode 100644
index 00000000..9a6a005d
--- /dev/null
+++ b/src/WixToolset.BuildTasks/WixCommandLineBuilder.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 WixToolset.BuildTasks
4{
5 using System;
6 using System.Diagnostics;
7 using System.Globalization;
8 using System.IO;
9 using System.Text;
10
11 using Microsoft.Build.Framework;
12 using Microsoft.Build.Utilities;
13
14 /// <summary>
15 /// Helper class for appending the command line arguments.
16 /// </summary>
17 public class WixCommandLineBuilder : CommandLineBuilder
18 {
19 internal const int Unspecified = -1;
20
21 /// <summary>
22 /// Append a switch to the command line if the value has been specified.
23 /// </summary>
24 /// <param name="switchName">Switch to append.</param>
25 /// <param name="value">Value specified by the user.</param>
26 public void AppendIfSpecified(string switchName, int value)
27 {
28 if (value != Unspecified)
29 {
30 this.AppendSwitchIfNotNull(switchName, value.ToString(CultureInfo.InvariantCulture));
31 }
32 }
33
34 /// <summary>
35 /// Append a switch to the command line if the condition is true.
36 /// </summary>
37 /// <param name="switchName">Switch to append.</param>
38 /// <param name="condition">Condition specified by the user.</param>
39 public void AppendIfTrue(string switchName, bool condition)
40 {
41 if (condition)
42 {
43 this.AppendSwitch(switchName);
44 }
45 }
46
47 /// <summary>
48 /// Append a switch to the command line if any values in the array have been specified.
49 /// </summary>
50 /// <param name="switchName">Switch to append.</param>
51 /// <param name="values">Values specified by the user.</param>
52 public void AppendArrayIfNotNull(string switchName, ITaskItem[] values)
53 {
54 if (values != null)
55 {
56 foreach (ITaskItem value in values)
57 {
58 this.AppendSwitchIfNotNull(switchName, value);
59 }
60 }
61 }
62
63 /// <summary>
64 /// Append a switch to the command line if any values in the array have been specified.
65 /// </summary>
66 /// <param name="switchName">Switch to append.</param>
67 /// <param name="values">Values specified by the user.</param>
68 public void AppendArrayIfNotNull(string switchName, string[] values)
69 {
70 if (values != null)
71 {
72 foreach (string value in values)
73 {
74 this.AppendSwitchIfNotNull(switchName, value);
75 }
76 }
77 }
78
79 /// <summary>
80 /// Build the extensions argument. Each extension is searched in the current folder, user defined search
81 /// directories (ReferencePath), HintPath, and under Wix Extension Directory in that order.
82 /// The order of precednce is based off of that described in Microsoft.Common.Targets's SearchPaths
83 /// property for the ResolveAssemblyReferences task.
84 /// </summary>
85 /// <param name="extensions">The list of extensions to include.</param>
86 /// <param name="wixExtensionDirectory">Evaluated default folder for Wix Extensions</param>
87 /// <param name="referencePaths">User defined reference directories to search in</param>
88 public void AppendExtensions(ITaskItem[] extensions, string wixExtensionDirectory, string [] referencePaths)
89 {
90 if (extensions == null)
91 {
92 return;
93 }
94
95 string resolvedPath;
96
97 foreach (ITaskItem extension in extensions)
98 {
99 string className = extension.GetMetadata("Class");
100
101 string fileName = Path.GetFileName(extension.ItemSpec);
102
103 if (Path.GetExtension(fileName).Length == 0)
104 {
105 fileName += ".dll";
106 }
107
108 // First try reference paths
109 resolvedPath = FileSearchHelperMethods.SearchFilePaths(referencePaths, fileName);
110
111 if (String.IsNullOrEmpty(resolvedPath))
112 {
113 // Now try HintPath
114 resolvedPath = extension.GetMetadata("HintPath");
115
116 if (!File.Exists(resolvedPath))
117 {
118 // Now try the item itself
119 resolvedPath = extension.ItemSpec;
120
121 if (Path.GetExtension(resolvedPath).Length == 0)
122 {
123 resolvedPath += ".dll";
124 }
125
126 if (!File.Exists(resolvedPath))
127 {
128 if (!String.IsNullOrEmpty(wixExtensionDirectory))
129 {
130 // Now try the extension directory
131 resolvedPath = Path.Combine(wixExtensionDirectory, Path.GetFileName(resolvedPath));
132 }
133
134 if (!File.Exists(resolvedPath))
135 {
136 // Extesnion wasn't found, just set it to the extension name passed in
137 resolvedPath = extension.ItemSpec;
138 }
139 }
140 }
141 }
142
143 if (String.IsNullOrEmpty(className))
144 {
145 this.AppendSwitchIfNotNull("-ext ", resolvedPath);
146 }
147 else
148 {
149 this.AppendSwitchIfNotNull("-ext ", className + ", " + resolvedPath);
150 }
151 }
152 }
153
154 /// <summary>
155 /// Append arbitrary text to the command-line if specified.
156 /// </summary>
157 /// <param name="textToAppend">Text to append.</param>
158 public void AppendTextIfNotNull(string textToAppend)
159 {
160 if (!String.IsNullOrEmpty(textToAppend))
161 {
162 this.AppendSpaceIfNotEmpty();
163 this.AppendTextUnquoted(textToAppend);
164 }
165 }
166
167 /// <summary>
168 /// Append arbitrary text to the command-line if specified.
169 /// </summary>
170 /// <param name="textToAppend">Text to append.</param>
171 public void AppendTextIfNotWhitespace(string textToAppend)
172 {
173 if (!String.IsNullOrWhiteSpace(textToAppend))
174 {
175 this.AppendSpaceIfNotEmpty();
176 this.AppendTextUnquoted(textToAppend);
177 }
178 }
179 }
180}
diff --git a/src/WixToolset.BuildTasks/WixToolTask.cs b/src/WixToolset.BuildTasks/WixToolTask.cs
new file mode 100644
index 00000000..2e5e8705
--- /dev/null
+++ b/src/WixToolset.BuildTasks/WixToolTask.cs
@@ -0,0 +1,406 @@
1// Copyright (c) .NET Foundation and 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.BuildTasks
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.Diagnostics.CodeAnalysis;
9 using System.Globalization;
10 using System.IO;
11 using System.Reflection;
12 using System.Text;
13 using System.Threading;
14
15 using Microsoft.Build.Framework;
16 using Microsoft.Build.Utilities;
17
18 /// <summary>
19 /// Base class for WiX tool tasks; executes tools in-process
20 /// so that repeated invocations are much faster.
21 /// </summary>
22 public abstract class WixToolTask : ToolTask, IDisposable
23 {
24 private string additionalOptions;
25 private bool disposed;
26 private bool noLogo;
27 private bool runAsSeparateProcess;
28 private bool suppressAllWarnings;
29 private string[] suppressSpecificWarnings;
30 private string[] treatSpecificWarningsAsErrors;
31 private bool treatWarningsAsErrors;
32 private bool verboseOutput;
33 private Queue<string> messageQueue;
34 private ManualResetEvent messagesAvailable;
35 private ManualResetEvent toolExited;
36 private int exitCode;
37
38 /// <summary>
39 /// Gets or sets additional options that are appended the the tool command-line.
40 /// </summary>
41 /// <remarks>
42 /// This allows the task to support extended options in the tool which are not
43 /// explicitly implemented as properties on the task.
44 /// </remarks>
45 public string AdditionalOptions
46 {
47 get { return this.additionalOptions; }
48 set { this.additionalOptions = value; }
49 }
50
51 /// <summary>
52 /// Gets or sets a flag indicating whether the task should be run as separate
53 /// process instead of in-proc with MSBuild which is the default.
54 /// </summary>
55 public bool RunAsSeparateProcess
56 {
57 get { return this.runAsSeparateProcess; }
58 set { this.runAsSeparateProcess = value; }
59 }
60
61#region Common Options
62 /// <summary>
63 /// Gets or sets whether all warnings should be suppressed.
64 /// </summary>
65 public bool SuppressAllWarnings
66 {
67 get { return this.suppressAllWarnings; }
68 set { this.suppressAllWarnings = value; }
69 }
70
71 /// <summary>
72 /// Gets or sets a list of specific warnings to be suppressed.
73 /// </summary>
74 public string[] SuppressSpecificWarnings
75 {
76 get { return this.suppressSpecificWarnings; }
77 set { this.suppressSpecificWarnings = value; }
78 }
79
80 /// <summary>
81 /// Gets or sets whether all warnings should be treated as errors.
82 /// </summary>
83 public bool TreatWarningsAsErrors
84 {
85 get { return this.treatWarningsAsErrors; }
86 set { this.treatWarningsAsErrors = value; }
87 }
88
89 /// <summary>
90 /// Gets or sets a list of specific warnings to treat as errors.
91 /// </summary>
92 public string[] TreatSpecificWarningsAsErrors
93 {
94 get { return this.treatSpecificWarningsAsErrors; }
95 set { this.treatSpecificWarningsAsErrors = value; }
96 }
97
98 /// <summary>
99 /// Gets or sets whether to display verbose output.
100 /// </summary>
101 public bool VerboseOutput
102 {
103 get { return this.verboseOutput; }
104 set { this.verboseOutput = value; }
105 }
106
107 /// <summary>
108 /// Gets or sets whether to display the logo.
109 /// </summary>
110 public bool NoLogo
111 {
112 get { return this.noLogo; }
113 set { this.noLogo = value; }
114 }
115#endregion
116
117 /// <summary>
118 /// Cleans up the ManualResetEvent members
119 /// </summary>
120 public void Dispose()
121 {
122 if (!this.disposed)
123 {
124 this.Dispose(true);
125 GC.SuppressFinalize(this);
126 disposed = true;
127 }
128 }
129
130 /// <summary>
131 /// Cleans up the ManualResetEvent members
132 /// </summary>
133 protected virtual void Dispose(bool disposing)
134 {
135 if (disposing)
136 {
137 messagesAvailable.Close();
138 toolExited.Close();
139 }
140 }
141
142 /// <summary>
143 /// Generate the command line arguments to write to the response file from the properties.
144 /// </summary>
145 /// <returns>Command line string.</returns>
146 protected override string GenerateResponseFileCommands()
147 {
148 WixCommandLineBuilder commandLineBuilder = new WixCommandLineBuilder();
149 this.BuildCommandLine(commandLineBuilder);
150 return commandLineBuilder.ToString();
151 }
152
153 /// <summary>
154 /// Builds a command line from options in this and derivative tasks.
155 /// </summary>
156 /// <remarks>
157 /// Derivative classes should call BuildCommandLine() on the base class to ensure that common command line options are added to the command.
158 /// </remarks>
159 protected virtual void BuildCommandLine(WixCommandLineBuilder commandLineBuilder)
160 {
161 commandLineBuilder.AppendIfTrue("-nologo", this.NoLogo);
162 commandLineBuilder.AppendArrayIfNotNull("-sw", this.SuppressSpecificWarnings);
163 commandLineBuilder.AppendIfTrue("-sw", this.SuppressAllWarnings);
164 commandLineBuilder.AppendIfTrue("-v", this.VerboseOutput);
165 commandLineBuilder.AppendArrayIfNotNull("-wx", this.TreatSpecificWarningsAsErrors);
166 commandLineBuilder.AppendIfTrue("-wx", this.TreatWarningsAsErrors);
167 }
168
169 /// <summary>
170 /// Executes a tool in-process by loading the tool assembly and invoking its entrypoint.
171 /// </summary>
172 /// <param name="pathToTool">Path to the tool to be executed; must be a managed executable.</param>
173 /// <param name="responseFileCommands">Commands to be written to a response file.</param>
174 /// <param name="commandLineCommands">Commands to be passed directly on the command-line.</param>
175 /// <returns>The tool exit code.</returns>
176 protected override int ExecuteTool(string pathToTool, string responseFileCommands, string commandLineCommands)
177 {
178 if (this.RunAsSeparateProcess)
179 {
180 return base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands);
181 }
182
183 this.messageQueue = new Queue<string>();
184 this.messagesAvailable = new ManualResetEvent(false);
185 this.toolExited = new ManualResetEvent(false);
186
187 Util.RunningInMsBuild = true;
188
189 WixToolTaskLogger logger = new WixToolTaskLogger(this.messageQueue, this.messagesAvailable);
190 TextWriter saveConsoleOut = Console.Out;
191 TextWriter saveConsoleError = Console.Error;
192 Console.SetOut(logger);
193 Console.SetError(logger);
194
195 string responseFile = null;
196 try
197 {
198 string responseFileSwitch;
199 responseFile = this.GetTemporaryResponseFile(responseFileCommands, out responseFileSwitch);
200 if (!String.IsNullOrEmpty(responseFileSwitch))
201 {
202 commandLineCommands = commandLineCommands + " " + responseFileSwitch;
203 }
204
205 string[] arguments = CommandLineResponseFile.ParseArgumentsToArray(commandLineCommands);
206
207 Thread toolThread = new Thread(new ParameterizedThreadStart(this.ExecuteToolThread));
208 toolThread.Start(new object[] { pathToTool, arguments });
209
210 this.HandleToolMessages();
211
212 if (this.exitCode == 0 && this.Log.HasLoggedErrors)
213 {
214 this.exitCode = -1;
215 }
216
217 return this.exitCode;
218 }
219 finally
220 {
221 if (responseFile != null)
222 {
223 File.Delete(responseFile);
224 }
225
226 Console.SetOut(saveConsoleOut);
227 Console.SetError(saveConsoleError);
228 }
229 }
230
231 /// <summary>
232 /// Called by a new thread to execute the tool in that thread.
233 /// </summary>
234 /// <param name="parameters">Tool path and arguments array.</param>
235 private void ExecuteToolThread(object parameters)
236 {
237 try
238 {
239 object[] pathAndArguments = (object[])parameters;
240 Assembly toolAssembly = Assembly.LoadFrom((string)pathAndArguments[0]);
241 this.exitCode = (int)toolAssembly.EntryPoint.Invoke(null, new object[] { pathAndArguments[1] });
242 }
243 catch (FileNotFoundException fnfe)
244 {
245 Log.LogError("Unable to load tool from path {0}. Consider setting the ToolPath parameter to $(WixToolPath).", fnfe.FileName);
246 this.exitCode = -1;
247 }
248 catch (Exception ex)
249 {
250 this.exitCode = -1;
251 this.LogEventsFromTextOutput(ex.Message, MessageImportance.High);
252 foreach (string stackTraceLine in ex.StackTrace.Split('\n'))
253 {
254 this.LogEventsFromTextOutput(stackTraceLine.TrimEnd(), MessageImportance.High);
255 }
256
257 throw;
258 }
259 finally
260 {
261 this.toolExited.Set();
262 }
263 }
264
265 /// <summary>
266 /// Waits for messages from the tool thread and sends them to the MSBuild logger on the original thread.
267 /// Returns when the tool thread exits.
268 /// </summary>
269 private void HandleToolMessages()
270 {
271 WaitHandle[] waitHandles = new WaitHandle[] { this.messagesAvailable, this.toolExited };
272 while (WaitHandle.WaitAny(waitHandles) == 0)
273 {
274 lock (this.messageQueue)
275 {
276 while (this.messageQueue.Count > 0)
277 {
278 this.LogEventsFromTextOutput(messageQueue.Dequeue(), MessageImportance.Normal);
279 }
280
281 this.messagesAvailable.Reset();
282 }
283 }
284 }
285
286 /// <summary>
287 /// Creates a temporary response file for tool execution.
288 /// </summary>
289 /// <returns>Path to the response file.</returns>
290 /// <remarks>
291 /// The temporary file should be deleted after the tool execution is finished.
292 /// </remarks>
293 private string GetTemporaryResponseFile(string responseFileCommands, out string responseFileSwitch)
294 {
295 string responseFile = null;
296 responseFileSwitch = null;
297
298 if (!String.IsNullOrEmpty(responseFileCommands))
299 {
300 responseFile = Path.GetTempFileName();
301 using (StreamWriter writer = new StreamWriter(responseFile, false, this.ResponseFileEncoding))
302 {
303 writer.Write(responseFileCommands);
304 }
305 responseFileSwitch = this.GetResponseFileSwitch(responseFile);
306 }
307 return responseFile;
308 }
309
310 /// <summary>
311 /// Cycles thru each task to find correct path of the file in question.
312 /// Looks at item spec, hintpath and then in user defined Reference Paths
313 /// </summary>
314 /// <param name="tasks">Input task array</param>
315 /// <param name="referencePaths">SemiColon delimited directories to search</param>
316 /// <returns>List of task item file paths</returns>
317 [SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists")]
318 protected static List<string> AdjustFilePaths(ITaskItem[] tasks, string[] referencePaths)
319 {
320 List<string> sourceFilePaths = new List<string>();
321
322 if (tasks == null)
323 {
324 return sourceFilePaths;
325 }
326
327 foreach (ITaskItem task in tasks)
328 {
329 string filePath = task.ItemSpec;
330 if (!File.Exists(filePath))
331 {
332 filePath = task.GetMetadata("HintPath");
333 if (!File.Exists(filePath))
334 {
335 string searchPath = FileSearchHelperMethods.SearchFilePaths(referencePaths, filePath);
336 if (!String.IsNullOrEmpty(searchPath))
337 {
338 filePath = searchPath;
339 }
340 }
341 }
342 sourceFilePaths.Add(filePath);
343 }
344
345 return sourceFilePaths;
346 }
347
348 /// <summary>
349 /// Used as a replacement for Console.Out to capture output from a tool
350 /// and redirect it to the MSBuild logging system.
351 /// </summary>
352 private class WixToolTaskLogger : TextWriter
353 {
354 private StringBuilder buffer;
355 private Queue<string> messageQueue;
356 private ManualResetEvent messagesAvailable;
357
358 /// <summary>
359 /// Creates a new logger that sends tool output to the tool task's log handler.
360 /// </summary>
361 public WixToolTaskLogger(Queue<string> messageQueue, ManualResetEvent messagesAvailable) : base(CultureInfo.CurrentCulture)
362 {
363 this.messageQueue = messageQueue;
364 this.messagesAvailable = messagesAvailable;
365 this.buffer = new StringBuilder();
366 }
367
368 /// <summary>
369 /// Gets the encoding of the logger.
370 /// </summary>
371 public override Encoding Encoding
372 {
373 get { return Encoding.Unicode; }
374 }
375
376 /// <summary>
377 /// Redirects output to a buffer; watches for newlines and sends each line to the
378 /// MSBuild logging system.
379 /// </summary>
380 /// <param name="value">Character being written.</param>
381 /// <remarks>All other Write() variants eventually call into this one.</remarks>
382 public override void Write(char value)
383 {
384 lock (this.messageQueue)
385 {
386 if (value == '\n')
387 {
388 if (this.buffer.Length > 0 && this.buffer[this.buffer.Length - 1] == '\r')
389 {
390 this.buffer.Length = this.buffer.Length - 1;
391 }
392
393 this.messageQueue.Enqueue(this.buffer.ToString());
394 this.messagesAvailable.Set();
395
396 this.buffer.Length = 0;
397 }
398 else
399 {
400 this.buffer.Append(value);
401 }
402 }
403 }
404 }
405 }
406}
diff --git a/src/WixToolset.BuildTasks/WixToolset.BuildTasks.csproj b/src/WixToolset.BuildTasks/WixToolset.BuildTasks.csproj
new file mode 100644
index 00000000..34a1a9f5
--- /dev/null
+++ b/src/WixToolset.BuildTasks/WixToolset.BuildTasks.csproj
@@ -0,0 +1,38 @@
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>net462</TargetFramework>
7 <Description></Description>
8 <Title>WiX Toolset MSBuild Tasks</Title>
9 </PropertyGroup>
10
11 <PropertyGroup>
12 <!-- <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> -->
13 <NoWarn>NU1701</NoWarn>
14 </PropertyGroup>
15
16 <ItemGroup>
17 <Content Include="redirect.wix.targets" CopyToOutputDirectory="PreserveNewest" />
18 <Content Include="redirect.wix.ca.targets" CopyToOutputDirectory="PreserveNewest" />
19 <Content Include="wix.targets" CopyToOutputDirectory="PreserveNewest" />
20 <Content Include="wix.ca.targets" CopyToOutputDirectory="PreserveNewest" />
21 <Content Include="wix.harvest.targets" CopyToOutputDirectory="PreserveNewest" />
22 <Content Include="wix.signing.targets" CopyToOutputDirectory="PreserveNewest" />
23 </ItemGroup>
24
25 <ItemGroup>
26 <ProjectReference Include="..\WixToolset.Core\WixToolset.Core.csproj" />
27 </ItemGroup>
28
29 <ItemGroup>
30 <PackageReference Include="WixToolset.Dtf.WindowsInstaller" Version="4.0.*" />
31 </ItemGroup>
32
33 <ItemGroup>
34 <PackageReference Include="Nerdbank.GitVersioning" Version="2.0.41" PrivateAssets="all" />
35 <PackageReference Include="Microsoft.Build.Tasks.Core" Version="14.3" PrivateAssets="all" Condition="'$(TargetFramework)'=='net462' " />
36 <PackageReference Include="Microsoft.Build.Tasks.Core" Version="15.3.409" PrivateAssets="all" Condition="'$(TargetFramework)'=='netstandard2.0' " />
37 </ItemGroup>
38</Project>
diff --git a/src/WixToolset.BuildTasks/heatdirectory.cs b/src/WixToolset.BuildTasks/heatdirectory.cs
new file mode 100644
index 00000000..1d5f104a
--- /dev/null
+++ b/src/WixToolset.BuildTasks/heatdirectory.cs
@@ -0,0 +1,103 @@
1// Copyright (c) .NET Foundation and 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.BuildTasks
4{
5 using Microsoft.Build.Framework;
6
7 public sealed class HeatDirectory : HeatTask
8 {
9 private string directory;
10 private bool keepEmptyDirectories;
11 private bool suppressCom;
12 private bool suppressRootDirectory;
13 private bool suppressRegistry;
14 private string template;
15 private string componentGroupName;
16 private string directoryRefId;
17 private string preprocessorVariable;
18
19 public string ComponentGroupName
20 {
21 get { return this.componentGroupName; }
22 set { this.componentGroupName = value; }
23 }
24
25 [Required]
26 public string Directory
27 {
28 get { return this.directory; }
29 set { this.directory = value; }
30 }
31
32 public string DirectoryRefId
33 {
34 get { return this.directoryRefId; }
35 set { this.directoryRefId = value; }
36 }
37
38 public bool KeepEmptyDirectories
39 {
40 get { return this.keepEmptyDirectories; }
41 set { this.keepEmptyDirectories = value; }
42 }
43
44 public string PreprocessorVariable
45 {
46 get { return this.preprocessorVariable; }
47 set { this.preprocessorVariable = value; }
48 }
49
50 public bool SuppressCom
51 {
52 get { return this.suppressCom; }
53 set { this.suppressCom = value; }
54 }
55
56 public bool SuppressRootDirectory
57 {
58 get { return this.suppressRootDirectory; }
59 set { this.suppressRootDirectory = value; }
60 }
61
62 public bool SuppressRegistry
63 {
64 get { return this.suppressRegistry; }
65 set { this.suppressRegistry = value; }
66 }
67
68 public string Template
69 {
70 get { return this.template; }
71 set { this.template = value; }
72 }
73
74 protected override string OperationName
75 {
76 get { return "dir"; }
77 }
78
79 /// <summary>
80 /// Generate the command line arguments to write to the response file from the properties.
81 /// </summary>
82 /// <returns>Command line string.</returns>
83 protected override string GenerateResponseFileCommands()
84 {
85 WixCommandLineBuilder commandLineBuilder = new WixCommandLineBuilder();
86
87 commandLineBuilder.AppendSwitch(this.OperationName);
88 commandLineBuilder.AppendFileNameIfNotNull(this.Directory);
89
90 commandLineBuilder.AppendSwitchIfNotNull("-cg ", this.ComponentGroupName);
91 commandLineBuilder.AppendSwitchIfNotNull("-dr ", this.DirectoryRefId);
92 commandLineBuilder.AppendIfTrue("-ke", this.KeepEmptyDirectories);
93 commandLineBuilder.AppendIfTrue("-scom", this.SuppressCom);
94 commandLineBuilder.AppendIfTrue("-sreg", this.SuppressRegistry);
95 commandLineBuilder.AppendIfTrue("-srd", this.SuppressRootDirectory);
96 commandLineBuilder.AppendSwitchIfNotNull("-template ", this.Template);
97 commandLineBuilder.AppendSwitchIfNotNull("-var ", this.PreprocessorVariable);
98
99 base.BuildCommandLine(commandLineBuilder);
100 return commandLineBuilder.ToString();
101 }
102 }
103}
diff --git a/src/WixToolset.BuildTasks/heatfile.cs b/src/WixToolset.BuildTasks/heatfile.cs
new file mode 100644
index 00000000..69e11b88
--- /dev/null
+++ b/src/WixToolset.BuildTasks/heatfile.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.BuildTasks
4{
5 using Microsoft.Build.Framework;
6
7 public sealed class HeatFile : HeatTask
8 {
9 private string file;
10 private bool suppressCom;
11 private bool suppressRegistry;
12 private bool suppressRootDirectory;
13 private string template;
14 private string componentGroupName;
15 private string directoryRefId;
16 private string preprocessorVariable;
17
18 public string ComponentGroupName
19 {
20 get { return this.componentGroupName; }
21 set { this.componentGroupName = value; }
22 }
23
24 public string DirectoryRefId
25 {
26 get { return this.directoryRefId; }
27 set { this.directoryRefId = value; }
28 }
29
30 [Required]
31 public string File
32 {
33 get { return this.file; }
34 set { this.file = value; }
35 }
36
37 public string PreprocessorVariable
38 {
39 get { return this.preprocessorVariable; }
40 set { this.preprocessorVariable = value; }
41 }
42
43 public bool SuppressCom
44 {
45 get { return this.suppressCom; }
46 set { this.suppressCom = value; }
47 }
48
49 public bool SuppressRegistry
50 {
51 get { return this.suppressRegistry; }
52 set { this.suppressRegistry = value; }
53 }
54
55 public bool SuppressRootDirectory
56 {
57 get { return this.suppressRootDirectory; }
58 set { this.suppressRootDirectory = value; }
59 }
60
61 public string Template
62 {
63 get { return this.template; }
64 set { this.template = value; }
65 }
66
67 protected override string OperationName
68 {
69 get { return "file"; }
70 }
71
72 /// <summary>
73 /// Generate the command line arguments to write to the response file from the properties.
74 /// </summary>
75 /// <returns>Command line string.</returns>
76 protected override string GenerateResponseFileCommands()
77 {
78 WixCommandLineBuilder commandLineBuilder = new WixCommandLineBuilder();
79
80 commandLineBuilder.AppendSwitch(this.OperationName);
81 commandLineBuilder.AppendFileNameIfNotNull(this.File);
82
83 commandLineBuilder.AppendSwitchIfNotNull("-cg ", this.ComponentGroupName);
84 commandLineBuilder.AppendSwitchIfNotNull("-dr ", this.DirectoryRefId);
85 commandLineBuilder.AppendIfTrue("-scom", this.SuppressCom);
86 commandLineBuilder.AppendIfTrue("-srd", this.SuppressRootDirectory);
87 commandLineBuilder.AppendIfTrue("-sreg", this.SuppressRegistry);
88 commandLineBuilder.AppendSwitchIfNotNull("-template ", this.Template);
89 commandLineBuilder.AppendSwitchIfNotNull("-var ", this.PreprocessorVariable);
90
91 base.BuildCommandLine(commandLineBuilder);
92 return commandLineBuilder.ToString();
93 }
94 }
95}
diff --git a/src/WixToolset.BuildTasks/heatproject.cs b/src/WixToolset.BuildTasks/heatproject.cs
new file mode 100644
index 00000000..8620ffa3
--- /dev/null
+++ b/src/WixToolset.BuildTasks/heatproject.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.BuildTasks
4{
5 using Microsoft.Build.Framework;
6
7 public sealed class HeatProject : HeatTask
8 {
9 private string configuration;
10 private string directoryIds;
11 private string generateType;
12 private bool generateWixVariables;
13 private string platform;
14 private string project;
15 private string projectName;
16 private string[] projectOutputGroups;
17
18 public string Configuration
19 {
20 get { return this.configuration; }
21 set { this.configuration = value; }
22 }
23
24 public string DirectoryIds
25 {
26 get { return this.directoryIds; }
27 set { this.directoryIds = value; }
28 }
29
30 public bool GenerateWixVariables
31 {
32 get { return this.generateWixVariables; }
33 set { this.generateWixVariables = value; }
34 }
35
36 public string GenerateType
37 {
38 get { return this.generateType; }
39 set { this.generateType = value; }
40 }
41
42 public string Platform
43 {
44 get { return this.platform; }
45 set { this.platform = value; }
46 }
47
48 [Required]
49 public string Project
50 {
51 get { return this.project; }
52 set { this.project = value; }
53 }
54
55 public string ProjectName
56 {
57 get { return this.projectName; }
58 set { this.projectName = value; }
59 }
60
61 public string[] ProjectOutputGroups
62 {
63 get
64 {
65 return this.projectOutputGroups;
66 }
67 set
68 {
69 this.projectOutputGroups = value;
70
71 // If it's just one string and it contains semicolons, let's
72 // split it into separate items.
73 if (this.projectOutputGroups.Length == 1)
74 {
75 this.projectOutputGroups = this.projectOutputGroups[0].Split(new char[] { ';' });
76 }
77 }
78 }
79
80 protected override string OperationName
81 {
82 get { return "project"; }
83 }
84
85 /// <summary>
86 /// Generate the command line arguments to write to the response file from the properties.
87 /// </summary>
88 /// <returns>Command line string.</returns>
89 protected override string GenerateResponseFileCommands()
90 {
91 WixCommandLineBuilder commandLineBuilder = new WixCommandLineBuilder();
92
93 commandLineBuilder.AppendSwitch(this.OperationName);
94 commandLineBuilder.AppendFileNameIfNotNull(this.Project);
95
96 commandLineBuilder.AppendSwitchIfNotNull("-configuration ", this.Configuration);
97 commandLineBuilder.AppendSwitchIfNotNull("-directoryid ", this.DirectoryIds);
98 commandLineBuilder.AppendSwitchIfNotNull("-generate ", this.GenerateType);
99 commandLineBuilder.AppendSwitchIfNotNull("-platform ", this.Platform);
100 commandLineBuilder.AppendArrayIfNotNull("-pog ", this.ProjectOutputGroups);
101 commandLineBuilder.AppendSwitchIfNotNull("-projectname ", this.ProjectName);
102 commandLineBuilder.AppendIfTrue("-wixvar", this.GenerateWixVariables);
103
104 base.BuildCommandLine(commandLineBuilder);
105 return commandLineBuilder.ToString();
106 }
107 }
108}
diff --git a/src/WixToolset.BuildTasks/heattask.cs b/src/WixToolset.BuildTasks/heattask.cs
new file mode 100644
index 00000000..bf0a2ad3
--- /dev/null
+++ b/src/WixToolset.BuildTasks/heattask.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.BuildTasks
4{
5 using System;
6 using System.Diagnostics;
7 using System.Globalization;
8 using System.IO;
9 using System.Text;
10
11 using Microsoft.Build.Framework;
12 using Microsoft.Build.Utilities;
13
14 /// <summary>
15 /// A base MSBuild task to run the WiX harvester.
16 /// Specific harvester tasks should extend this class.
17 /// </summary>
18 public abstract class HeatTask : WixToolTask
19 {
20 private const string HeatToolName = "Heat.exe";
21
22 private bool autogenerageGuids;
23 private bool generateGuidsNow;
24 private ITaskItem outputFile;
25 private bool suppressFragments;
26 private bool suppressUniqueIds;
27 private string[] transforms;
28
29 public bool AutogenerateGuids
30 {
31 get { return this.autogenerageGuids; }
32 set { this.autogenerageGuids = value; }
33 }
34
35 public bool GenerateGuidsNow
36 {
37 get { return this.generateGuidsNow; }
38 set { this.generateGuidsNow = value; }
39 }
40
41 [Required]
42 [Output]
43 public ITaskItem OutputFile
44 {
45 get { return this.outputFile; }
46 set { this.outputFile = value; }
47 }
48
49 public bool SuppressFragments
50 {
51 get { return this.suppressFragments; }
52 set { this.suppressFragments = value; }
53 }
54
55 public bool SuppressUniqueIds
56 {
57 get { return this.suppressUniqueIds; }
58 set { this.suppressUniqueIds = value; }
59 }
60
61 public string[] Transforms
62 {
63 get { return this.transforms; }
64 set { this.transforms = value; }
65 }
66
67 /// <summary>
68 /// Get the name of the executable.
69 /// </summary>
70 /// <remarks>The ToolName is used with the ToolPath to get the location of heat.exe.</remarks>
71 /// <value>The name of the executable.</value>
72 protected override string ToolName
73 {
74 get { return HeatToolName; }
75 }
76
77 /// <summary>
78 /// Gets the name of the heat operation performed by the task.
79 /// </summary>
80 /// <remarks>This is the first parameter passed on the heat.exe command-line.</remarks>
81 /// <value>The name of the heat operation performed by the task.</value>
82 protected abstract string OperationName
83 {
84 get;
85 }
86
87 /// <summary>
88 /// Get the path to the executable.
89 /// </summary>
90 /// <remarks>GetFullPathToTool is only called when the ToolPath property is not set (see the ToolName remarks above).</remarks>
91 /// <returns>The full path to the executable or simply heat.exe if it's expected to be in the system path.</returns>
92 protected override string GenerateFullPathToTool()
93 {
94 // If there's not a ToolPath specified, it has to be in the system path.
95 if (String.IsNullOrEmpty(this.ToolPath))
96 {
97 return HeatToolName;
98 }
99
100 return Path.Combine(Path.GetFullPath(this.ToolPath), HeatToolName);
101 }
102
103 /// <summary>
104 /// Builds a command line from options in this task.
105 /// </summary>
106 protected override void BuildCommandLine(WixCommandLineBuilder commandLineBuilder)
107 {
108 base.BuildCommandLine(commandLineBuilder);
109
110 commandLineBuilder.AppendIfTrue("-ag", this.AutogenerateGuids);
111 commandLineBuilder.AppendIfTrue("-gg", this.GenerateGuidsNow);
112 commandLineBuilder.AppendIfTrue("-nologo", this.NoLogo);
113 commandLineBuilder.AppendIfTrue("-sfrag", this.SuppressFragments);
114 commandLineBuilder.AppendIfTrue("-suid", this.SuppressUniqueIds);
115 commandLineBuilder.AppendArrayIfNotNull("-sw", this.SuppressSpecificWarnings);
116 commandLineBuilder.AppendArrayIfNotNull("-t ", this.Transforms);
117 commandLineBuilder.AppendTextIfNotNull(this.AdditionalOptions);
118 commandLineBuilder.AppendSwitchIfNotNull("-out ", this.OutputFile);
119 }
120 }
121}
diff --git a/src/WixToolset.BuildTasks/redirect.wix.ca.targets b/src/WixToolset.BuildTasks/redirect.wix.ca.targets
new file mode 100644
index 00000000..74e6ec3c
--- /dev/null
+++ b/src/WixToolset.BuildTasks/redirect.wix.ca.targets
@@ -0,0 +1,11 @@
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
5<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
6 <PropertyGroup>
7 <WixInstallFolder>$([MSBuild]::GetRegistryValueFromView('HKEY_LOCAL_MACHINE\SOFTWARE\WiX Toolset\v4', 'InstallFolder', null, RegistryView.Registry32))</WixInstallFolder>
8 </PropertyGroup>
9
10 <Import Project="$(WixInstallFolder)sdk\wix.ca.targets" />
11</Project>
diff --git a/src/WixToolset.BuildTasks/redirect.wix.targets b/src/WixToolset.BuildTasks/redirect.wix.targets
new file mode 100644
index 00000000..b40c4c36
--- /dev/null
+++ b/src/WixToolset.BuildTasks/redirect.wix.targets
@@ -0,0 +1,11 @@
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
5<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
6 <PropertyGroup>
7 <WixInstallFolder>$([MSBuild]::GetRegistryValueFromView('HKEY_LOCAL_MACHINE\SOFTWARE\WiX Toolset\v4', 'InstallFolder', null, RegistryView.Registry32))</WixInstallFolder>
8 </PropertyGroup>
9
10 <Import Project="$(WixInstallFolder)bin\wix.targets" />
11</Project>
diff --git a/src/WixToolset.BuildTasks/wix.ca.targets b/src/WixToolset.BuildTasks/wix.ca.targets
new file mode 100644
index 00000000..4578c2d8
--- /dev/null
+++ b/src/WixToolset.BuildTasks/wix.ca.targets
@@ -0,0 +1,123 @@
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
5<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
6
7 <Import Project="$(CustomBeforeWixCATargets)" Condition=" '$(CustomBeforeWixCATargets)' != '' and Exists('$(CustomBeforeWixCATargets)')" />
8
9 <PropertyGroup>
10 <WixCATargetsImported>true</WixCATargetsImported>
11
12 <TargetCAFileName Condition=" '$(TargetCAFileName)' == '' ">$(TargetName).CA$(TargetExt)</TargetCAFileName>
13
14 <WixSdkPath Condition=" '$(WixSdkPath)' == '' ">$(MSBuildThisFileDirectory)</WixSdkPath>
15 <WixSdkX86Path Condition=" '$(WixSdkX86Path)' == '' ">$(WixSdkPath)x86\</WixSdkX86Path>
16 <WixSdkX64Path Condition=" '$(WixSdkX64Path)' == '' ">$(WixSdkPath)x64\</WixSdkX64Path>
17
18 <MakeSfxCA Condition=" '$(MakeSfxCA)' == '' ">$(WixSdkPath)MakeSfxCA.exe</MakeSfxCA>
19 <SfxCADll Condition=" '$(SfxCADll)' == '' and '$(Platform)' == 'x64' ">$(WixSdkX64Path)SfxCA.dll</SfxCADll>
20 <SfxCADll Condition=" '$(SfxCADll)' == '' ">$(WixSdkX86Path)SfxCA.dll</SfxCADll>
21 </PropertyGroup>
22
23 <!--
24 ==================================================================================================
25 PackCustomAction
26
27 Creates an MSI managed custom action package that includes the custom action assembly,
28 local assembly dependencies, and project content files.
29
30 [IN]
31 @(IntermediateAssembly) - Managed custom action assembly.
32 @(Content) - Project items of type Content will be included in the package.
33 $(CustomActionContents) - Optional space-delimited list of additional files to include.
34
35 [OUT]
36 $(IntermediateOutputPath)$(TargetCAFileName) - Managed custom action package with unmanaged stub.
37 ==================================================================================================
38 -->
39 <Target Name="PackCustomAction"
40 Inputs="@(IntermediateAssembly);@(Content);$(CustomActionContents)"
41 Outputs="$(IntermediateOutputPath)$(TargetCAFileName)">
42
43 <!-- Find all referenced items marked CopyLocal, but exclude non-binary files. -->
44 <ItemGroup>
45 <CustomActionReferenceContents Include="@(ReferenceCopyLocalPaths)"
46 Condition=" '%(Extension)' == '.dll' or '%(Extension)' == '.exe' " />
47 <CustomActionReferenceContents Include="@(ReferenceComWrappersToCopyLocal)"
48 Condition=" '%(Extension)' == '.dll' or '%(Extension)' == '.exe' " />
49
50 <!-- include PDBs for Debug only -->
51 <CustomActionReferenceContents Include="@(IntermediateAssembly->'%(RootDir)%(Directory)%(Filename).pdb')"
52 Condition=" Exists('%(RootDir)%(Directory)%(Filename).pdb') and '$(Configuration)' == 'Debug' " />
53 <CustomActionReferenceContents Include="@(ReferenceCopyLocalPaths)"
54 Condition=" '%(Extension)' == '.pdb' and '$(Configuration)' == 'Debug' " />
55 <CustomActionReferenceContents Include="@(ReferenceComWrappersToCopyLocal)"
56 Condition=" '%(Extension)' == '.pdb' and '$(Configuration)' == 'Debug' " />
57 </ItemGroup>
58
59 <!--
60 Items to include in the CA package:
61 - Reference assemblies marked CopyLocal
62 - Project items of type Content
63 - Additional items in the CustomActionContents property
64 -->
65 <PropertyGroup>
66 <CustomActionContents>@(CustomActionReferenceContents);@(Content->'%(FullPath)');$(CustomActionContents)</CustomActionContents>
67 </PropertyGroup>
68
69 <ItemGroup>
70 <IntermediateCAAssembly Include="@(IntermediateAssembly->'%(FullPath)')" />
71 <IntermediateCAPackage Include="@(IntermediateAssembly->'%(RootDir)%(Directory)$(TargetCAFileName)')" />
72 </ItemGroup>
73
74 <!-- Run the MakeSfxCA.exe CA packaging tool. -->
75 <Exec Command='"$(MakeSfxCA)" "@(IntermediateCAPackage)" "$(SfxCADll)" "@(IntermediateCAAssembly)" "$(CustomActionContents)"'
76 WorkingDirectory="$(ProjectDir)" />
77
78 <!-- Add modules to be copied to output dir. -->
79 <ItemGroup>
80 <AddModules Include="@(IntermediateCAPackage)" />
81 </ItemGroup>
82 </Target>
83
84 <!--
85 ==================================================================================================
86 CleanCustomAction
87
88 Cleans the .CA.dll binary created by the PackCustomAction target.
89
90 ==================================================================================================
91 -->
92 <Target Name="CleanCustomAction">
93 <Delete Files="$(IntermediateOutputPath)$(TargetCAFileName)"
94 TreatErrorsAsWarnings="true" />
95 </Target>
96
97 <!--
98 ==================================================================================================
99 AfterCompile (redefinition)
100
101 Calls the PackCustomAction target after compiling.
102 Overrides the empty AfterCompile target from Microsoft.Common.targets.
103
104 ==================================================================================================
105 -->
106 <Target Name="AfterCompile"
107 DependsOnTargets="PackCustomAction" />
108
109 <!--
110 ==================================================================================================
111 BeforeClean (redefinition)
112
113 Calls the CleanCustomAction target before cleaning.
114 Overrides the empty AfterCompile target from Microsoft.Common.targets.
115
116 ==================================================================================================
117 -->
118 <Target Name="BeforeClean"
119 DependsOnTargets="CleanCustomAction" />
120
121 <Import Project="$(CustomAfterWixCATargets)" Condition=" '$(CustomAfterWixCATargets)' != '' and Exists('$(CustomAfterWixCATargets)')" />
122
123</Project>
diff --git a/src/WixToolset.BuildTasks/wix.harvest.targets b/src/WixToolset.BuildTasks/wix.harvest.targets
new file mode 100644
index 00000000..e94dfcea
--- /dev/null
+++ b/src/WixToolset.BuildTasks/wix.harvest.targets
@@ -0,0 +1,511 @@
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
5<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
6
7 <!-- These properties can be overridden to support non-default installations. -->
8 <PropertyGroup>
9 <WixTargetsPath Condition=" '$(WixTargetsPath)' == '' ">$(MSBuildThisFileFullPath)</WixTargetsPath>
10 <WixTasksPath Condition=" '$(WixTasksPath)' == '' ">$(WixTargetsPath)WixTasks.dll</WixTasksPath>
11 </PropertyGroup>
12
13 <!-- These tasks are extensions for harvesting WiX source code from other sources. -->
14 <UsingTask TaskName="HeatFile" AssemblyFile="$(WixTasksPath)" />
15 <UsingTask TaskName="HeatDirectory" AssemblyFile="$(WixTasksPath)" />
16 <UsingTask TaskName="HeatProject" AssemblyFile="$(WixTasksPath)" />
17
18 <UsingTask TaskName="RefreshGeneratedFile" AssemblyFile="$(WixTasksPath)"/>
19 <UsingTask TaskName="RefreshBundleGeneratedFile" AssemblyFile="$(WixTasksPath)"/>
20
21 <!-- Default Harvester properties-->
22 <PropertyGroup>
23 <HarvestNoLogo Condition=" '$(HarvestNoLogo)' == '' ">$(NoLogo)</HarvestNoLogo>
24 <HarvestSuppressAllWarnings Condition=" '$(HarvestSuppressAllWarnings)' == '' ">$(SuppressAllWarnings)</HarvestSuppressAllWarnings>
25 <HarvestSuppressSpecificWarnings Condition=" '$(HarvestSuppressSpecificWarnings)' == '' ">$(SuppressSpecificWarnings)</HarvestSuppressSpecificWarnings>
26 <HarvestTreatWarningsAsErrors Condition=" '$(HarvestTreatWarningsAsErrors)' == '' ">$(TreatWarningsAsErrors)</HarvestTreatWarningsAsErrors>
27 <HarvestTreatSpecificWarningsAsErrors Condition=" '$(HarvestTreatSpecificWarningsAsErrors)' == '' ">$(TreatSpecificWarningsAsErrors)</HarvestTreatSpecificWarningsAsErrors>
28 <HarvestVerboseOutput Condition=" '$(HarvestVerboseOutput)' == '' ">$(VerboseOutput)</HarvestVerboseOutput>
29 <HarvestAutogenerateGuids Condition=" '$(HarvestAutogenerateGuids)' == '' ">true</HarvestAutogenerateGuids>
30 <HarvestGenerateGuidsNow Condition=" '$(HarvestGenerateGuidsNow)' == '' ">false</HarvestGenerateGuidsNow>
31 <HarvestSuppressFragments Condition=" '$(HarvestSuppressFragments)' == '' ">true</HarvestSuppressFragments>
32 <HarvestSuppressUniqueIds Condition=" '$(HarvestSuppressUniqueIds)' == '' ">false</HarvestSuppressUniqueIds>
33 </PropertyGroup>
34
35 <!-- Default HarvestProjects properties -->
36 <PropertyGroup>
37 <!-- Project harvesting is defaulted to off until it works more consistently. -->
38 <EnableProjectHarvesting Condition=" '$(EnableProjectHarvesting)'=='' ">false</EnableProjectHarvesting>
39
40 <HarvestProjectsNoLogo Condition=" '$(HarvestProjectsNoLogo)' == '' ">$(HarvestNoLogo)</HarvestProjectsNoLogo>
41 <HarvestProjectsSuppressAllWarnings Condition=" '$(HarvestProjectsSuppressAllWarnings)' == '' ">$(HarvestSuppressAllWarnings)</HarvestProjectsSuppressAllWarnings>
42 <HarvestProjectsSuppressSpecificWarnings Condition=" '$(HarvestProjectsSuppressSpecificWarnings)' == '' ">$(HarvestSuppressSpecificWarnings)</HarvestProjectsSuppressSpecificWarnings>
43 <HarvestProjectsTreatWarningsAsErrors Condition=" '$(HarvestProjectsTreatWarningsAsErrors)' == '' ">$(HarvestTreatWarningsAsErrors)</HarvestProjectsTreatWarningsAsErrors>
44 <HarvestProjectsTreatSpecificWarningsAsErrors Condition=" '$(HarvestProjectsTreatSpecificWarningsAsErrors)' == '' ">$(HarvestTreatSpecificWarningsAsErrors)</HarvestProjectsTreatSpecificWarningsAsErrors>
45 <HarvestProjectsVerboseOutput Condition=" '$(HarvestProjectsVerboseOutput)' == '' ">$(HarvestVerboseOutput)</HarvestProjectsVerboseOutput>
46 <HarvestProjectsAutogenerateGuids Condition=" '$(HarvestProjectsAutogenerateGuids)' == '' ">$(HarvestAutogenerateGuids)</HarvestProjectsAutogenerateGuids>
47 <HarvestProjectsGenerateGuidsNow Condition=" '$(HarvestProjectsGenerateGuidsNow)' == '' ">$(HarvestGenerateGuidsNow)</HarvestProjectsGenerateGuidsNow>
48 <HarvestProjectsSuppressFragments Condition=" '$(HarvestProjectsSuppressFragments)' == '' ">$(HarvestSuppressFragments)</HarvestProjectsSuppressFragments>
49 <HarvestProjectsSuppressUniqueIds Condition=" '$(HarvestProjectsSuppressUniqueIds)' == '' ">$(HarvestSuppressUniqueIds)</HarvestProjectsSuppressUniqueIds>
50 <HarvestProjectsTransforms Condition=" '$(HarvestProjectsTransforms)' == '' ">$(HarvestTransforms)</HarvestProjectsTransforms>
51 <HarvestProjectsGeneratedFile Condition=" '$(HarvestProjectsGeneratedFile)' == '' and '$(OutputType)' != 'Bundle' ">$(IntermediateOutputPath)Product.Generated.wxs</HarvestProjectsGeneratedFile>
52 <HarvestProjectsGeneratedFile Condition=" '$(HarvestProjectsGeneratedFile)' == '' and '$(OutputType)' == 'Bundle' ">$(IntermediateOutputPath)Bundle.Generated.wxs</HarvestProjectsGeneratedFile>
53 </PropertyGroup>
54
55 <!-- Default HarvestDirectory properties -->
56 <PropertyGroup>
57 <HarvestDirectoryNoLogo Condition=" '$(HarvestDirectoryNoLogo)' == '' ">$(HarvestNoLogo)</HarvestDirectoryNoLogo>
58 <HarvestDirectorySuppressAllWarnings Condition=" '$(HarvestDirectorySuppressAllWarnings)' == '' ">$(HarvestSuppressAllWarnings)</HarvestDirectorySuppressAllWarnings>
59 <HarvestDirectorySuppressSpecificWarnings Condition=" '$(HarvestDirectorySuppressSpecificWarnings)' == '' ">$(HarvestSuppressSpecificWarnings)</HarvestDirectorySuppressSpecificWarnings>
60 <HarvestDirectoryTreatWarningsAsErrors Condition=" '$(HarvestDirectoryTreatWarningsAsErrors)' == '' ">$(HarvestTreatWarningsAsErrors)</HarvestDirectoryTreatWarningsAsErrors>
61 <HarvestDirectoryTreatSpecificWarningsAsErrors Condition=" '$(HarvestDirectoryTreatSpecificWarningsAsErrors)' == '' ">$(HarvestTreatSpecificWarningsAsErrors)</HarvestDirectoryTreatSpecificWarningsAsErrors>
62 <HarvestDirectoryVerboseOutput Condition=" '$(HarvestDirectoryVerboseOutput)' == '' ">$(HarvestVerboseOutput)</HarvestDirectoryVerboseOutput>
63 <HarvestDirectoryAutogenerateGuids Condition=" '$(HarvestDirectoryAutogenerateGuids)' == '' ">$(HarvestAutogenerateGuids)</HarvestDirectoryAutogenerateGuids>
64 <HarvestDirectoryGenerateGuidsNow Condition=" '$(HarvestDirectoryGenerateGuidsNow)' == '' ">$(HarvestGenerateGuidsNow)</HarvestDirectoryGenerateGuidsNow>
65 <HarvestDirectorySuppressFragments Condition=" '$(HarvestDirectorySuppressFragments)' == '' ">$(HarvestSuppressFragments)</HarvestDirectorySuppressFragments>
66 <HarvestDirectorySuppressUniqueIds Condition=" '$(HarvestDirectorySuppressUniqueIds)' == '' ">$(HarvestSuppressUniqueIds)</HarvestDirectorySuppressUniqueIds>
67 <HarvestDirectoryTransforms Condition=" '$(HarvestDirectoryTransforms)' == '' ">$(HarvestTransforms)</HarvestDirectoryTransforms>
68 </PropertyGroup>
69
70 <!-- Default HarvestFile properties -->
71 <PropertyGroup>
72 <HarvestFileNoLogo Condition=" '$(HarvestFileNoLogo)' == '' ">$(HarvestNoLogo)</HarvestFileNoLogo>
73 <HarvestFileSuppressAllWarnings Condition=" '$(HarvestFileSuppressAllWarnings)' == '' ">$(HarvestSuppressAllWarnings)</HarvestFileSuppressAllWarnings>
74 <HarvestFileSuppressSpecificWarnings Condition=" '$(HarvestFileSuppressSpecificWarnings)' == '' ">$(HarvestSuppressSpecificWarnings)</HarvestFileSuppressSpecificWarnings>
75 <HarvestFileTreatWarningsAsErrors Condition=" '$(HarvestFileTreatWarningsAsErrors)' == '' ">$(HarvestTreatWarningsAsErrors)</HarvestFileTreatWarningsAsErrors>
76 <HarvestFileTreatSpecificWarningsAsErrors Condition=" '$(HarvestFileTreatSpecificWarningsAsErrors)' == '' ">$(HarvestTreatSpecificWarningsAsErrors)</HarvestFileTreatSpecificWarningsAsErrors>
77 <HarvestFileVerboseOutput Condition=" '$(HarvestFileVerboseOutput)' == '' ">$(HarvestVerboseOutput)</HarvestFileVerboseOutput>
78 <HarvestFileAutogenerateGuids Condition=" '$(HarvestFileAutogenerateGuids)' == '' ">$(HarvestAutogenerateGuids)</HarvestFileAutogenerateGuids>
79 <HarvestFileGenerateGuidsNow Condition=" '$(HarvestFileGenerateGuidsNow)' == '' ">$(HarvestGenerateGuidsNow)</HarvestFileGenerateGuidsNow>
80 <HarvestFileSuppressFragments Condition=" '$(HarvestFileSuppressFragments)' == '' ">$(HarvestSuppressFragments)</HarvestFileSuppressFragments>
81 <HarvestFileSuppressUniqueIds Condition=" '$(HarvestFileSuppressUniqueIds)' == '' ">$(HarvestSuppressUniqueIds)</HarvestFileSuppressUniqueIds>
82 <HarvestFileTransforms Condition=" '$(HarvestFileTransforms)' == '' ">$(HarvestTransforms)</HarvestFileTransforms>
83 </PropertyGroup>
84
85 <!--
86 ==================================================================================================
87 Harvest
88 ==================================================================================================
89 -->
90 <PropertyGroup>
91 <HarvestDependsOn>
92 ConvertReferences;
93 ConvertBundleReferences;
94 HarvestProjects;
95 HarvestDirectory;
96 HarvestFile;
97 GenerateCode;
98 </HarvestDependsOn>
99 </PropertyGroup>
100 <Target
101 Name="Harvest"
102 DependsOnTargets="$(HarvestDependsOn)" />
103
104 <!--
105 ==================================================================================================
106 GenerateCode
107 ==================================================================================================
108 -->
109 <PropertyGroup>
110 <GenerateCodeDependsOn>
111 RefreshGeneratedFile;
112 RefreshBundleGeneratedFile
113 </GenerateCodeDependsOn>
114 </PropertyGroup>
115 <Target
116 Name="GenerateCode"
117 DependsOnTargets="$(GenerateCodeDependsOn)" />
118
119 <!--
120 ================================================================================================
121 ConvertReferences
122
123 Converts project references to HeatProject items to auto generate authoring.
124 ================================================================================================
125 -->
126 <Target
127 Name="ConvertReferences"
128 Condition=" $(EnableProjectHarvesting) and ('$(OutputType)' == 'Package' or '$(OutputType)' == 'PatchCreation' or '$(OutputType)' == 'Module') ">
129
130 <ItemGroup>
131 <_HeatProjectReference Include="@(_MSBuildProjectReferenceExistent)" Condition=" '%(_MSBuildProjectReferenceExistent.DoNotHarvest)' == '' ">
132 <DirectoryIds>%(_MSBuildProjectReferenceExistent.RefTargetDir)</DirectoryIds>
133 <ProjectOutputGroups>Binaries;Symbols;Sources;Content;Satellites;Documents</ProjectOutputGroups>
134 <ProjectName>%(_MSBuildProjectReferenceExistent.Name)</ProjectName>
135 <HeatOutput>$(IntermediateOutputPath)_%(_MSBuildProjectReferenceExistent.Filename).wxs</HeatOutput>
136 </_HeatProjectReference>
137 <HeatProject Include="@(_HeatProjectReference)" />
138 </ItemGroup>
139
140 <Error
141 Text="The following files are deprecated and should be removed from your project(s): @(Compile->'%(Identity)', ', ')"
142 Condition=" '%(Compile.GenerateComponentGroups)' != '' " />
143
144 <ItemGroup>
145 <!-- Unconditionally generate Compile items so they are always linked in. -->
146 <Compile Include="$(HarvestProjectsGeneratedFile)" />
147 <_GeneratedFiles Include="$(HarvestProjectsGeneratedFile)" />
148 </ItemGroup>
149
150 </Target>
151
152 <!--
153 ================================================================================================
154 ConvertBundleReferences
155
156 Converts project references in Bundle projects to HeatProject items to auto generate authoring.
157 ================================================================================================
158 -->
159 <Target
160 Name="ConvertBundleReferences"
161 Condition=" $(EnableProjectHarvesting) and ('$(OutputType)' == 'Bundle') ">
162
163 <ItemGroup>
164 <_HeatProjectReference Include="@(_MSBuildProjectReferenceExistent)" Condition=" '%(_MSBuildProjectReferenceExistent.DoNotHarvest)' == '' ">
165 <ProjectOutputGroups>Binaries;Symbols;Sources;Content;Satellites;Documents</ProjectOutputGroups>
166 <GenerateType>payloadgroup</GenerateType>
167 <HeatOutput>$(IntermediateOutputPath)_%(_MSBuildProjectReferenceExistent.Filename).wxs</HeatOutput>
168 </_HeatProjectReference>
169 <HeatProject Include="@(_HeatProjectReference)" />
170 </ItemGroup>
171
172 <Error
173 Text="The following files are deprecated and should be removed from your project(s): @(Compile->'%(Identity)', ', ')"
174 Condition=" '%(Compile.GenerateComponentGroups)' != '' " />
175
176 <ItemGroup>
177 <!-- Unconditionally generate Compile items so they are always linked in. -->
178 <Compile Include="$(HarvestProjectsGeneratedFile)" />
179 <_GeneratedFiles Include="$(HarvestProjectsGeneratedFile)" />
180 </ItemGroup>
181
182 </Target>
183
184 <!--
185 ================================================================================================
186 CombineHarvestProjects
187
188 Combines HeatProject and HarvestProject items together and ensures each has HeatOutput metadata.
189 ================================================================================================
190 -->
191 <Target
192 Name="CombineHarvestProjects"
193 Condition=" '@(HeatProject)' != '' or '@(HarvestProject)' != '' ">
194
195 <!-- Add default HeatOutputs for those without one specified -->
196 <CreateItem Include="@(HeatProject)" Condition= " '%(HeatProject.HeatOutput)' == '' "
197 AdditionalMetadata="HeatOutput=$(IntermediateOutputPath)_%(HeatProject.Filename).wxs">
198 <Output TaskParameter="Include" ItemName="_AllHeatProjects" />
199 </CreateItem>
200 <CreateItem Include="@(HarvestProject)" Condition= " '%(HarvestProject.HeatOutput)' == '' "
201 AdditionalMetadata="HeatOutput=$(IntermediateOutputPath)_%(HarvestProject.Filename).wxs">
202 <Output TaskParameter="Include" ItemName="_AllHeatProjects" />
203 </CreateItem>
204
205
206 <CreateItem Include="@(HeatProject)" Condition= " '%(HeatProject.HeatOutput)' != '' ">
207 <Output TaskParameter="Include" ItemName="_AllHeatProjects" />
208 </CreateItem>
209 <CreateItem Include="@(HarvestProject)" Condition= " '%(HarvestProject.HeatOutput)' != '' ">
210 <Output TaskParameter="Include" ItemName="_AllHeatProjects" />
211 </CreateItem>
212
213 </Target>
214
215 <!--
216 ================================================================================================
217 HarvestProjects
218
219 Harvests outputs of other MSBuild projects files using the VS project extension to heat.exe.
220
221 [IN]
222 @(HarvestProject)
223 @(HeatProject)
224 - The list of projects to harvest. HeatProject is provided for backward compatibility.
225 You should use HarvestProject instead.
226
227 %(HarvestProject.Transforms)
228 %(HeatProject.Transforms)
229 - XSL transforms to apply to the harvested WiX.
230
231 %(HarvestProject.ProjectOutputGroups)
232 %(HeatProjects.ProjectOutputGroups)
233 - The project output groups to harvest
234
235 [OUT]
236 %(HeatOutput)
237 - The generated .wxs files which are added to the @(Compile) item list.
238 ================================================================================================
239 -->
240 <ItemDefinitionGroup>
241 <HeatProject>
242 <Transforms>$(HarvestProjectsTransforms)</Transforms>
243 <ProjectOutputGroups>$(HarvestProjectsProjectOutputGroups)</ProjectOutputGroups>
244 <DirectoryIds>$(HarvestProjectsDirectoryIds)</DirectoryIds>
245 </HeatProject>
246 <HarvestProject>
247 <Transforms>$(HarvestProjectsTransforms)</Transforms>
248 <ProjectOutputGroups>$(HarvestProjectsProjectOutputGroups)</ProjectOutputGroups>
249 <DirectoryIds>$(HarvestProjectsDirectoryIds)</DirectoryIds>
250 </HarvestProject>
251 </ItemDefinitionGroup>
252
253 <PropertyGroup>
254 <HarvestProjectsDependsOn>CombineHarvestProjects</HarvestProjectsDependsOn>
255 </PropertyGroup>
256 <Target Name="HarvestProjects"
257 DependsOnTargets="$(HarvestProjectsDependsOn)"
258 Inputs="@(_AllHeatProjects);%(_AllHeatProjects.Transforms);$(MSBuildAllProjects);$(ProjectPath)"
259 Outputs="@(_AllHeatProjects -> '%(HeatOutput)')"
260 Condition=" $(EnableProjectHarvesting) and ('@(HeatProject)' != '' or '@(HarvestProject)' != '') ">
261
262 <HeatProject
263 NoLogo="$(HarvestProjectsNoLogo)"
264 SuppressAllWarnings="$(HarvestProjectsSuppressAllWarnings)"
265 SuppressSpecificWarnings="$(HarvestProjectsSuppressSpecificWarnings)"
266 ToolPath="$(WixToolPath)"
267 TreatWarningsAsErrors="$(HarvestProjectsTreatWarningsAsErrors)"
268 TreatSpecificWarningsAsErrors="$(HarvestProjectsTreatSpecificWarningsAsErrors)"
269 VerboseOutput="$(HarvestProjectsVerboseOutput)"
270 AutogenerateGuids="$(HarvestProjectsAutogenerateGuids)"
271 GenerateGuidsNow="$(HarvestProjectsGenerateGuidsNow)"
272 OutputFile="%(_AllHeatProjects.HeatOutput)"
273 SuppressFragments="$(HarvestProjectsSuppressFragments)"
274 SuppressUniqueIds="$(HarvestProjectsSuppressUniqueIds)"
275 Transforms="%(_AllHeatProjects.Transforms)"
276 Project="%(_AllHeatProjects.FullPath)"
277 ProjectOutputGroups="%(_AllHeatProjects.ProjectOutputGroups)"
278 GenerateType="%(_AllHeatProjects.GenerateType)"
279 DirectoryIds="%(_AllHeatProjects.DirectoryIds)"
280 ProjectName="%(_AllHeatProjects.ProjectName)"
281 Configuration="%(_AllHeatProjects.Configuration)"
282 Platform="%(_AllHeatProjects.Platform)"
283 RunAsSeparateProcess="$(RunWixToolsOutOfProc)"
284 GenerateWixVariables="$(HarvestProjectsGenerateWixVariables)"
285 AdditionalOptions="$(HarvestProjectsAdditionalOptions)">
286
287 <Output TaskParameter="OutputFile" ItemName="Compile" />
288 <Output TaskParameter="OutputFile" ItemName="FileWrites" />
289
290 </HeatProject>
291
292 </Target>
293
294 <!--
295 ================================================================================================
296 HarvestDirectory
297
298 Harvests directories using heat.exe.
299
300 [IN]
301 @(HarvestDirectory) - The list of directories to harvest.
302 %(HarvestDirectory.Transforms) - XSL transforms to apply to the harvested WiX.
303 %(HarvestDirectory.ComponentGroupName) - The name of the ComponentGroup to create.
304 %(HarvestDirectory.DirectoryRefId) - The ID of the directory to reference instead of TARGETDIR.
305 %(HarvestDirectory.KeepEmptyDirectories) - Whether to create Directory entries for empty directories.
306 %(HarvestDirectory.PreprocessorVariable) - Substitute SourceDir for another variable name (ex: var.Dir).
307 %(HarvestDirectory.SuppressCom) - Suppress COM elements.
308 %(HarvestDirectory.SuppressRootDirectory) - Suppress a Directory element for the root directory.
309 $(HarvestDirectory.SuppressRegistry) - Suppress registry harvesting.
310
311 [OUT]
312 $(IntermediateOutputPath)_%(HarvestDirectory.ComponentGroupName)_dir.wxs
313 - The generated .wxs files which are added to the @(Compile) item list.
314 ================================================================================================
315 -->
316
317 <ItemDefinitionGroup>
318 <HarvestDirectory>
319 <Transforms>$(HarvestDirectoryTransforms)</Transforms>
320 <ComponentGroupName>$(HarvestDirectoryComponentGroupName)</ComponentGroupName>
321 <DirectoryRefId>$(HarvestDirectoryDirectoryRefId)</DirectoryRefId>
322 <KeepEmptyDirectories>$(HarvestDirectoryKeepEmptyDirectories)</KeepEmptyDirectories>
323 <PreprocessorVariable>$(HarvestDirectoryPreprocessorVariable)</PreprocessorVariable>
324 <SuppressCom>$(HarvestDirectorySuppressCom)</SuppressCom>
325 <SuppressRootDirectory>$(HarvestDirectorySuppressRootDirectory)</SuppressRootDirectory>
326 <SuppressRegistry>$(HarvestDirectorySuppressRegistry)</SuppressRegistry>
327 </HarvestDirectory>
328 </ItemDefinitionGroup>
329
330 <PropertyGroup>
331 <HarvestDirectoryDependsOn>
332 GetHarvestDirectoryContent
333 </HarvestDirectoryDependsOn>
334 </PropertyGroup>
335
336 <!-- Creates items to include content since wildcards will not work in Target/@Inputs. -->
337 <Target Name="GetHarvestDirectoryContent">
338 <CreateItem Include="@(HarvestDirectory->'%(FullPath)\**\*')">
339 <Output TaskParameter="Include" ItemName="_HarvestDirectoryContent" />
340 </CreateItem>
341 </Target>
342
343 <Target Name="HarvestDirectory"
344 DependsOnTargets="$(HarvestDirectoryDependsOn)"
345 Inputs="@(_HarvestDirectoryContent);%(HarvestDirectory.Transforms)"
346 Outputs="$(IntermediateOutputPath)_%(HarvestDirectory.ComponentGroupName)_dir.wxs"
347 Condition=" '@(HarvestDirectory)' != '' ">
348
349 <HeatDirectory
350 NoLogo="$(HarvestDirectoryNoLogo)"
351 SuppressAllWarnings="$(HarvestDirectorySuppressAllWarnings)"
352 SuppressSpecificWarnings="$(HarvestDirectorySuppressSpecificWarnings)"
353 ToolPath="$(WixToolPath)"
354 TreatWarningsAsErrors="$(HarvestDirectoryTreatWarningsAsErrors)"
355 TreatSpecificWarningsAsErrors="$(HarvestDirectoryTreatSpecificWarningsAsErrors)"
356 VerboseOutput="$(HarvestDirectoryVerboseOutput)"
357 AutogenerateGuids="$(HarvestDirectoryAutogenerateGuids)"
358 GenerateGuidsNow="$(HarvestDirectoryGenerateGuidsNow)"
359 OutputFile="$(IntermediateOutputPath)_%(HarvestDirectory.ComponentGroupName)_dir.wxs"
360 SuppressFragments="$(HarvestDirectorySuppressFragments)"
361 SuppressUniqueIds="$(HarvestDirectorySuppressUniqueIds)"
362 Transforms="%(HarvestDirectory.Transforms)"
363 Directory="@(HarvestDirectory)"
364 ComponentGroupName="%(HarvestDirectory.ComponentGroupName)"
365 DirectoryRefId="%(HarvestDirectory.DirectoryRefId)"
366 KeepEmptyDirectories="%(HarvestDirectory.KeepEmptyDirectories)"
367 PreprocessorVariable="%(HarvestDirectory.PreprocessorVariable)"
368 RunAsSeparateProcess="$(RunWixToolsOutOfProc)"
369 SuppressCom="%(HarvestDirectory.SuppressCom)"
370 SuppressRootDirectory="%(HarvestDirectory.SuppressRootDirectory)"
371 SuppressRegistry="%(HarvestDirectory.SuppressRegistry)"
372 AdditionalOptions="$(HarvestDirectoryAdditionalOptions)">
373
374 <Output TaskParameter="OutputFile" ItemName="Compile" />
375 <Output TaskParameter="OutputFile" ItemName="FileWrites" />
376
377 </HeatDirectory>
378
379 </Target>
380
381 <!--
382 ================================================================================================
383 HarvestFile
384
385 Harvests files of different types using heat.exe. This can harvest registry from
386 self-registering files, files with typelibs, and more.
387
388 [IN]
389 @(HarvestFile) - The list of files to harvest.
390 %(HarvestFile.Transforms) - XSL transforms to apply to the harvested WiX.
391 %(HarvestFile.ComponentGroupName) - The name of the ComponentGroup to create.
392 %(HarvestFile.DirectoryRefId) - The ID of the directory to reference instead of TARGETDIR.
393 %(HarvestFile.PreprocessorVariable) - Substitute SourceDir for another variable name (ex: var.Dir).
394 %(HarvestFile.SuppressCom) - Suppress COM elements.
395 %(HarvestFile.SuppressRootDirectory) - Suppress a Directory element for the root directory.
396 $(HarvestFile.SuppressRegistry) - Suppress registry harvesting.
397
398 [OUT]
399 $(IntermediateOutputPath)_%(HarvestFile.Filename)_file.wxs
400 - The generated .wxs files which are added to the @(Compile) item list.
401 ================================================================================================
402 -->
403
404 <ItemDefinitionGroup>
405 <HarvestFile>
406 <Transforms>$(HarvestFileTransforms)</Transforms>
407 <ComponentGroupName>$(HarvestFileComponentGroupName)</ComponentGroupName>
408 <DirectoryRefId>$(HarvestFileDirectoryRefId)</DirectoryRefId>
409 <PreprocessorVariable>$(HarvestFilePreprocessorVariable)</PreprocessorVariable>
410 <SuppressCom>$(HarvestFileSuppressCom)</SuppressCom>
411 <SuppressRegistry>$(HarvestFileSuppressRegistry)</SuppressRegistry>
412 <SuppressRootDirectory>$(HarvestFileSuppressRootDirectory)</SuppressRootDirectory>
413 </HarvestFile>
414 </ItemDefinitionGroup>
415
416 <PropertyGroup>
417 <HarvestFileDependsOn></HarvestFileDependsOn>
418 </PropertyGroup>
419 <Target Name="HarvestFile"
420 DependsOnTargets="$(HarvestFileDependsOn)"
421 Inputs="@(HarvestFile);%(HarvestFile.Transforms)"
422 Outputs="$(IntermediateOutputPath)_%(HarvestFile.Filename)_file.wxs"
423 Condition=" '@(HarvestFile)' != '' ">
424
425 <HeatFile
426 NoLogo="$(HarvestFileNoLogo)"
427 SuppressAllWarnings="$(HarvestFileSuppressAllWarnings)"
428 SuppressSpecificWarnings="$(HarvestFileSuppressSpecificWarnings)"
429 ToolPath="$(WixToolPath)"
430 TreatWarningsAsErrors="$(HarvestFileTreatWarningsAsErrors)"
431 TreatSpecificWarningsAsErrors="$(HarvestFileTreatSpecificWarningsAsErrors)"
432 VerboseOutput="$(HarvestFileVerboseOutput)"
433 AutogenerateGuids="$(HarvestFileAutogenerateGuids)"
434 GenerateGuidsNow="$(HarvestFileGenerateGuidsNow)"
435 OutputFile="$(IntermediateOutputPath)_%(HarvestFile.Filename)_file.wxs"
436 SuppressFragments="$(HarvestFileSuppressFragments)"
437 SuppressUniqueIds="$(HarvestFileSuppressUniqueIds)"
438 Transforms="%(HarvestFile.Transforms)"
439 File="@(HarvestFile)"
440 ComponentGroupName="%(HarvestFile.ComponentGroupName)"
441 DirectoryRefId="%(HarvestFile.DirectoryRefId)"
442 PreprocessorVariable="%(HarvestFile.PreprocessorVariable)"
443 RunAsSeparateProcess="$(RunWixToolsOutOfProc)"
444 SuppressCom="%(HarvestFile.SuppressCom)"
445 SuppressRegistry="%(HarvestFile.SuppressRegistry)"
446 SuppressRootDirectory="%(HarvestFile.SuppressRootDirectory)"
447 AdditionalOptions="$(HarvestFileAdditionalOptions)">
448
449 <Output TaskParameter="OutputFile" ItemName="Compile" />
450 <Output TaskParameter="OutputFile" ItemName="FileWrites" />
451
452 </HeatFile>
453
454 </Target>
455
456 <!--
457 ================================================================================================
458 RefreshGeneratedFile
459
460 Generates code based on metadata defined in project references.
461
462 [IN]
463 @(_MSBuildResolvedProjectReferencePaths) - The list of MSBuildable project references.
464
465 [OUT]
466 @(_GeneratedFiles) - The generated source file.
467 ================================================================================================
468 -->
469 <PropertyGroup>
470 <RefreshGeneratedFileDependsOn></RefreshGeneratedFileDependsOn>
471 </PropertyGroup>
472 <Target Name="RefreshGeneratedFile"
473 DependsOnTargets="$(RefreshGeneratedFileDependsOn)"
474 Inputs="@(_MSBuildResolvedProjectReferencePaths);@(Compile);$(ProjectPath)"
475 Outputs="@(_GeneratedFiles)"
476 Condition=" $(EnableProjectHarvesting) and ('$(OutputType)' == 'Package' or '$(OutputType)' == 'PatchCreation' or '$(OutputType)' == 'Module') and '@(_GeneratedFiles)' != '' ">
477
478 <RefreshGeneratedFile
479 GeneratedFiles="@(_GeneratedFiles)"
480 ProjectReferencePaths="@(_MSBuildResolvedProjectReferencePaths)" />
481
482 </Target>
483
484 <!--
485 ================================================================================================
486 RefreshBundleGeneratedFile
487
488 Generates code for bundle projects based on metadata defined in project references.
489
490 [IN]
491 @(_MSBuildResolvedProjectReferencePaths) - The list of MSBuildable project references.
492
493 [OUT]
494 @(_GeneratedFiles) - The generated source file.
495 ================================================================================================
496 -->
497 <PropertyGroup>
498 <RefreshBundleGeneratedFileDependsOn></RefreshBundleGeneratedFileDependsOn>
499 </PropertyGroup>
500 <Target Name="RefreshBundleGeneratedFile"
501 DependsOnTargets="$(RefreshBundleGeneratedFileDependsOn)"
502 Inputs="@(_MSBuildResolvedProjectReferencePaths);@(Compile);$(ProjectPath)"
503 Outputs="@(_GeneratedFiles)"
504 Condition=" $(EnableProjectHarvesting) and ('$(OutputType)' == 'Bundle' and '@(_GeneratedFiles)' != '') ">
505
506 <RefreshBundleGeneratedFile
507 GeneratedFiles="@(_GeneratedFiles)"
508 ProjectReferencePaths="@(_MSBuildResolvedProjectReferencePaths)" />
509 </Target>
510
511</Project>
diff --git a/src/WixToolset.BuildTasks/wix.signing.targets b/src/WixToolset.BuildTasks/wix.signing.targets
new file mode 100644
index 00000000..6351cc8b
--- /dev/null
+++ b/src/WixToolset.BuildTasks/wix.signing.targets
@@ -0,0 +1,378 @@
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
5<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
6
7 <!-- These properties can be overridden to support non-default installations. -->
8 <PropertyGroup>
9 <WixTargetsPath Condition=" '$(WixTargetsPath)' == '' ">$(MSBuildThisFileFullPath)</WixTargetsPath>
10 <WixTasksPath Condition=" '$(WixTasksPath)' == '' ">$(WixTargetsPath)WixTasks.dll</WixTasksPath>
11
12 <SignedFile Condition=" '$(SignedFile)' == '' ">$(MSBuildProjectFile).Signed.txt</SignedFile>
13 </PropertyGroup>
14
15 <UsingTask TaskName="Insignia" AssemblyFile="$(WixTasksPath)" />
16
17 <!-- Default Inscribe properties. -->
18 <PropertyGroup>
19 <InscribeNoLogo Condition=" '$(InscribeNoLogo)' == '' ">$(NoLogo)</InscribeNoLogo>
20 <InscribeSuppressAllWarnings Condition=" '$(InscribeSuppressAllWarnings)' == '' ">$(SuppressAllWarnings)</InscribeSuppressAllWarnings>
21 <InscribeSuppressSpecificWarnings Condition=" '$(InscribeSuppressSpecificWarnings)' == '' ">$(SuppressSpecificWarnings)</InscribeSuppressSpecificWarnings>
22 <InscribeTreatWarningsAsErrors Condition=" '$(InscribeTreatWarningsAsErrors)' == '' ">$(TreatWarningsAsErrors)</InscribeTreatWarningsAsErrors>
23 <InscribeTreatSpecificWarningsAsErrors Condition=" '$(InscribeTreatSpecificWarningsAsErrors)' == '' ">$(TreatSpecificWarningsAsErrors)</InscribeTreatSpecificWarningsAsErrors>
24 <InscribeVerboseOutput Condition=" '$(InscribeVerboseOutput)' == '' ">$(VerboseOutput)</InscribeVerboseOutput>
25 </PropertyGroup>
26
27 <!--
28 ==================================================================================================
29 Signing
30 ==================================================================================================
31 -->
32 <PropertyGroup>
33 <InternalSignDependsOn Condition=" '$(OutputType)' == 'Module' ">
34 GetMsmsToSign;
35 InternalSignMsm;
36 </InternalSignDependsOn>
37 <InternalSignDependsOn Condition=" '$(OutputType)' == 'Package' ">
38 GetCabsToSign;
39 GetMsiToSign;
40 InternalSignCabs;
41 InscribeMsi;
42 InternalSignMsi;
43 </InternalSignDependsOn>
44 <InternalSignDependsOn Condition=" '$(OutputType)' == 'Bundle' ">
45 GetContainersToSign;
46 InternalSignContainers;
47 InscribeBundleEngine;
48 InternalSignBundleEngine;
49 InscribeBundle;
50 InternalSignBundle;
51 </InternalSignDependsOn>
52
53 <SigningDependsOn>
54 CompileAndLink;
55 BeforeSigning;
56 $(InternalSignDependsOn);
57 AfterSigning
58 </SigningDependsOn>
59 </PropertyGroup>
60 <Target
61 Name="Signing"
62 DependsOnTargets="$(SigningDependsOn)"
63 Inputs="@(SignTargetPath)"
64 Outputs="$(IntermediateOutputPath)$(SignedFile)"
65 Condition=" '@(SignTargetPath)' != '' ">
66
67 <CreateItem Include="$(IntermediateOutputPath)$(SignedFile)">
68 <Output TaskParameter="Include" ItemName="FileWrites" />
69 </CreateItem>
70
71 <WriteLinesToFile
72 File="$(IntermediateOutputPath)$(SignedFile)"
73 Lines="^$(MSBuildProjectFullPath);@(SignMsm);@(SignCabs);@(SignMsi);@(SignContainers);@(SignBundleEngine);@(SignBundle)"
74 Overwrite="true" />
75 </Target>
76
77 <!-- Internal targets so correct signing targets are called. -->
78 <Target
79 Name="GetMsmsToSign"
80 Inputs="@(SignTargetPath)"
81 Outputs="$(IntermediateOutputPath)$(SignedFile)">
82 <CreateItem Include="@(SignTargetPath)">
83 <Output TaskParameter="Include" ItemName="SignMsm" />
84 <Output TaskParameter="Include" ItemName="FileWrites" />
85 </CreateItem>
86 </Target>
87
88 <Target
89 Name="InternalSignMsm"
90 DependsOnTargets="SignMsm"
91 Condition=" '@(SignMsm)' != '' " />
92
93 <Target
94 Name="GetCabsToSign"
95 Inputs="@(SignTargetPath)"
96 Outputs="$(IntermediateOutputPath)$(SignedFile)">
97 <GetCabList Database="%(SignTargetPath.FullPath)">
98 <Output TaskParameter="CabList" ItemName="SignCabs" />
99 <Output TaskParameter="CabList" ItemName="FileWrites" />
100 </GetCabList>
101 </Target>
102
103 <Target
104 Name="InternalSignCabs"
105 DependsOnTargets="SignCabs"
106 Condition=" '@(SignCabs)' != '' " />
107
108 <Target
109 Name="GetMsiToSign"
110 Inputs="@(SignTargetPath)"
111 Outputs="$(IntermediateOutputPath)$(SignedFile)">
112 <CreateItemAvoidingInference InputProperties="@(SignTargetPath)">
113 <Output TaskParameter="OuputItems" ItemName="SignMsi" />
114 <Output TaskParameter="OuputItems" ItemName="FileWrites" />
115 </CreateItemAvoidingInference>
116 </Target>
117
118 <Target
119 Name="InternalSignMsi"
120 DependsOnTargets="SignMsi"
121 Inputs="@(SignTargetPath)"
122 Outputs="$(IntermediateOutputPath)$(SignedFile)"
123 Condition=" '@(SignMsi)' != '' " />
124
125 <Target
126 Name="GetContainersToSign"
127 Inputs="@(SignTargetPath)"
128 Outputs="$(IntermediateOutputPath)$(SignedFile)">
129 <!-- TODO: implement signing detached containers -->
130 </Target>
131
132 <Target
133 Name="InternalSignContainers"
134 DependsOnTargets="SignContainers"
135 Condition=" '@(SignContainers)' != '' " />
136
137 <Target
138 Name="InternalSignBundleEngine"
139 DependsOnTargets="SignBundleEngine"
140 Condition=" '@(SignBundleEngine)' != '' " />
141
142 <Target
143 Name="InternalSignBundle"
144 DependsOnTargets="SignBundle"
145 Condition=" '@(SignBundle)' != '' " />
146
147 <!--
148 ================================================================================================
149 InscribeMsi
150
151 To be called after signing an MSI's cabs - inscribes an MSI with the digital signature of its
152 external cabs.
153
154 [IN/OUT]
155 @(SignTargetPath) - The database file to inscribe - database file will be modified in-place.
156
157 [OUT]
158 @(SignMsi) - The database file to sign.
159 ================================================================================================
160 -->
161 <PropertyGroup>
162 <InscribeMsiDependsOn>
163 PrepareForBuild;
164 ResolveWixExtensionReferences;
165 CompileAndLink;
166 InternalSignCabs
167 </InscribeMsiDependsOn>
168 </PropertyGroup>
169 <Target
170 Name="InscribeMsi"
171 DependsOnTargets="$(InscribeMsiDependsOn)"
172 Inputs="@(SignTargetPath)"
173 Outputs="$(IntermediateOutputPath)$(SignedFile)"
174 Condition=" '@(SignCabs)' != '' ">
175
176 <Insignia
177 DatabaseFile="%(SignTargetPath.FullPath)"
178 OutputFile="%(SignTargetPath.FullPath)"
179 ToolPath="$(WixToolPath)"
180 NoLogo="$(InscribeNoLogo)"
181 RunAsSeparateProcess="$(RunWixToolsOutOfProc)"
182 SuppressAllWarnings="$(InscribeSuppressAllWarnings)"
183 SuppressSpecificWarnings="$(InscribeSuppressSpecificWarnings)"
184 TreatWarningsAsErrors="$(InscribeTreatWarningsAsErrors)"
185 TreatSpecificWarningsAsErrors="$(InscribeTreatSpecificWarningsAsErrors)"
186 VerboseOutput="$(InscribeVerboseOutput)"
187 AdditionalOptions="$(InscribeAdditionalOptions)" />
188 </Target>
189
190 <!--
191 ================================================================================================
192 InscribeBundleEngine
193
194 To be called after signing a bundle's detached containers. Also removes attached container
195 so engine can be signed without attached container.
196
197 [IN]
198 @(SignTargetPath) - The bundle to inscribe.
199
200 [OUT]
201 @(SignBundleEngine) - The bundle engine file to be signed.
202 ================================================================================================
203 -->
204 <PropertyGroup>
205 <InscribeBundleEngineDependsOn>
206 PrepareForBuild;
207 ResolveWixExtensionReferences;
208 CompileAndLink;
209 InternalSignContainers
210 </InscribeBundleEngineDependsOn>
211 </PropertyGroup>
212 <Target
213 Name="InscribeBundleEngine"
214 DependsOnTargets="$(InscribeBundleEngineDependsOn)"
215 Inputs="@(SignTargetPath)"
216 Outputs="$(IntermediateOutputPath)$(SignedFile)">
217
218 <Insignia
219 BundleFile="@(SignTargetPath)"
220 OutputFile="$(IntermediateOutputPath)%(SignTargetPath.Filename)%(SignTargetPath.Extension)"
221 ToolPath="$(WixToolPath)"
222 NoLogo="$(InscribeNoLogo)"
223 RunAsSeparateProcess="$(RunWixToolsOutOfProc)"
224 SuppressAllWarnings="$(InscribeSuppressAllWarnings)"
225 SuppressSpecificWarnings="$(InscribeSuppressSpecificWarnings)"
226 TreatWarningsAsErrors="$(InscribeTreatWarningsAsErrors)"
227 TreatSpecificWarningsAsErrors="$(InscribeTreatSpecificWarningsAsErrors)"
228 VerboseOutput="$(InscribeVerboseOutput)"
229 AdditionalOptions="$(InscribeAdditionalOptions)">
230 <Output TaskParameter="Output" ItemName="SignBundleEngine" />
231 </Insignia>
232
233 <!-- Explicitly add output to FileWrites to ensure even when the target is up to date. -->
234 <CreateItem Include="$(IntermediateOutputPath)%(SignTargetPath.Filename)%(SignTargetPath.Extension)">
235 <Output TaskParameter="Include" ItemName="FileWrites" />
236 </CreateItem>
237
238 </Target>
239
240 <!--
241 ================================================================================================
242 InscribeBundle
243
244 To be called after signing the bundle engine to reattach the attached container.
245
246 [IN]
247 @(Inscribe) - The bundle to inscribe.
248
249 [OUT]
250 @(SignBundle) - The bundle engine file to be signed.
251 ================================================================================================
252 -->
253 <PropertyGroup>
254 <InscribeBundleDependsOn>
255 PrepareForBuild;
256 ResolveWixExtensionReferences;
257 CompileAndLink;
258 InternalSignBundleEngine
259 </InscribeBundleDependsOn>
260 </PropertyGroup>
261 <Target
262 Name="InscribeBundle"
263 DependsOnTargets="$(InscribeBundleDependsOn)"
264 Inputs="@(SignTargetPath)"
265 Outputs="$(IntermediateOutputPath)$(SignedFile)">
266
267 <Insignia
268 BundleFile="@(SignBundleEngine)"
269 OriginalBundleFile="@(SignTargetPath)"
270 OutputFile="@(SignTargetPath)"
271 ToolPath="$(WixToolPath)"
272 NoLogo="$(InscribeNoLogo)"
273 RunAsSeparateProcess="$(RunWixToolsOutOfProc)"
274 SuppressAllWarnings="$(InscribeSuppressAllWarnings)"
275 SuppressSpecificWarnings="$(InscribeSuppressSpecificWarnings)"
276 TreatWarningsAsErrors="$(InscribeTreatWarningsAsErrors)"
277 TreatSpecificWarningsAsErrors="$(InscribeTreatSpecificWarningsAsErrors)"
278 VerboseOutput="$(InscribeVerboseOutput)"
279 AdditionalOptions="$(InscribeAdditionalOptions)">
280 <Output TaskParameter="Output" ItemName="SignBundle" />
281 <Output TaskParameter="Output" ItemName="FileWrites" />
282 </Insignia>
283 </Target>
284
285 <!--
286 ==================================================================================================
287 BeforeSigning
288
289 Redefine this target in your project in order to run tasks just before all signing tasks.
290 ==================================================================================================
291 -->
292 <Target Name="BeforeSigning" />
293
294 <!--
295 ==================================================================================================
296 SignMsm
297
298 Redefine this target in your project in order to sign merge modules.
299
300 [IN]
301 @(SignMsm) - merge module files to sign.
302 ==================================================================================================
303 -->
304 <Target Name="SignMsm" />
305
306 <!--
307 ==================================================================================================
308 SignCabs
309
310 Redefine this target in your project in order to sign the cabs of your database.
311
312 [IN]
313 @(SignCabs) - cabinet files to sign.
314 ==================================================================================================
315 -->
316 <Target Name="SignCabs" />
317
318 <!--
319 ==================================================================================================
320 SignMsi
321
322 Redefine this target in your project in order to sign your database, after it has been inscribed
323 with the signatures of your signed cabs.
324
325 [IN]
326 @(SignMsi) - database files to sign.
327 ==================================================================================================
328 -->
329 <Target Name="SignMsi" />
330
331 <!--
332 ==================================================================================================
333 SignContainers
334
335 Redefine this target in your project in order to sign your bundle's detached containers.
336
337 [IN]
338 @(SignContainers) - detached container files to sign.
339 ==================================================================================================
340 -->
341 <Target Name="SignContainers" />
342
343 <!--
344 ==================================================================================================
345 SignBundleEngine
346
347 Redefine this target in your project in order to sign your bundle, after it has been inscribed
348 with the signatures of your signed containers.
349
350 [IN]
351 @(SignBundleEngine) - bundle engine file to sign.
352 ==================================================================================================
353 -->
354 <Target Name="SignBundleEngine" />
355
356 <!--
357 ==================================================================================================
358 SignBundle
359
360 Redefine this target in your project in order to sign your bundle, after the attached container
361 is reattached.
362
363 [IN]
364 @(SignBundle) - bundle file to sign.
365 ==================================================================================================
366 -->
367 <Target Name="SignBundle" />
368
369 <!--
370 ==================================================================================================
371 AfterSigning
372
373 Redefine this target in your project in order to run tasks just after all signing tasks.
374 ==================================================================================================
375 -->
376 <Target Name="AfterSigning" />
377
378</Project>
diff --git a/src/WixToolset.BuildTasks/wix.targets b/src/WixToolset.BuildTasks/wix.targets
new file mode 100644
index 00000000..eadd33ec
--- /dev/null
+++ b/src/WixToolset.BuildTasks/wix.targets
@@ -0,0 +1,1353 @@
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
5<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" InitialTargets="_CheckRequiredProperties" DefaultTargets="Build">
6 <PropertyGroup>
7 <WixTargetsImported>true</WixTargetsImported>
8 </PropertyGroup>
9
10 <!--
11 //////////////////////////////////////////////////////////////////////////////////////////////////
12 //////////////////////////////////////////////////////////////////////////////////////////////////
13 Extension Points
14 //////////////////////////////////////////////////////////////////////////////////////////////////
15 //////////////////////////////////////////////////////////////////////////////////////////////////
16 -->
17
18 <!-- Allow a user-customized targets files to be used as part of the build. -->
19 <Import Project="$(CustomBeforeWixTargets)" Condition=" '$(CustomBeforeWixTargets)' != '' and Exists('$(CustomBeforeWixTargets)')" />
20
21 <!-- These properties can be overridden to support non-default installations. -->
22 <PropertyGroup>
23 <WixBinDir Condition=" '$(WixBinDir)' == ''">$(MSBuildThisFileDirectory)</WixBinDir>
24 <WixTasksPath Condition=" '$(WixTasksPath)' == '' ">$(WixBinDir)WixToolset.BuildTasks.dll</WixTasksPath>
25 <WixHarvestTargetsPath Condition=" '$(WixHarvestTargetsPath)' == '' ">$(WixBinDir)wix.harvest.targets</WixHarvestTargetsPath>
26 <WixSigningTargetsPath Condition=" '$(WixSigningTargetsPath)' == '' ">$(WixBinDir)wix.signing.targets</WixSigningTargetsPath>
27 <LuxTargetsPath Condition=" '$(LuxTargetsPath)' == '' ">$(WixBinDir)lux.targets</LuxTargetsPath>
28 <LuxTasksPath Condition=" '$(LuxTasksPath)' == '' ">$(WixBinDir)LuxTasks.dll</LuxTasksPath>
29 </PropertyGroup>
30
31 <!-- This makes the project files a dependency of all targets so that things rebuild if they change -->
32 <PropertyGroup>
33 <MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
34 <MSBuildAllProjects Condition="Exists('$(WixHarvestTargetsPath)')">$(MSBuildAllProjects);$(WixHarvestTargetsPath)</MSBuildAllProjects>
35 <MSBuildAllProjects Condition="Exists('$(WixSigningTargetsPath)')">$(MSBuildAllProjects);$(WixSigningTargetsPath)</MSBuildAllProjects>
36 <MSBuildAllProjects Condition="Exists('$(LuxTargetsPath)')">$(MSBuildAllProjects);$(LuxTargetsPath)</MSBuildAllProjects>
37 <MSBuildAllProjects Condition="Exists('$(CustomBeforeWixTargets)')">$(MSBuildAllProjects);$(CustomBeforeWixTargets)</MSBuildAllProjects>
38 <MSBuildAllProjects Condition="Exists('$(CustomAfterWixTargets)')">$(MSBuildAllProjects);$(CustomAfterWixTargets)</MSBuildAllProjects>
39 </PropertyGroup>
40
41 <!--
42 //////////////////////////////////////////////////////////////////////////////////////////////////
43 //////////////////////////////////////////////////////////////////////////////////////////////////
44 Declarations for Microsoft.Common.targets
45 //////////////////////////////////////////////////////////////////////////////////////////////////
46 //////////////////////////////////////////////////////////////////////////////////////////////////
47 -->
48
49 <PropertyGroup>
50 <DefaultLanguageSourceExtension>.wxs</DefaultLanguageSourceExtension>
51 <Language>wix</Language>
52 <TargetRuntime>Managed</TargetRuntime>
53
54 <!-- Use OutputName to set the AssemblyName for Microsoft.Common.targets -->
55 <OutputName Condition=" '$(OutputName)'=='' ">$(MSBuildProjectName)</OutputName>
56 <AssemblyName>$(OutputName)</AssemblyName>
57
58 <!-- Default the OutputType to a known WiX toolset TYPE. -->
59 <_OriginalOutputType>$(OutputType)</_OriginalOutputType>
60 <OutputType Condition=" '$(OutputType)' == '' ">Package</OutputType>
61 </PropertyGroup>
62
63 <!--
64 IDE Macros available from both integrated builds and from command line builds.
65 The following properties are 'macros' that are available via IDE for pre and post build steps.
66 All of them should be added to WixBuildMacroCollection to ensure that they are shown in the UI.
67 -->
68 <PropertyGroup>
69 <TargetExt Condition=" '$(OutputType)' == 'Package' ">.msi</TargetExt>
70 <TargetExt Condition=" '$(OutputType)' == 'Module' ">.msm</TargetExt>
71 <TargetExt Condition=" '$(OutputType)' == 'PatchCreation' ">.pcp</TargetExt>
72 <TargetExt Condition=" '$(OutputType)' == 'Library' ">.wixlib</TargetExt>
73 <TargetExt Condition=" '$(OutputType)' == 'Bundle' ">.exe</TargetExt>
74 </PropertyGroup>
75
76 <!-- Provide the correct output name for the .wixpdb -->
77 <ItemGroup Condition="'$(_DebugSymbolsProduced)' == 'true'">
78 <_DebugSymbolsIntermediatePath Include="$(PdbOutputDir)$(TargetPdbName)" Condition=" '@(_DebugSymbolsIntermediatePath)' == '' " />
79 </ItemGroup>
80
81 <Import Project="$(MSBuildToolsPath)\Microsoft.Common.targets" />
82
83 <PropertyGroup>
84 <!-- Default pdb output path to the intermediate output directory -->
85 <PdbOutputDir Condition=" '$(PdbOutputDir)'=='' ">$(IntermediateOutputPath)</PdbOutputDir>
86 <PdbOutputDir Condition=" '$(PdbOutputDir)' != '' and !HasTrailingSlash('$(PdbOutputDir)') ">$(PdbOutputDir)\</PdbOutputDir>
87
88 <!-- Example, C:\MyProjects\MyProject\bin\debug\ -->
89 <TargetPdbDir Condition=" '$(PdbOutputDir)'!='' ">$([System.IO.Path]::GetFullPath(`$([System.IO.Path]::Combine(`$(MSBuildProjectDirectory)`, `$(PdbOutputDir)`))`))</TargetPdbDir>
90
91 <!-- Example, MySetup.wixpdb" -->
92 <TargetPdbName Condition=" '$(TargetPdbName)' == '' ">$(TargetName).wixpdb</TargetPdbName>
93
94 <!-- Example, C:\MyProjects\MyProject\bin\debug\MyPackage.wixpdb -->
95 <TargetPdbPath Condition=" '$(TargetPdbPath)' == '' ">$(TargetPdbDir)$(TargetPdbName)</TargetPdbPath>
96 </PropertyGroup>
97
98 <!--
99 //////////////////////////////////////////////////////////////////////////////////////////////////
100 //////////////////////////////////////////////////////////////////////////////////////////////////
101 Property Declarations
102 //////////////////////////////////////////////////////////////////////////////////////////////////
103 //////////////////////////////////////////////////////////////////////////////////////////////////
104 -->
105
106 <!-- These tasks can be used as general-purpose build tasks. -->
107 <UsingTask TaskName="Candle" AssemblyFile="$(WixTasksPath)" />
108 <UsingTask TaskName="DoIt" AssemblyFile="$(WixTasksPath)" />
109 <UsingTask TaskName="Lit" AssemblyFile="$(WixTasksPath)" />
110 <UsingTask TaskName="Light" AssemblyFile="$(WixTasksPath)" />
111 <UsingTask TaskName="Torch" AssemblyFile="$(WixTasksPath)" />
112
113 <!-- These tasks are specific to the build process defined in this file, and are not considered general-purpose build tasks. -->
114 <UsingTask TaskName="CreateItemAvoidingInference" AssemblyFile="$(WixTasksPath)" />
115 <UsingTask TaskName="CreateProjectReferenceDefineConstants" AssemblyFile="$(WixTasksPath)" />
116 <UsingTask TaskName="WixAssignCulture" AssemblyFile="$(WixTasksPath)" />
117 <UsingTask TaskName="ResolveWixReferences" AssemblyFile="$(WixTasksPath)"/>
118 <UsingTask TaskName="ReplaceString" AssemblyFile="$(WixTasksPath)"/>
119 <UsingTask TaskName="GetCabList" AssemblyFile="$(WixTasksPath)" />
120 <UsingTask TaskName="GetLooseFileList" AssemblyFile="$(WixTasksPath)" />
121 <UsingTask TaskName="GenerateCompileWithObjectPath" AssemblyFile="$(WixTasksPath)"/>
122
123 <!-- WiX tools are 32bit EXEs, so run them out-of-proc when MSBuild is not 32bit. -->
124 <PropertyGroup>
125 <RunWixToolsOutOfProc Condition=" '$(PROCESSOR_ARCHITECTURE)'!='x86' ">true</RunWixToolsOutOfProc>
126 </PropertyGroup>
127
128 <PropertyGroup>
129 <BindContentsFile Condition=" '$(BindContentsFile)' == '' ">$(MSBuildProjectFile).BindContentsFileList.txt</BindContentsFile>
130 <BindOutputsFile Condition=" '$(BindOutputsFile)' == '' ">$(MSBuildProjectFile).BindOutputsFileList.txt</BindOutputsFile>
131 <BindBuiltOutputsFile Condition=" '$(BindBuiltOutputsFile)' == '' ">$(MSBuildProjectFile).BindBuiltOutputsFileList.txt</BindBuiltOutputsFile>
132 </PropertyGroup>
133
134 <PropertyGroup>
135 <CabinetCachePath Condition=" '$(CabinetCachePath)'=='' and '$(ReuseCabinetCache)'=='true' ">$(IntermediateOutputPath)cabcache\</CabinetCachePath>
136 </PropertyGroup>
137
138 <PropertyGroup>
139 <WixToolDir Condition=" '$(WixToolDir)' == ''">$(WixBinDir)</WixToolDir>
140 <WixExtDir Condition=" '$(WixExtDir)' == ''">$(WixToolDir)</WixExtDir>
141 </PropertyGroup>
142
143 <!--
144 Set the SignTargetPath item directly when output is a Bundle. The AssignCultures target
145 sets SignTargetPath item for other output types based on the cultures provided.
146 -->
147 <ItemGroup>
148 <SignTargetPath Include="$(TargetPath)" Condition=" '$(OutputType)' == 'Bundle' AND '$(SignOutput)' == 'true' AND '$(SuppressLayout)' != 'true' " />
149 </ItemGroup>
150
151 <!--
152 //////////////////////////////////////////////////////////////////////////////////////////////////
153 //////////////////////////////////////////////////////////////////////////////////////////////////
154 Default Compiler, Linker, and Librarian Property Declarations
155 //////////////////////////////////////////////////////////////////////////////////////////////////
156 //////////////////////////////////////////////////////////////////////////////////////////////////
157 -->
158
159 <!-- If WixExtension was passed in via the command line, then convert it to an ItemGroup -->
160 <ItemGroup>
161 <WixExtension Include="$(WixExtension)" Condition=" '$(WixExtension)' != '' " />
162 </ItemGroup>
163
164 <!-- Defaut Compiler properties. -->
165 <PropertyGroup>
166 <CompilerNoLogo Condition=" '$(CompilerNoLogo)' == '' ">$(NoLogo)</CompilerNoLogo>
167 <CompilerSuppressAllWarnings Condition=" '$(CompilerSuppressAllWarnings)' == '' ">$(SuppressAllWarnings)</CompilerSuppressAllWarnings>
168 <CompilerSuppressSpecificWarnings Condition=" '$(CompilerSuppressSpecificWarnings)' == '' ">$(SuppressSpecificWarnings)</CompilerSuppressSpecificWarnings>
169 <CompilerTreatWarningsAsErrors Condition=" '$(CompilerTreatWarningsAsErrors)' == '' ">$(TreatWarningsAsErrors)</CompilerTreatWarningsAsErrors>
170 <CompilerTreatSpecificWarningsAsErrors Condition=" '$(CompilerTreatSpecificWarningsAsErrors)' == '' ">$(TreatSpecificWarningsAsErrors)</CompilerTreatSpecificWarningsAsErrors>
171 <CompilerVerboseOutput Condition=" '$(CompilerVerboseOutput)' == '' ">$(VerboseOutput)</CompilerVerboseOutput>
172 <!-- TODO: This probably doesn't work any longer since Platform won't be defined until Microsoft.Common.targets is included -->
173 <InstallerPlatform Condition=" '$(InstallerPlatform)' == '' and '$(Platform)' != 'AnyCPU' and '$(Platform)' != 'Any CPU' ">$(Platform)</InstallerPlatform>
174 </PropertyGroup>
175
176 <!-- Default Lib properties. -->
177 <PropertyGroup>
178 <LibNoLogo Condition=" '$(LibNoLogo)' == '' ">$(NoLogo)</LibNoLogo>
179 <LibBindFiles Condition=" '$(LibBindFiles)' == '' ">$(BindFiles)</LibBindFiles>
180 <LibPedantic Condition=" '$(LibPedantic)' == '' ">$(Pedantic)</LibPedantic>
181 <LibSuppressAllWarnings Condition=" '$(LibSuppressAllWarnings)' == '' ">$(SuppressAllWarnings)</LibSuppressAllWarnings>
182 <LibSuppressSpecificWarnings Condition=" '$(LibSuppressSpecificWarnings)' == '' ">$(SuppressSpecificWarnings)</LibSuppressSpecificWarnings>
183 <LibSuppressSchemaValidation Condition=" '$(LibSuppressSchemaValidation)' == '' ">$(SuppressSchemaValidation)</LibSuppressSchemaValidation>
184 <LibSuppressIntermediateFileVersionMatching Condition=" '$(LibSuppressIntermediateFileVersionMatching)' == '' ">$(SuppressIntermediateFileVersionMatching)</LibSuppressIntermediateFileVersionMatching>
185 <LibTreatWarningsAsErrors Condition=" '$(LibTreatWarningsAsErrors)' == '' ">$(TreatWarningsAsErrors)</LibTreatWarningsAsErrors>
186 <LibTreatSpecificWarningsAsErrors Condition=" '$(LibTreatSpecificWarningsAsErrors)' == '' ">$(TreatSpecificWarningsAsErrors)</LibTreatSpecificWarningsAsErrors>
187 <LibVerboseOutput Condition=" '$(LibVerboseOutput)' == '' ">$(VerboseOutput)</LibVerboseOutput>
188 </PropertyGroup>
189
190 <!-- Default Linker properties. -->
191 <PropertyGroup>
192 <LinkerNoLogo Condition=" '$(LinkerNoLogo)' == '' ">$(NoLogo)</LinkerNoLogo>
193 <LinkerBindFiles Condition=" '$(LinkerBindFiles)' == '' ">$(BindFiles)</LinkerBindFiles>
194 <LinkerPedantic Condition=" '$(LinkerPedantic)' == '' ">$(Pedantic)</LinkerPedantic>
195 <LinkerSuppressAllWarnings Condition=" '$(LinkerSuppressAllWarnings)' == '' ">$(SuppressAllWarnings)</LinkerSuppressAllWarnings>
196 <LinkerSuppressSpecificWarnings Condition=" '$(LinkerSuppressSpecificWarnings)' == '' ">$(SuppressSpecificWarnings)</LinkerSuppressSpecificWarnings>
197 <LinkerSuppressSchemaValidation Condition=" '$(LinkerSuppressSchemaValidation)' == '' ">$(SuppressSchemaValidation)</LinkerSuppressSchemaValidation>
198 <LinkerSuppressIntermediateFileVersionMatching Condition=" '$(LinkerSuppressIntermediateFileVersionMatching)' == '' ">$(SuppressIntermediateFileVersionMatching)</LinkerSuppressIntermediateFileVersionMatching>
199 <LinkerTreatWarningsAsErrors Condition=" '$(LinkerTreatWarningsAsErrors)' == '' ">$(TreatWarningsAsErrors)</LinkerTreatWarningsAsErrors>
200 <LinkerTreatSpecificWarningsAsErrors Condition=" '$(LinkerTreatSpecificWarningsAsErrors)' == '' ">$(TreatSpecificWarningsAsErrors)</LinkerTreatSpecificWarningsAsErrors>
201 <LinkerVerboseOutput Condition=" '$(LinkerVerboseOutput)' == '' ">$(VerboseOutput)</LinkerVerboseOutput>
202 </PropertyGroup>
203
204 <!-- If BindInputPaths (or LinkerBindInputPaths) was passed in via the command line, then convert it to an ItemGroup -->
205 <ItemGroup>
206 <BindInputPaths Include="$(BindInputPaths)" Condition=" '$(BindInputPaths)' != '' " />
207 <LinkerBindInputPaths Include="$(LinkerBindInputPaths)" Condition=" '$(LinkerBindInputPaths)' != '' " />
208 </ItemGroup>
209
210 <!-- Default Lit and Light "properties" -->
211 <ItemGroup>
212 <LinkerBindInputPaths Condition=" '@(LinkerBindInputPaths)' == '' " Include="@(BindInputPaths)" />
213 </ItemGroup>
214
215 <!--
216 //////////////////////////////////////////////////////////////////////////////////////////////////
217 //////////////////////////////////////////////////////////////////////////////////////////////////
218 Initial Targets
219 //////////////////////////////////////////////////////////////////////////////////////////////////
220 //////////////////////////////////////////////////////////////////////////////////////////////////
221 -->
222
223 <!--
224 ==================================================================================================
225 _CheckRequiredProperties
226
227 Checks properties that must be set in the main project file or on the command line before
228 using this .TARGETS file.
229
230 [IN]
231 $(OutputName) - The name of the MSI/MSM/wixlib to build (without the extension)
232 $(OutputType) - Possible values are 'package', 'PatchCreation', 'module', 'library', 'bundle'
233 ==================================================================================================
234 -->
235 <PropertyGroup>
236 <_PleaseSetThisInProjectFile>Please set this in the project file before the &lt;Import&gt; of the wix.targets file.</_PleaseSetThisInProjectFile>
237 <_OutputTypeDescription>The OutputType defines whether a Windows Installer package (.msi), PatchCreation (.pcp), merge module (.msm), wix library (.wixlib), or self-extracting executable (.exe) is being built. $(_PleaseSetThisInProjectFile) Possible values are 'Package', 'Module', 'Library', and 'Bundle'.</_OutputTypeDescription>
238 </PropertyGroup>
239 <Target Name="_CheckRequiredProperties">
240
241 <Error
242 Code="WIXTARGETS100"
243 Condition=" '$(OutputName)' == '' "
244 Text="The OutputName property is not set in project &quot;$(MSBuildProjectFile)&quot;. The OutputName defines the name of the output without a file extension. $(_PleaseSetThisInProjectFile)" />
245
246 <Warning
247 Code="WIXTARGETS101"
248 Condition=" '$(_OriginalOutputType)' == '' "
249 Text="The OutputType property is not set in project &quot;$(MSBuildProjectFile)&quot;. Defaulting to '$(OutputType)'. $(_OutputTypeDescription)" />
250
251 <Error
252 Code="WIXTARGETS102"
253 Condition=" '$(OutputType)' != 'Package' and '$(OutputType)' != 'PatchCreation' and '$(OutputType)' != 'Module' and '$(OutputType)' != 'Library' and '$(OutputType)' != 'Bundle' "
254 Text="The OutputType property '$(OutputType)' is not valid in project &quot;$(MSBuildProjectFile)&quot;. $(_OutputTypeDescription)" />
255
256 <Error
257 Code="WIXTARGETS103"
258 Condition=" '$(MSBuildToolsVersion)' == '' OR '$(MSBuildToolsVersion)' &lt; '4.0' "
259 Text="MSBuild v$(MSBuildToolsVersion) is not supported by the project &quot;$(MSBuildProjectFile)&quot;. You must use MSBuild v4.0 or later." />
260
261 </Target>
262
263 <!--
264 //////////////////////////////////////////////////////////////////////////////////////////////////
265 //////////////////////////////////////////////////////////////////////////////////////////////////
266 Build Targets
267 //////////////////////////////////////////////////////////////////////////////////////////////////
268 //////////////////////////////////////////////////////////////////////////////////////////////////
269 -->
270
271 <!--
272 ==================================================================================================
273 CoreBuild - OVERRIDE DependsOn
274
275 The core build step calls each of the build targets.
276
277 This is where we insert our targets into the build process.
278 ==================================================================================================
279 -->
280 <PropertyGroup>
281 <CoreBuildDependsOn>
282 BuildOnlySettings;
283 PrepareForBuild;
284 PreBuildEvent;
285 ResolveReferences;
286
287 <!--CompileAndLink;-->
288 DoIt;
289 Signing;
290
291 GetTargetPath;
292 PrepareForRun;
293 IncrementalClean;
294 PostBuildEvent
295 </CoreBuildDependsOn>
296 </PropertyGroup>
297
298
299 <!--
300 //////////////////////////////////////////////////////////////////////////////////////////////////
301 //////////////////////////////////////////////////////////////////////////////////////////////////
302 Resolve References Targets
303 //////////////////////////////////////////////////////////////////////////////////////////////////
304 //////////////////////////////////////////////////////////////////////////////////////////////////
305 -->
306
307 <!--
308 ==================================================================================================
309 ResolveReferences - OVERRIDE DependsOn
310
311 ==================================================================================================
312 -->
313 <PropertyGroup>
314 <ResolveReferencesDependsOn>
315 BeforeResolveReferences;
316 AssignProjectConfiguration;
317 ResolveProjectReferences;
318 ResolveWixLibraryReferences;
319 ResolveWixExtensionReferences;
320 AfterResolveReferences
321 </ResolveReferencesDependsOn>
322 </PropertyGroup>
323
324 <!--
325 ================================================================================================
326 ResolveProjectReferences
327
328 Builds all of the referenced projects to get their outputs.
329
330 [IN]
331 @(NonVCProjectReference) - The list of non-VC project references.
332
333 [OUT]
334 @(ProjectReferenceWithConfiguration) - The list of non-VC project references.
335 @(WixLibProjects) - Paths to any .wixlibs that were built by referenced projects.
336 ================================================================================================
337 -->
338 <Target
339 Name="ResolveProjectReferences"
340 DependsOnTargets="AssignProjectConfiguration;_SplitProjectReferencesByFileExistence"
341 Condition=" '@(ProjectReferenceWithConfiguration)' != '' ">
342
343 <!-- Issue a warning for each non-existent project. -->
344 <Warning
345 Text="The referenced project '%(_MSBuildProjectReferenceNonexistent.Identity)' does not exist."
346 Condition=" '@(_MSBuildProjectReferenceNonexistent)' != '' " />
347
348 <!--
349 When building this project from the IDE or when building a .sln from the command line or
350 when only building .wixlib project references, gather the referenced build outputs. The
351 code that builds the .sln will already have built the project, so there's no need to do
352 it again here and when building only .wixlib project references we'll use the results to
353 determine which projects to build.
354
355 The ContinueOnError setting is here so that, during project load, as much information as
356 possible will be passed to the compilers.
357 -->
358 <MSBuild
359 Projects="@(_MSBuildProjectReferenceExistent)"
360 Targets="%(_MSBuildProjectReferenceExistent.Targets);GetTargetPath"
361 Properties="%(_MSBuildProjectReferenceExistent.SetConfiguration);%(_MSBuildProjectReferenceExistent.SetPlatform)"
362 Condition="('$(BuildingSolutionFile)' == 'true' or '$(BuildingInsideVisualStudio)' == 'true' or '$(BuildProjectReferences)' != 'true') and '@(_MSBuildProjectReferenceExistent)' != '' "
363 ContinueOnError="!$(BuildingProject)">
364
365 <Output TaskParameter="TargetOutputs" ItemName="_GatheredProjectReferencePaths" />
366 </MSBuild>
367
368 <!--
369 Determine which project references should be built. Note: we will not build any project references
370 if building in the IDE because it builds project references directly.
371
372 If BuildProjectReferences is 'true' (the default) then take all MSBuild project references that exist
373 on disk and add them to the list of things to build. This is the easy case.
374 -->
375 <CreateItem
376 Include="@(_MSBuildProjectReferenceExistent)"
377 Condition=" '$(BuildProjectReferences)' == 'true' and '$(BuildingInsideVisualStudio)' != 'true' ">
378
379 <Output TaskParameter="Include" ItemName="_ProjectReferencesToBuild" />
380 </CreateItem>
381
382 <!--
383 If BuildProjectReferences is 'wixlib' then build only the MSBuild project references that exist and
384 create a .wixlib file. That requires us to first filter the gathered project references down to only
385 those that build .wixlibs.
386 -->
387 <CreateItem
388 Include="@(_GatheredProjectReferencePaths)"
389 Condition=" '$(BuildProjectReferences)' == 'wixlib' and '%(Extension)' == '.wixlib' and '$(BuildingInsideVisualStudio)' != 'true' ">
390
391 <Output TaskParameter="Include" ItemName="_ReferencedWixLibPaths" />
392 </CreateItem>
393
394 <!--
395 The second step when building only 'wixlib' project references is to create the list of existing MSBuild
396 project references that do *not* build a .wixlib. These are the projects that will be skipped.
397 -->
398 <CreateItem
399 Include="@(_MSBuildProjectReferenceExistent->'%(FullPath)')"
400 Exclude="@(_ReferencedWixLibPaths->'%(MSBuildSourceProjectFile)')"
401 Condition=" '$(BuildProjectReferences)' == 'wixlib' and '$(BuildingInsideVisualStudio)' != 'true' ">
402
403 <Output TaskParameter="Include" ItemName="_ProjectReferencesToSkip" />
404 </CreateItem>
405
406 <!--
407 Finally, when building only 'wixlib' project references, the list of projects to build are naturally the
408 list of projects *not* being skipped.
409 -->
410 <CreateItem
411 Include="@(_MSBuildProjectReferenceExistent->'%(FullPath)')"
412 Exclude="@(_ProjectReferencesToSkip)"
413 Condition=" '$(BuildProjectReferences)' == 'wixlib' and '$(BuildingInsideVisualStudio)' != 'true' ">
414
415 <Output TaskParameter="Include" ItemName="_ProjectReferencesToBuild" />
416 </CreateItem>
417
418 <!-- Display a warning for all projects being skipped. -->
419 <Warning
420 Text="BuildProjectReferences set to '$(BuildProjectReferences)'. Skipping the non-Library project: %(_ProjectReferencesToSkip.Identity)"
421 Condition=" '@(_ProjectReferencesToSkip)' != '' " />
422
423 <Message
424 Importance="low"
425 Text="Project reference to build: %(_ProjectReferencesToBuild.Identity), properties: %(_ProjectReferencesToBuild.Properties)"
426 Condition=" '@(_ProjectReferencesToBuild)' != '' " />
427
428 <!--
429 Build referenced projects when building from the command line.
430
431 The $(ProjectReferenceBuildTargets) will normally be blank so that the project's default target
432 is used during a P2P reference. However if a custom build process requires that the referenced
433 project has a different target to build it can be specified.
434 -->
435 <MSBuild
436 Projects="@(_ProjectReferencesToBuild)"
437 Targets="$(ProjectReferenceBuildTargets)"
438 Properties="%(_ProjectReferencesToBuild.SetConfiguration);%(_ProjectReferencesToBuild.SetPlatform)"
439 Condition=" '@(_ProjectReferencesToBuild)' != '' ">
440
441 <Output TaskParameter="TargetOutputs" ItemName="_BuiltProjectReferencePaths" />
442 </MSBuild>
443
444 <!--
445 VC project references must build GetNativeTargetPath because neither GetTargetPath nor the return of the default build
446 target return the output for a native .vcxproj.
447 -->
448 <MSBuild
449 Projects="@(_MSBuildProjectReferenceExistent)"
450 Targets="GetNativeTargetPath"
451 Properties="%(_MSBuildProjectReferenceExistent.SetConfiguration);%(_MSBuildProjectReferenceExistent.SetPlatform)"
452 Condition=" '@(ProjectReferenceWithConfiguration)' != '' and '%(_MSBuildProjectReferenceExistent.Extension)' == '.vcxproj' ">
453
454 <Output TaskParameter="TargetOutputs" ItemName="_ResolvedProjectReferencePaths" />
455 <Output TaskParameter="TargetOutputs" ItemName="_MSBuildResolvedProjectReferencePaths" />
456 </MSBuild>
457
458 <!-- Assign the unique gathered and built project references to the resolved project
459 reference paths. -->
460 <RemoveDuplicates Inputs="@(_GatheredProjectReferencePaths);@(_BuiltProjectReferencePaths)">
461 <Output TaskParameter="Filtered" ItemName="_ResolvedProjectReferencePaths" />
462 <Output TaskParameter="Filtered" ItemName="_MSBuildResolvedProjectReferencePaths" />
463 </RemoveDuplicates>
464
465 <!-- Create list of all .wixlib project references. -->
466 <CreateItem
467 Include="@(_ResolvedProjectReferencePaths)"
468 Condition=" '%(Extension)' == '.wixlib' ">
469
470 <Output TaskParameter="Include" ItemName="WixLibProjects" />
471 </CreateItem>
472
473 <Message
474 Importance="low"
475 Text="Library from referenced projects: %(WixLibProjects.Identity)"
476 Condition=" '@(WixLibProjects)' != '' " />
477
478 </Target>
479
480 <!--
481 ================================================================================================
482 ResolveWixLibraryReferences
483
484 Resolve the library references to full paths.
485
486 [IN]
487 @(WixLibrary) - The list of .wixlib files.
488
489 [OUT]
490 @(_ResolvedWixLibraryPaths) - Item group with full paths to libraries
491 ================================================================================================
492 -->
493 <PropertyGroup>
494 <ResolveWixLibraryReferencesDependsOn></ResolveWixLibraryReferencesDependsOn>
495 </PropertyGroup>
496 <Target
497 Name="ResolveWixLibraryReferences"
498 DependsOnTargets="$(ResolveWixLibraryReferencesDependsOn)"
499 Condition=" '@(WixLibrary)' != ''">
500
501 <!--
502 The WixLibrarySearchPaths property is set to find assemblies in the following order:
503
504 (1) $(ReferencePaths) - the reference paths property, which comes from the .USER file.
505 (2) The hintpath from the referenced item itself, indicated by {HintPathFromItem}.
506 (3) Treat the reference's Include as if it were a real file name.
507 (4) Path specified by the WixExtDir property.
508 -->
509 <CreateProperty Condition=" '$(WixLibrarySearchPaths)' == '' " Value="
510 $(ReferencePaths);
511 {HintPathFromItem};
512 {RawFileName};
513 $(WixExtDir)
514 ">
515 <Output TaskParameter="Value" PropertyName="WixLibrarySearchPaths" />
516 </CreateProperty>
517
518 <ResolveWixReferences
519 WixReferences="@(WixLibrary)"
520 SearchPaths="$(WixLibrarySearchPaths)"
521 SearchFilenameExtensions=".wixlib">
522 <Output TaskParameter="ResolvedWixReferences" ItemName="_AllResolvedWixLibraryPaths" />
523 </ResolveWixReferences>
524
525 <!-- Remove duplicate library items that would cause build errors -->
526 <RemoveDuplicates Inputs="@(_AllResolvedWixLibraryPaths)">
527 <Output TaskParameter="Filtered" ItemName="_ResolvedWixLibraryPaths" />
528 </RemoveDuplicates>
529
530 </Target>
531
532 <!--
533 ==================================================================================================
534 ResolveWixExtensionReferences
535
536 Resolves WiX extension references to full paths. Any properties you use
537 to resolve paths to extensions must be defined before importing this
538 file or the extensions will be automatically resolved to $(WixExtDir).
539
540 [IN]
541 @(WixExtension) - WixExtension item group
542
543 [OUT]
544 @(_ResolvedWixExtensionPaths) - Item group with full paths to extensions
545 ==================================================================================================
546 -->
547 <PropertyGroup>
548 <ResolveWixExtensionReferencesDependsOn></ResolveWixExtensionReferencesDependsOn>
549 </PropertyGroup>
550 <Target
551 Name="ResolveWixExtensionReferences"
552 DependsOnTargets="$(ResolveWixExtensionReferencesDependsOn)"
553 Condition=" '@(WixExtension)' != ''">
554
555 <!--
556 The WixExtensionSearchPaths property is set to find assemblies in the following order:
557
558 (1) $(ReferencePaths) - the reference paths property, which comes from the .USER file.
559 (2) The hintpath from the referenced item itself, indicated by {HintPathFromItem}.
560 (3) Treat the reference's Include as if it were a real file name.
561 (4) Path specified by the WixExtDir property.
562 -->
563 <CreateProperty Condition=" '$(WixExtensionSearchPaths)' == '' " Value="
564 $(ReferencePaths);
565 {HintPathFromItem};
566 {RawFileName};
567 $(WixExtDir)
568 ">
569 <Output TaskParameter="Value" PropertyName="WixExtensionSearchPaths" />
570 </CreateProperty>
571
572 <ResolveWixReferences
573 WixReferences="@(WixExtension)"
574 SearchPaths="$(WixExtensionSearchPaths)"
575 SearchFilenameExtensions=".dll">
576 <Output TaskParameter="ResolvedWixReferences" ItemName="_AllResolvedWixExtensionPaths" />
577 </ResolveWixReferences>
578
579 <!-- Remove duplicate extension items that would cause build errors -->
580 <RemoveDuplicates Inputs="@(_AllResolvedWixExtensionPaths)">
581 <Output TaskParameter="Filtered" ItemName="_ResolvedWixExtensionPaths" />
582 </RemoveDuplicates>
583 </Target>
584
585 <!--
586 ================================================================================================
587 GetTargetPath - OVERRIDE DependsOn
588
589 This stand-alone target returns the name of the build product (i.e. MSI, MSM) that would be
590 produced if we built this project.
591 ================================================================================================
592 -->
593 <PropertyGroup>
594 <GetTargetPathDependsOn>AssignCultures</GetTargetPathDependsOn>
595 </PropertyGroup>
596
597
598 <!--
599 //////////////////////////////////////////////////////////////////////////////////////////////////
600 //////////////////////////////////////////////////////////////////////////////////////////////////
601 DoIt Targets
602 //////////////////////////////////////////////////////////////////////////////////////////////////
603 //////////////////////////////////////////////////////////////////////////////////////////////////
604 -->
605
606 <!--
607 ==================================================================================================
608 DoIt
609 ==================================================================================================
610 -->
611 <PropertyGroup>
612 <DoItDependsOn>
613 PrepareForBuild;
614 ResolveReferences;
615 BeforeCompile;
616 _TimeStampBeforeCompile;
617 Harvest;
618
619 CalculateDefineConstants;
620 GenerateCompileWithObjectPath;
621
622 AssignCultures;
623 ReadPreviousBindInputsAndBuiltOutputs;
624
625 ActuallyDoIt;
626
627 UpdateLinkFileWrites;
628 _TimeStampAfterCompile;
629 AfterCompile
630 </DoItDependsOn>
631 </PropertyGroup>
632 <Target
633 Name="DoIt"
634 DependsOnTargets="$(DoItDependsOn)" />
635
636 <Target
637 Name="ActuallyDoIt"
638
639 Inputs="@(Compile);
640 @(Content);
641 @(EmbeddedResource);
642 @(WixObject);
643 @(_ResolvedProjectReferencePaths);
644 @(_ResolvedWixLibraryPaths);
645 @(_ResolvedWixExtensionPaths);
646 @(_BindInputs);
647 $(MSBuildAllProjects)"
648 Outputs="$(IntermediateOutputPath)%(CultureGroup.OutputFolder)$(BindBuiltOutputsFile);@(_BindBuiltOutputs)"
649 Condition=" '@(Compile)' != '' and ('$(OutputType)' == 'Bundle' or '$(OutputType)' == 'Package' or '$(OutputType)' == 'PatchCreation' or '$(OutputType)' == 'Module')">
650
651
652 <PropertyGroup>
653 <PdbOutputFile>$(TargetPdbDir)%(CultureGroup.OutputFolder)$(TargetPdbName)</PdbOutputFile>
654 </PropertyGroup>
655
656 <DoIt
657 SourceFiles="@(_CompileWithObjectPath)"
658 LocalizationFiles="@(EmbeddedResource)"
659 ObjectFiles="@(CompileObjOutput);@(WixObject);@(WixLibProjects);@(_ResolvedWixLibraryPaths)"
660
661 Cultures="%(CultureGroup.Identity)"
662
663 ExtensionDirectory="$(WixExtDir)"
664 Extensions="@(_ResolvedWixExtensionPaths)"
665
666 IntermediateDirectory="$(IntermediateOutputPath)"
667
668 OutputFile="$(IntermediateOutputPath)%(CultureGroup.OutputFolder)$(TargetName)$(TargetExt)"
669 PdbOutputFile="$(PdbOutputFile)"
670
671 AdditionalOptions="$(CompilerAdditionalOptions) $(LinkerAdditionalOptions)"
672 DefineConstants="$(DefineConstants);$(SolutionDefineConstants);$(ProjectDefineConstants);$(ProjectReferenceDefineConstants)"
673 IncludeSearchPaths="$(IncludeSearchPaths)"
674 InstallerPlatform="$(InstallerPlatform)"
675 NoLogo="true"
676 Pedantic="$(Pedantic)"
677 ReferencePaths="$(ReferencePaths)"
678
679 SuppressSpecificWarnings="$(CompilerSuppressSpecificWarnings);$(LinkerSuppressSpecificWarnings)"
680 TreatSpecificWarningsAsErrors="$(CompilerTreatSpecificWarningsAsErrors)"
681
682 BindInputPaths="@(LinkerBindInputPaths)"
683 BindFiles="$(LinkerBindFiles)"
684 BindContentsFile="$(IntermediateOutputPath)%(CultureGroup.OutputFolder)$(BindContentsFile)"
685 BindOutputsFile="$(IntermediateOutputPath)%(CultureGroup.OutputFolder)$(BindOutputsFile)"
686 BindBuiltOutputsFile="$(IntermediateOutputPath)%(CultureGroup.OutputFolder)$(BindBuiltOutputsFile)"
687
688 CabinetCachePath="$(CabinetCachePath)"
689 CabinetCreationThreadCount="$(CabinetCreationThreadCount)"
690 DefaultCompressionLevel="$(DefaultCompressionLevel)"
691
692 UnreferencedSymbolsFile="$(UnreferencedSymbolsFile)"
693 WixProjectFile="$(ProjectPath)"
694 WixVariables="$(WixVariables)"
695
696 SuppressValidation="$(SuppressValidation)"
697 SuppressIces="$(SuppressIces)"
698 AdditionalCub="$(AdditionalCub)" />
699
700 <!--
701 SuppressAllWarnings="$(CompilerSuppressAllWarnings);$(LinkerSuppressAllWarnings)"
702 TreatWarningsAsErrors="$(CompilerTreatWarningsAsErrors);$(LinkerTreatWarningsAsErrors)"
703 VerboseOutput="$(CompilerVerboseOutput);$(LinkerVerboseOutput)"
704 -->
705
706</Target>
707
708
709 <!--
710 //////////////////////////////////////////////////////////////////////////////////////////////////
711 //////////////////////////////////////////////////////////////////////////////////////////////////
712 CompileAndLink Targets
713 //////////////////////////////////////////////////////////////////////////////////////////////////
714 //////////////////////////////////////////////////////////////////////////////////////////////////
715 -->
716
717 <!--
718 ==================================================================================================
719 CompileAndLink
720 ==================================================================================================
721 -->
722 <PropertyGroup>
723 <CompileAndLinkDependsOn>
724 ResolveReferences;
725 BeforeCompile;
726 _TimeStampBeforeCompile;
727 Harvest;
728 Compile;
729 Lib;
730 Link;
731 UpdateLinkFileWrites;
732 _TimeStampAfterCompile;
733 AfterCompile
734 </CompileAndLinkDependsOn>
735 </PropertyGroup>
736 <Target
737 Name="CompileAndLink"
738 DependsOnTargets="$(CompileAndLinkDependsOn)" />
739
740 <!--
741 ==================================================================================================
742 CalculateDefineConstants
743
744 Adds project references to the constants passed into the compiler.
745
746 [IN]
747 @(_ResolvedProjectReferencePaths) - paths to projects' outputs
748 $(VSProjectConfigurations) - map of project names to configurations, provided by VS when building in the IDE
749
750 [OUT]
751 $(ProjectReferenceDefineConstants) - the list of referenced project variables to be passed into the compiler
752 ==================================================================================================
753 -->
754 <PropertyGroup>
755 <CalculateDefineConstantsDependsOn>ResolveReferences</CalculateDefineConstantsDependsOn>
756 </PropertyGroup>
757 <Target
758 Name="CalculateDefineConstants"
759 DependsOnTargets="$(CalculateDefineConstantsDependsOn)"
760 Condition=" '@(_ResolvedProjectReferencePaths)' != '' ">
761
762 <PropertyGroup>
763 <ProjectDefineConstants>
764 Configuration=$(ConfigurationName);
765 OutDir=$(OutDir);
766 Platform=$(PlatformName);
767 ProjectDir=$(ProjectDir);
768 ProjectExt=$(ProjectExt);
769 ProjectFileName=$(ProjectFileName);
770 ProjectName=$(ProjectName);
771 ProjectPath=$(ProjectPath);
772 TargetDir=$(TargetDir);
773 TargetExt=$(TargetExt);
774 TargetFileName=$(TargetFileName);
775 TargetName=$(TargetName);
776 TargetPath=$(TargetPath);
777 </ProjectDefineConstants>
778 </PropertyGroup>
779
780 <PropertyGroup>
781 <SolutionDefineConstants Condition=" '$(DevEnvDir)'!='*Undefined*' ">$(SolutionDefineConstants);DevEnvDir=$(DevEnvDir)</SolutionDefineConstants>
782 <SolutionDefineConstants Condition=" '$(SolutionDir)'!='*Undefined*' ">$(SolutionDefineConstants);SolutionDir=$(SolutionDir)</SolutionDefineConstants>
783 <SolutionDefineConstants Condition=" '$(SolutionExt)'!='*Undefined*' ">$(SolutionDefineConstants);SolutionExt=$(SolutionExt)</SolutionDefineConstants>
784 <SolutionDefineConstants Condition=" '$(SolutionFileName)'!='*Undefined*' ">$(SolutionDefineConstants);SolutionFileName=$(SolutionFileName)</SolutionDefineConstants>
785 <SolutionDefineConstants Condition=" '$(SolutionName)'!='*Undefined*' ">$(SolutionDefineConstants);SolutionName=$(SolutionName)</SolutionDefineConstants>
786 <SolutionDefineConstants Condition=" '$(SolutionPath)'!='*Undefined*' ">$(SolutionDefineConstants);SolutionPath=$(SolutionPath)</SolutionDefineConstants>
787 </PropertyGroup>
788
789 <CreateProjectReferenceDefineConstants
790 ProjectReferencePaths="@(_ResolvedProjectReferencePaths)"
791 ProjectConfigurations="$(VSProjectConfigurations)">
792
793 <Output TaskParameter="DefineConstants" PropertyName="ProjectReferenceDefineConstants" />
794 </CreateProjectReferenceDefineConstants>
795 </Target>
796
797 <!--
798 ================================================================================================
799 GenerateCompileWithObjectPath
800
801 Generates metadata on the for compile output objects.
802
803 ================================================================================================
804 -->
805 <PropertyGroup>
806 <GenerateCompileWithObjectPathDependsOn></GenerateCompileWithObjectPathDependsOn>
807 </PropertyGroup>
808 <Target
809 Name="GenerateCompileWithObjectPath"
810 Condition=" '@(Compile)' != '' ">
811
812 <GenerateCompileWithObjectPath
813 Compile="@(Compile)"
814 IntermediateOutputPath="$(IntermediateOutputPath)">
815 <Output TaskParameter="CompileWithObjectPath" ItemName="_CompileWithObjectPath" />
816 </GenerateCompileWithObjectPath>
817
818 </Target>
819
820 <!--
821 ================================================================================================
822 Compile
823
824 Compiles the wxs files into wixobj files using candle.exe.
825
826 [IN]
827 @(Compile) - The list of wxs files to compile.
828 @(Content) - Files that the project uses in the installer.
829 @(WixExtension) - The list of wixlib or wix dll extensions.
830
831 [OUT]
832 @(CompileObjOutput) - The compiled .wixobj files.
833 ================================================================================================
834 -->
835 <PropertyGroup>
836 <CompileDependsOn>
837 PrepareForBuild;
838 ResolveReferences;
839 CalculateDefineConstants;
840 GenerateCompileWithObjectPath
841 </CompileDependsOn>
842 </PropertyGroup>
843 <Target
844 Name="Compile"
845 Inputs="@(Compile);
846 @(Content);
847 @(_ResolvedWixExtensionPaths);
848 @(_ResolvedProjectReferencePaths);
849 $(MSBuildAllProjects)"
850 Outputs="@(_CompileWithObjectPath -> '%(ObjectPath)%(Filename).wixobj')"
851 DependsOnTargets="$(CompileDependsOn)"
852 Condition=" '@(Compile)' != '' ">
853
854 <Candle
855 SourceFiles="@(_CompileWithObjectPath)"
856 AdditionalOptions="$(CompilerAdditionalOptions)"
857 DefineConstants="$(DefineConstants);$(SolutionDefineConstants);$(ProjectDefineConstants);$(ProjectReferenceDefineConstants)"
858 ExtensionDirectory="$(WixExtDir)"
859 Extensions="@(_ResolvedWixExtensionPaths)"
860 PreprocessToStdOut="$(PreprocessToStdOut)"
861 PreprocessToFile="$(PreprocessToFile)"
862 IncludeSearchPaths="$(IncludeSearchPaths)"
863 InstallerPlatform="$(InstallerPlatform)"
864 IntermediateDirectory="$(IntermediateOutputPath)"
865 NoLogo="$(CompilerNoLogo)"
866 OutputFile="%(_CompileWithObjectPath.ObjectPath)"
867 Pedantic="$(Pedantic)"
868 ReferencePaths="$(ReferencePaths)"
869 RunAsSeparateProcess="$(RunWixToolsOutOfProc)"
870 SuppressAllWarnings="$(CompilerSuppressAllWarnings)"
871 SuppressSpecificWarnings="$(CompilerSuppressSpecificWarnings)"
872 ToolPath="$(WixToolDir)"
873 TreatWarningsAsErrors="$(CompilerTreatWarningsAsErrors)"
874 TreatSpecificWarningsAsErrors="$(CompilerTreatSpecificWarningsAsErrors)"
875 VerboseOutput="$(CompilerVerboseOutput)">
876 </Candle>
877
878 <!-- These will be still be set even if the Compile target is up to date. -->
879 <ItemGroup>
880 <CompileObjOutput Include="@(_CompileWithObjectPath -> '%(ObjectPath)%(Filename).wixobj')" />
881 <FileWrites Include="@(CompileObjOutput)" />
882 </ItemGroup>
883 </Target>
884
885 <!--
886 ================================================================================================
887 Lib
888
889 Links the .wixobj, .wxl, .wixlib, wix extensions into a .wixlib file using lit.exe.
890
891 [IN]
892 @(CompileObjOutput) - The compiled .wixobj file.
893 @(EmbeddedResource) - The list of wxl files to use for localization.
894 @(WixObject) - The list of .wixobj files.
895 @(WixLibrary) - The list of .wixlib files.
896 @(WixExtension) - The list of wix dll extension files.
897
898 [OUT]
899 $(TargetPath) - The compiled .wixlib file.
900 ================================================================================================
901 -->
902 <PropertyGroup>
903 <LibDependsOn>
904 PrepareForBuild;
905 ResolveReferences
906 </LibDependsOn>
907 </PropertyGroup>
908 <Target
909 Name="Lib"
910 Inputs="@(CompileObjOutput);
911 @(EmbeddedResource);
912 @(WixObject);
913 @(WixLibrary);
914 @(_ResolvedWixExtensionPaths);
915 $(MSBuildAllProjects)"
916 Outputs="$(TargetPath)"
917 DependsOnTargets="$(LibDependsOn)"
918 Condition=" '$(OutputType)' == 'Library' ">
919
920 <Lit
921 ObjectFiles="@(CompileObjOutput);@(WixObject);@(WixLibProjects);@(WixLibrary)"
922 AdditionalOptions="$(LibAdditionalOptions)"
923 BindInputPaths="@(LinkerBindInputPaths)"
924 BindFiles="$(LibBindFiles)"
925 ExtensionDirectory="$(WixExtDir)"
926 Extensions="@(_ResolvedWixExtensionPaths)"
927 LocalizationFiles="@(EmbeddedResource)"
928 NoLogo="$(LibNoLogo)"
929 OutputFile="$(TargetPath)"
930 Pedantic="$(LibPedantic)"
931 ReferencePaths="$(ReferencePaths)"
932 RunAsSeparateProcess="$(RunWixToolsOutOfProc)"
933 SuppressAllWarnings="$(LibSuppressAllWarnings)"
934 SuppressIntermediateFileVersionMatching="$(LibSuppressIntermediateFileVersionMatching)"
935 SuppressSchemaValidation="$(LibSuppressSchemaValidation)"
936 SuppressSpecificWarnings="$(LibSuppressSpecificWarnings)"
937 ToolPath="$(WixToolDir)"
938 TreatWarningsAsErrors="$(LibTreatWarningsAsErrors)"
939 VerboseOutput="$(LibVerboseOutput)" />
940 </Target>
941
942 <!--
943 ================================================================================================
944 AssignCultures
945
946 Determines the final list of culture groups to build based on either the Cultures property or
947 those specified in .wxl files.
948
949 Culture groups specified in the Cultures property must be specified as a semi-colon
950 delimited list of groups, with comma-delimited cultures within a group.
951 For example:
952 <Cultures>en-US,en;en-GB,en</Cultures>
953 This will build 2 targets, outputing to en-US and en-GB sub-folders. Light will first look
954 for strings in the first culture (en-US or en-GB) then the second (en).
955
956 Cultures of .wxl files will be used when the Culture property is not set. The culture of a
957 .wxl file is determined by the Culture attribute in the WixLocalization element in the file
958
959 Sets the OutputFolder metadata on each culture group. In most cases this is the same as the
960 first culture in the culture group. When the Culture's property is unspecified and no .wxl
961 files are provided this is the same as the output directory. When the Culture's property
962 specifies a single culture group and no .wxl files are provided this is the same as the output
963 directory.
964
965 Updates the TargetPath and TargetPdbPath properties to be used in subsequent targets.
966
967 [IN]
968 @(EmbeddedResource) - The list of wxl files to use for localization.
969 $(Cultures) - The list of culture groups to build.
970
971 [OUT]
972 @(CultureGroup) - The list of culture group strings with OutputFolder metadata
973 $(TargetPath) - Property list of target link output MSIs/MSMs
974 $(TargetPdbPath) - Property list of target output pdbs
975 @(SignTargetPath) - The list of target to be signed
976
977 ================================================================================================
978 -->
979 <Target
980 Name="AssignCultures"
981 Condition=" '$(OutputType)' == 'Package' or '$(OutputType)' == 'PatchCreation' or '$(OutputType)' == 'Module' ">
982
983 <WixAssignCulture
984 Cultures="$(Cultures)"
985 Files="@(EmbeddedResource)">
986
987 <Output TaskParameter="CultureGroups" ItemName="CultureGroup" />
988 </WixAssignCulture>
989
990 <!-- Build an itemgroup of outputs -->
991 <ItemGroup>
992 <_TargetPathItems Include="$(TargetDir)%(CultureGroup.OutputFolder)$(TargetName)$(TargetExt)" />
993 <_TargetPdbPathItems Include="$(TargetPdbDir)%(CultureGroup.OutputFolder)$(TargetPdbName)" />
994 </ItemGroup>
995
996 <!-- Convert the itemgroup to a semicolon-delimited property -->
997 <PropertyGroup>
998 <TargetPath>@(_TargetPathItems)</TargetPath>
999 <TargetPdbPath>@(_TargetPdbPathItems)</TargetPdbPath>
1000 </PropertyGroup>
1001
1002 <!-- Set the sign target items, if we're signing output. -->
1003 <ItemGroup Condition=" '$(SignOutput)' == 'true' AND '$(SuppressLayout)' != 'true' ">
1004 <SignTargetPath Include="@(_TargetPathItems)" />
1005 </ItemGroup>
1006 </Target>
1007
1008 <!--
1009 ================================================================================================
1010 ReadPreviousBindInputsAndBuiltOutputs
1011
1012 Reads a previous build's Bind contents and built outputs file into @(_BindInputs) and
1013 @(_BindBuiltOutputs) respectively.
1014
1015 Note: Only the *built* outputs are used because using files copied to output folder
1016 can cause perpetual incremental build.
1017
1018 Imagine the case where you have: Msi.wixproj -> Lib.wixproj -> Exe.csproj. The
1019 Exe.csproj cannot be both an input to Lib.wixproj and an output of Msi.wixproj
1020 (as an uncompressed file) because the Lib.wixproj will always newer than the
1021 Exe.csproj.
1022
1023 [IN]
1024
1025 [OUT]
1026 @(_BindInputs) - the content files required to bind (i.e. the Binary/@SourceFile and File/@Source files).
1027 @(_BindBuiltOutputs) - the previously built .msi, .msm, .pcp, .exe .wixpdb, .cabs, etc.
1028 Does not include content copied to output folder.
1029 ================================================================================================
1030 -->
1031 <Target
1032 Name="ReadPreviousBindInputsAndBuiltOutputs"
1033 Condition=" '$(OutputType)' == 'Bundle' or '$(OutputType)' == 'Package' or '$(OutputType)' == 'PatchCreation' or '$(OutputType)' == 'Module' ">
1034
1035 <ReadLinesFromFile File="$(IntermediateOutputPath)%(CultureGroup.OutputFolder)$(BindContentsFile)">
1036 <Output TaskParameter="Lines" ItemName="_BindInputs" />
1037 </ReadLinesFromFile>
1038
1039 <ReadLinesFromFile File="$(IntermediateOutputPath)%(CultureGroup.OutputFolder)$(BindBuiltOutputsFile)">
1040 <Output TaskParameter="Lines" ItemName="_BindBuiltOutputs" />
1041 </ReadLinesFromFile>
1042
1043 <Message Importance="low" Text="Previous bind inputs: @(_BindInputs)" />
1044 <Message Importance="low" Text="Previous bind outputs: @(_BindBuiltOutputs)" />
1045 </Target>
1046
1047 <!--
1048 ================================================================================================
1049 Link
1050
1051 Links the .wixobj, .wxl, .wixlib, wix extensions into an .msi or .msm file using light.exe,
1052 once per culture group. All WXL files are passed into light and the culture switch determines
1053 which are used
1054
1055 [IN]
1056 @(CompileObjOutput) - The compiled .wixobj file.
1057 @(CultureGroup) - The cultures to build
1058 @(EmbeddedResource) - The list of wxl files to use for localization.
1059 @(WixObject) - The list of .wixobj files.
1060 @(WixLibrary) - The list of .wixlib files.
1061 @(WixExtension) - The list of wix dll extension files.
1062
1063 [OUT]
1064 $(TargetDir)\%(Culture)\$(TargetName)$(TargetExt) - The compiled .msi, .msm, or .exe files.
1065 ================================================================================================
1066 -->
1067 <PropertyGroup>
1068 <LinkDependsOn>
1069 PrepareForBuild;
1070 ResolveReferences;
1071 AssignCultures;
1072 ReadPreviousBindInputsAndBuiltOutputs;
1073 </LinkDependsOn>
1074 </PropertyGroup>
1075 <Target
1076 Name="Link"
1077 Inputs="@(CompileObjOutput);
1078 @(EmbeddedResource);
1079 @(WixObject);
1080 @(_ResolvedProjectReferencePaths);
1081 @(_ResolvedWixLibraryPaths);
1082 @(_ResolvedWixExtensionPaths);
1083 $(MSBuildAllProjects);
1084 @(_BindInputs)"
1085 Outputs="$(IntermediateOutputPath)%(CultureGroup.OutputFolder)$(BindBuiltOutputsFile);@(_BindBuiltOutputs)"
1086 DependsOnTargets="$(LinkDependsOn)"
1087 Condition=" '$(OutputType)' == 'Bundle' or '$(OutputType)' == 'Package' or '$(OutputType)' == 'PatchCreation' or '$(OutputType)' == 'Module' ">
1088
1089 <PropertyGroup>
1090 <PdbOutputFile>$(TargetPdbDir)%(CultureGroup.OutputFolder)$(TargetPdbName)</PdbOutputFile>
1091 </PropertyGroup>
1092
1093 <!-- Call light using the culture subdirectory for output -->
1094 <Light
1095 ObjectFiles="@(CompileObjOutput);@(WixObject);@(WixLibProjects);@(_ResolvedWixLibraryPaths)"
1096 AdditionalOptions="$(LinkerAdditionalOptions)"
1097 AllowIdenticalRows="$(AllowIdenticalRows)"
1098 AllowUnresolvedReferences="$(AllowUnresolvedReferences)"
1099 AdditionalCub="$(AdditionalCub)"
1100 BackwardsCompatibleGuidGeneration="$(BackwardsCompatibleGuidGeneration)"
1101 BindInputPaths="@(LinkerBindInputPaths)"
1102 BindFiles="$(LinkerBindFiles)"
1103 BindContentsFile="$(IntermediateOutputPath)%(CultureGroup.OutputFolder)$(BindContentsFile)"
1104 BindOutputsFile="$(IntermediateOutputPath)%(CultureGroup.OutputFolder)$(BindOutputsFile)"
1105 BindBuiltOutputsFile="$(IntermediateOutputPath)%(CultureGroup.OutputFolder)$(BindBuiltOutputsFile)"
1106 CabinetCachePath="$(CabinetCachePath)"
1107 CabinetCreationThreadCount="$(CabinetCreationThreadCount)"
1108 Cultures="%(CultureGroup.Identity)"
1109 CustomBinder="$(CustomBinder)"
1110 DefaultCompressionLevel="$(DefaultCompressionLevel)"
1111 DropUnrealTables="$(DropUnrealTables)"
1112 ExactAssemblyVersions="$(ExactAssemblyVersions)"
1113 ExtensionDirectory="$(WixExtDir)"
1114 Extensions="@(_ResolvedWixExtensionPaths)"
1115 Ices="$(Ices)"
1116 LeaveTemporaryFiles="$(LeaveTemporaryFiles)"
1117 LocalizationFiles="@(EmbeddedResource)"
1118 NoLogo="$(LinkerNoLogo)"
1119 OutputAsXml="$(OutputAsXml)"
1120 OutputFile="$(IntermediateOutputPath)%(CultureGroup.OutputFolder)$(TargetName)$(TargetExt)"
1121 PdbOutputFile="$(PdbOutputFile)"
1122 Pedantic="$(LinkerPedantic)"
1123 ReferencePaths="$(ReferencePaths)"
1124 ReuseCabinetCache="$(ReuseCabinetCache)"
1125 RunAsSeparateProcess="$(RunWixToolsOutOfProc)"
1126 SuppressAclReset="$(SuppressAclReset)"
1127 SuppressAllWarnings="$(LinkerSuppressAllWarnings)"
1128 SuppressAssemblies="$(SuppressAssemblies)"
1129 SuppressDefaultAdminSequenceActions="$(SuppressDefaultAdminSequenceActions)"
1130 SuppressDefaultAdvSequenceActions="$(SuppressDefaultAdvSequenceActions)"
1131 SuppressDefaultUISequenceActions="$(SuppressDefaultUISequenceActions)"
1132 SuppressFileHashAndInfo="$(SuppressFileHashAndInfo)"
1133 SuppressFiles="$(SuppressFiles)"
1134 SuppressIntermediateFileVersionMatching="$(LinkerSuppressIntermediateFileVersionMatching)"
1135 SuppressIces="$(SuppressIces)"
1136 SuppressLayout="$(SuppressLayout)"
1137 SuppressLocalization="$(SuppressLocalization)"
1138 SuppressMsiAssemblyTableProcessing="$(SuppressMsiAssemblyTableProcessing)"
1139 SuppressPdbOutput="$(SuppressPdbOutput)"
1140 SuppressSchemaValidation="$(LinkerSuppressSchemaValidation)"
1141 SuppressValidation="$(SuppressValidation)"
1142 SuppressSpecificWarnings="$(LinkerSuppressSpecificWarnings)"
1143 SuppressTagSectionIdAttributeOnTuples="$(SuppressTagSectionIdAttributeOnTuples)"
1144 ToolPath="$(WixToolDir)"
1145 TreatWarningsAsErrors="$(LinkerTreatWarningsAsErrors)"
1146 UnreferencedSymbolsFile="$(UnreferencedSymbolsFile)"
1147 VerboseOutput="$(LinkerVerboseOutput)"
1148 WixProjectFile="$(ProjectPath)"
1149 WixVariables="$(WixVariables)" />
1150 </Target>
1151
1152 <!--
1153 ================================================================================================
1154 UpdateLinkFileWrites
1155
1156 Reads the bind outputs file(s) output generated during Link to correctly set the @(FileWrites)
1157 item. Most targets have it easy because they can do a static mapping from inputs to the outputs.
1158 However, the Link target outputs are determined after a rather complex calculation we call
1159 linking and binding!
1160
1161 This target runs independently after Link to ensure that @(FileWrites) is updated even if the
1162 "Light" task fails.
1163
1164 [IN]
1165 Path to bind outputs file(s).
1166
1167 [OUT]
1168 @(FileWrites) updated with outputs from bind.
1169 ================================================================================================
1170 -->
1171 <Target
1172 Name="UpdateLinkFileWrites"
1173 Condition=" '$(OutputType)' == 'Bundle' or '$(OutputType)' == 'Package' or '$(OutputType)' == 'PatchCreation' or '$(OutputType)' == 'Module' ">
1174
1175 <ReadLinesFromFile File="$(IntermediateOutputPath)%(CultureGroup.OutputFolder)$(BindOutputsFile)">
1176 <Output TaskParameter="Lines" ItemName="FileWrites"/>
1177 </ReadLinesFromFile>
1178
1179 <ItemGroup>
1180 <FileWrites Include="$(IntermediateOutputPath)%(CultureGroup.OutputFolder)$(BindContentsFile)" />
1181 <FileWrites Include="$(IntermediateOutputPath)%(CultureGroup.OutputFolder)$(BindOutputsFile)" />
1182 <FileWrites Include="$(IntermediateOutputPath)%(CultureGroup.OutputFolder)$(BindBuiltOutputsFile)" />
1183 </ItemGroup>
1184
1185 <Message Importance="low" Text="Build files after link: @(FileWrites)" />
1186 </Target>
1187
1188 <!--
1189 //////////////////////////////////////////////////////////////////////////////////////////////////
1190 //////////////////////////////////////////////////////////////////////////////////////////////////
1191 AllProjectOutputGroups Section
1192 //////////////////////////////////////////////////////////////////////////////////////////////////
1193 //////////////////////////////////////////////////////////////////////////////////////////////////
1194 -->
1195
1196 <!--
1197 ==================================================================================================
1198 AllProjectOutputGroups - OVERRIDE Target
1199
1200 ==================================================================================================
1201 -->
1202 <Target
1203 Name="AllProjectOutputGroups"
1204 DependsOnTargets="
1205 BuiltProjectOutputGroup;
1206 DebugSymbolsProjectOutputGroup;
1207 SourceFilesProjectOutputGroup;
1208 ContentFilesProjectOutputGroup" />
1209
1210 <!--
1211 This is the key output for the BuiltProjectOutputGroup and is meant to be read directly from the IDE.
1212 Reading an item is faster than invoking a target.
1213 -->
1214 <ItemGroup>
1215 <BuiltProjectOutputGroupKeyOutput Include="$(TargetPath)">
1216 <IsKeyOutput>true</IsKeyOutput>
1217 <FinalOutputPath>$(TargetPath)</FinalOutputPath>
1218 <TargetPath>$(TargetFileName)</TargetPath>
1219 </BuiltProjectOutputGroupKeyOutput>
1220 </ItemGroup>
1221
1222 <!--
1223 ==================================================================================================
1224 BuiltProjectOutputGroup - OVERRIDE Target
1225 ==================================================================================================
1226 -->
1227 <PropertyGroup>
1228 <BuiltProjectOutputGroupDependsOn>PrepareForBuild;AssignCultures</BuiltProjectOutputGroupDependsOn>
1229 </PropertyGroup>
1230 <Target
1231 Name="BuiltProjectOutputGroup"
1232 Outputs="@(BuiltProjectOutputGroupOutput)"
1233 DependsOnTargets="$(BuiltProjectOutputGroupDependsOn)">
1234
1235 <!-- Don't add BuiltProjectOutputGroupKeyOutput - to avoid duplicates, we only want to get the updated list of TargetPaths from the TargetPath property below -->
1236
1237 <!-- Try to read the outputs from the bind outputs text file since that's the output list straight from linker. -->
1238 <ReadLinesFromFile File="$(IntermediateOutputPath)%(CultureGroup.OutputFolder)$(BindOutputsFile)">
1239 <Output TaskParameter="Lines" ItemName="_BuiltProjectOutputGroupOutputIntermediate"/>
1240 </ReadLinesFromFile>
1241
1242 <!-- If we didn't get anything from the bind outputs text file, default to the target path. -->
1243 <ItemGroup Condition=" '@(_BuiltProjectOutputGroupOutputIntermediate)'=='' ">
1244 <_BuiltProjectOutputGroupOutputIntermediate Include="$(TargetPath)" />
1245 </ItemGroup>
1246
1247 <!-- Convert intermediate items into final items; this way we can get the full path for each item -->
1248 <ItemGroup>
1249 <BuiltProjectOutputGroupOutput Include="@(_BuiltProjectOutputGroupOutputIntermediate->'%(FullPath)')">
1250 <!-- For compatibility with 2.0 -->
1251 <OriginalItemSpec Condition="'%(_BuiltProjectOutputGroupOutputIntermediate.OriginalItemSpec)' == ''">%(_BuiltProjectOutputGroupOutputIntermediate.FullPath)</OriginalItemSpec>
1252 </BuiltProjectOutputGroupOutput>
1253 </ItemGroup>
1254 </Target>
1255
1256 <!--
1257 ==================================================================================================
1258 DebugSymbolsProjectOutputGroup
1259
1260 Populates the Debug Symbols project output group.
1261 ==================================================================================================
1262 -->
1263 <PropertyGroup>
1264 <DebugSymbolsProjectOutputGroupDependsOn>AssignCultures</DebugSymbolsProjectOutputGroupDependsOn>
1265 </PropertyGroup>
1266 <Target
1267 Name="DebugSymbolsProjectOutputGroup"
1268 Outputs="@(DebugSymbolsProjectOutputGroupOutput)"
1269 DependsOnTargets="$(DebugSymbolsProjectOutputGroupDependsOn)">
1270
1271 <!-- Include build output pdb(s). Different than predefined itemgroup since AssignCultures target may change -->
1272 <ItemGroup>
1273 <DebugSymbolsProjectOutputGroupOutput Include="$(TargetPdbPath)" Condition=" '$(SuppressPdbOutput)' != 'true' "/>
1274 </ItemGroup>
1275 </Target>
1276
1277
1278 <!--
1279 ==================================================================================================
1280 CopyFilesToOutputDirectory - OVERRIDE Target
1281
1282 Copy all build outputs, satellites and other necessary files to the final directory.
1283 ============================================================
1284 -->
1285 <Target
1286 Name="CopyFilesToOutputDirectory">
1287
1288 <PropertyGroup>
1289 <!-- By default we're using hard links to copy to the output directory, disabling this could slow the build significantly -->
1290 <CreateHardLinksForCopyFilesToOutputDirectoryIfPossible Condition=" '$(CreateHardLinksForCopyFilesToOutputDirectoryIfPossible)' == '' ">true</CreateHardLinksForCopyFilesToOutputDirectoryIfPossible>
1291 </PropertyGroup>
1292
1293 <PropertyGroup>
1294 <CopyBuildOutputToOutputDirectory Condition="'$(CopyBuildOutputToOutputDirectory)'==''">true</CopyBuildOutputToOutputDirectory>
1295 <CopyOutputSymbolsToOutputDirectory Condition="'$(CopyOutputSymbolsToOutputDirectory)'==''">true</CopyOutputSymbolsToOutputDirectory>
1296 <FullIntermediateOutputPath>$([System.IO.Path]::GetFullPath($(IntermediateOutputPath)))</FullIntermediateOutputPath>
1297 </PropertyGroup>
1298
1299 <!-- Copy the bound files. -->
1300 <ReadLinesFromFile File="$(IntermediateOutputPath)%(CultureGroup.OutputFolder)$(BindOutputsFile)">
1301 <Output TaskParameter="Lines" ItemName="_FullPathToCopy"/>
1302 </ReadLinesFromFile>
1303
1304 <ItemGroup>
1305 <_FullPathToCopy Include="$(TargetPath)" Condition=" '@(_FullPathToCopy)'=='' " />
1306 <_RelativePath Include="$([MSBuild]::MakeRelative($(FullIntermediateOutputPath), %(_FullPathToCopy.Identity)))" />
1307 </ItemGroup>
1308
1309 <Copy
1310 SourceFiles="@(_RelativePath->'$(IntermediateOutputPath)%(Identity)')"
1311 DestinationFiles="@(_RelativePath->'$(OutDir)%(Identity)')"
1312 SkipUnchangedFiles="$(SkipCopyUnchangedFiles)"
1313 OverwriteReadOnlyFiles="$(OverwriteReadOnlyFiles)"
1314 Retries="$(CopyRetryCount)"
1315 RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)"
1316 UseHardlinksIfPossible="$(CreateHardLinksForCopyFilesToOutputDirectoryIfPossible)"
1317 Condition="'$(CopyBuildOutputToOutputDirectory)' == 'true' and '$(SkipCopyBuildProduct)' != 'true'"
1318 >
1319
1320 <Output TaskParameter="DestinationFiles" ItemName="MainAssembly"/>
1321 <Output TaskParameter="DestinationFiles" ItemName="FileWrites"/>
1322
1323 </Copy>
1324
1325 <Message Importance="High" Text="$(MSBuildProjectName) -&gt; $(TargetPath)" Condition="'$(CopyBuildOutputToOutputDirectory)' == 'true' and '$(SkipCopyBuildProduct)'!='true'" />
1326 <!--<Message Importance="High" Text="$(MSBuildProjectName) -&gt; @(MainAssembly->'%(FullPath)')" Condition="'$(CopyBuildOutputToOutputDirectory)' == 'true' and '$(SkipCopyBuildProduct)'!='true'" />-->
1327
1328 <!-- Copy the debug information file (.pdb), if any
1329 <Copy
1330 SourceFiles="@(_DebugSymbolsIntermediatePath)"
1331 DestinationFiles="@(_DebugSymbolsOutputPath)"
1332 SkipUnchangedFiles="$(SkipCopyUnchangedFiles)"
1333 OverwriteReadOnlyFiles="$(OverwriteReadOnlyFiles)"
1334 Retries="$(CopyRetryCount)"
1335 RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)"
1336 UseHardlinksIfPossible="$(CreateHardLinksForCopyFilesToOutputDirectoryIfPossible)"
1337 Condition="'$(_DebugSymbolsProduced)'=='true' and '$(SkipCopyingSymbolsToOutputDirectory)' != 'true' and '$(CopyOutputSymbolsToOutputDirectory)'=='true'">
1338
1339 <Output TaskParameter="DestinationFiles" ItemName="FileWrites"/>
1340
1341 </Copy>
1342 -->
1343 </Target>
1344
1345
1346 <Import Project="$(WixHarvestTargetsPath)" Condition=" '$(WixHarvestTargetsPath)' != '' and Exists('$(WixHarvestTargetsPath)')" />
1347 <Import Project="$(WixSigningTargetsPath)" Condition=" '$(WixSigningTargetsPath)' != '' and Exists('$(WixSigningTargetsPath)')" />
1348 <Import Project="$(LuxTargetsPath)" Condition=" '$(LuxTargetsPath)' != '' and Exists('$(LuxTargetsPath)')" />
1349
1350 <!-- Extension point: Define CustomAfterWixTargets to a .targets file that you want to include after this file. -->
1351 <Import Project="$(CustomAfterWixTargets)" Condition=" '$(CustomAfterWixTargets)' != '' and Exists('$(CustomAfterWixTargets)')" />
1352
1353</Project>
diff --git a/src/WixToolset.Core/AppCommon.cs b/src/WixToolset.Core/AppCommon.cs
new file mode 100644
index 00000000..30ec7771
--- /dev/null
+++ b/src/WixToolset.Core/AppCommon.cs
@@ -0,0 +1,145 @@
1// Copyright (c) .NET Foundation and 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.Collections.Specialized;
7 using System.Configuration;
8 using System.Globalization;
9 using System.IO;
10 using System.Reflection;
11 using System.Text;
12 using System.Threading;
13 using WixToolset.Data;
14
15 /// <summary>
16 /// Common utilities for Wix applications.
17 /// </summary>
18 public static class AppCommon
19 {
20 /// <summary>
21 /// Read the configuration file (*.exe.config).
22 /// </summary>
23 /// <param name="extensions">Extensions to load.</param>
24 public static void ReadConfiguration(StringCollection extensions)
25 {
26 if (null == extensions)
27 {
28 throw new ArgumentNullException("extensions");
29 }
30
31#if REDO_IN_NETCORE
32 // Don't use the default AppSettings reader because
33 // the tool may be called from within another process.
34 // Instead, read the .exe.config file from the tool location.
35 string toolPath = (new System.Uri(Assembly.GetCallingAssembly().CodeBase)).LocalPath;
36 Configuration config = ConfigurationManager.OpenExeConfiguration(toolPath);
37 if (config.HasFile)
38 {
39 KeyValueConfigurationElement configVal = config.AppSettings.Settings["extensions"];
40 if (configVal != null)
41 {
42 string extensionTypes = configVal.Value;
43 foreach (string extensionType in extensionTypes.Split(";".ToCharArray()))
44 {
45 extensions.Add(extensionType);
46 }
47 }
48 }
49#endif
50 }
51
52 /// <summary>
53 /// Gets a unique temporary location or uses the provided temporary location.
54 /// </summary>
55 /// <param name="tempLocation">Optional temporary location to use.</param>
56 /// <returns>Temporary location.</returns>
57 public static string GetTempLocation(string tempLocation = null)
58 {
59 if (String.IsNullOrEmpty(tempLocation))
60 {
61 tempLocation = Environment.GetEnvironmentVariable("WIX_TEMP") ?? Path.GetTempPath();
62
63 do
64 {
65 tempLocation = Path.Combine(tempLocation, DateTime.Now.ToString("wixyyMMddTHHmmssffff"));
66 } while (Directory.Exists(tempLocation));
67 }
68
69 return tempLocation;
70 }
71
72 /// <summary>
73 /// Delete a directory with retries and best-effort cleanup.
74 /// </summary>
75 /// <param name="path">The directory to delete.</param>
76 /// <param name="messageHandler">The message handler.</param>
77 /// <returns>True if all files were deleted, false otherwise.</returns>
78 public static bool DeleteDirectory(string path, IMessageHandler messageHandler)
79 {
80 return Common.DeleteTempFiles(path, messageHandler);
81 }
82
83 /// <summary>
84 /// Prepares the console for localization.
85 /// </summary>
86 public static void PrepareConsoleForLocalization()
87 {
88 Thread.CurrentThread.CurrentUICulture = CultureInfo.CurrentUICulture.GetConsoleFallbackUICulture();
89 if ((Console.OutputEncoding.CodePage != Encoding.UTF8.CodePage) &&
90 (Console.OutputEncoding.CodePage != Thread.CurrentThread.CurrentUICulture.TextInfo.OEMCodePage) &&
91 (Console.OutputEncoding.CodePage != Thread.CurrentThread.CurrentUICulture.TextInfo.ANSICodePage))
92 {
93 Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-US");
94 }
95 }
96
97 /// <summary>
98 /// Handler for display message events.
99 /// </summary>
100 /// <param name="sender">Sender of message.</param>
101 /// <param name="e">Event arguments containing message to display.</param>
102 public static void ConsoleDisplayMessage(object sender, DisplayEventArgs e)
103 {
104 switch (e.Level)
105 {
106 case MessageLevel.Warning:
107 case MessageLevel.Error:
108 Console.Error.WriteLine(e.Message);
109 break;
110 default:
111 Console.WriteLine(e.Message);
112 break;
113 }
114 }
115
116 /// <summary>
117 /// Creates and returns the string for CreatingApplication field (MSI Summary Information Stream).
118 /// </summary>
119 /// <remarks>It reads the AssemblyProductAttribute and AssemblyVersionAttribute of executing assembly
120 /// and builds the CreatingApplication string of the form "[ProductName] ([ProductVersion])".</remarks>
121 /// <returns>Returns value for PID_APPNAME."</returns>
122 public static string GetCreatingApplicationString()
123 {
124 Assembly assembly = Assembly.GetExecutingAssembly();
125 return WixDistribution.ReplacePlaceholders("[AssemblyProduct] ([FileVersion])", assembly);
126 }
127
128 /// <summary>
129 /// Displays help message header on Console for caller tool.
130 /// </summary>
131 public static void DisplayToolHeader()
132 {
133 Assembly assembly = Assembly.GetCallingAssembly();
134 Console.WriteLine(WixDistribution.ReplacePlaceholders(WixDistributionSpecificStrings.ToolsetHelpHeader, assembly));
135 }
136
137 /// <summary>
138 /// Displays help message header on Console for caller tool.
139 /// </summary>
140 public static void DisplayToolFooter()
141 {
142 Console.Write(WixDistribution.ReplacePlaceholders(WixDistributionSpecificStrings.ToolsetHelpFooter, null));
143 }
144 }
145}
diff --git a/src/WixToolset.Core/AssemblyInfo.cs b/src/WixToolset.Core/AssemblyInfo.cs
new file mode 100644
index 00000000..b3740b2a
--- /dev/null
+++ b/src/WixToolset.Core/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(true)]
9[assembly: ComVisible(false)]
diff --git a/src/WixToolset.Core/Bind/BindBundleCommand.cs b/src/WixToolset.Core/Bind/BindBundleCommand.cs
new file mode 100644
index 00000000..7ea0c830
--- /dev/null
+++ b/src/WixToolset.Core/Bind/BindBundleCommand.cs
@@ -0,0 +1,905 @@
1// Copyright (c) .NET Foundation and 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.Bind
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.Reflection;
12 using WixToolset.Bind.Bundles;
13 using WixToolset.Data;
14 using WixToolset.Data.Rows;
15 using WixToolset.Extensibility;
16
17 /// <summary>
18 /// Binds a this.bundle.
19 /// </summary>
20 internal class BindBundleCommand : ICommand
21 {
22 public CompressionLevel DefaultCompressionLevel { private get; set; }
23
24 public IEnumerable<IBinderExtension> Extensions { private get; set; }
25
26 public BinderFileManagerCore FileManagerCore { private get; set; }
27
28 public IEnumerable<IBinderFileManager> FileManagers { private get; set; }
29
30 public Output Output { private get; set; }
31
32 public string OutputPath { private get; set; }
33
34 public string PdbFile { private get; set; }
35
36 public TableDefinitionCollection TableDefinitions { private get; set; }
37
38 public string TempFilesLocation { private get; set; }
39
40 public WixVariableResolver WixVariableResolver { private get; set; }
41
42 public IEnumerable<FileTransfer> FileTransfers { get; private set; }
43
44 public IEnumerable<string> ContentFilePaths { get; private set; }
45
46 public void Execute()
47 {
48 this.FileTransfers = Enumerable.Empty<FileTransfer>();
49 this.ContentFilePaths = Enumerable.Empty<string>();
50
51 // First look for data we expect to find... Chain, WixGroups, etc.
52
53 // We shouldn't really get past the linker phase if there are
54 // no group items... that means that there's no UX, no Chain,
55 // *and* no Containers!
56 Table chainPackageTable = this.GetRequiredTable("WixBundlePackage");
57
58 Table wixGroupTable = this.GetRequiredTable("WixGroup");
59
60 // Ensure there is one and only one row in the WixBundle table.
61 // The compiler and linker behavior should have colluded to get
62 // this behavior.
63 WixBundleRow bundleRow = (WixBundleRow)this.GetSingleRowTable("WixBundle");
64
65 bundleRow.PerMachine = true; // default to per-machine but the first-per user package wil flip the bundle per-user.
66
67 // Ensure there is one and only one row in the WixBootstrapperApplication table.
68 // The compiler and linker behavior should have colluded to get
69 // this behavior.
70 Row baRow = this.GetSingleRowTable("WixBootstrapperApplication");
71
72 // Ensure there is one and only one row in the WixChain table.
73 // The compiler and linker behavior should have colluded to get
74 // this behavior.
75 WixChainRow chainRow = (WixChainRow)this.GetSingleRowTable("WixChain");
76
77 if (Messaging.Instance.EncounteredError)
78 {
79 return;
80 }
81
82 // Localize fields, resolve wix variables, and resolve file paths.
83 ExtractEmbeddedFiles filesWithEmbeddedFiles = new ExtractEmbeddedFiles();
84
85 IEnumerable<DelayedField> delayedFields;
86 {
87 ResolveFieldsCommand command = new ResolveFieldsCommand();
88 command.Tables = this.Output.Tables;
89 command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles;
90 command.FileManagerCore = this.FileManagerCore;
91 command.FileManagers = this.FileManagers;
92 command.SupportDelayedResolution = true;
93 command.TempFilesLocation = this.TempFilesLocation;
94 command.WixVariableResolver = this.WixVariableResolver;
95 command.Execute();
96
97 delayedFields = command.DelayedFields;
98 }
99
100 if (Messaging.Instance.EncounteredError)
101 {
102 return;
103 }
104
105 // If there are any fields to resolve later, create the cache to populate during bind.
106 IDictionary<string, string> variableCache = null;
107 if (delayedFields.Any())
108 {
109 variableCache = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
110 }
111
112 // TODO: Although the WixSearch tables are defined in the Util extension,
113 // the Bundle Binder has to know all about them. We hope to revisit all
114 // of this in the 4.0 timeframe.
115 IEnumerable<WixSearchInfo> orderedSearches = this.OrderSearches();
116
117 // Extract files that come from cabinet files (this does not extract files from merge modules).
118 {
119 ExtractEmbeddedFilesCommand extractEmbeddedFilesCommand = new ExtractEmbeddedFilesCommand();
120 extractEmbeddedFilesCommand.FilesWithEmbeddedFiles = filesWithEmbeddedFiles;
121 extractEmbeddedFilesCommand.Execute();
122 }
123
124 // Get the explicit payloads.
125 RowDictionary<WixBundlePayloadRow> payloads = new RowDictionary<WixBundlePayloadRow>(this.Output.Tables["WixBundlePayload"]);
126
127 // Update explicitly authored payloads with their parent package and container (as appropriate)
128 // to make it easier to gather the payloads later.
129 foreach (WixGroupRow row in wixGroupTable.RowsAs<WixGroupRow>())
130 {
131 if (ComplexReferenceChildType.Payload == row.ChildType)
132 {
133 WixBundlePayloadRow payload = payloads.Get(row.ChildId);
134
135 if (ComplexReferenceParentType.Package == row.ParentType)
136 {
137 Debug.Assert(String.IsNullOrEmpty(payload.Package));
138 payload.Package = row.ParentId;
139 }
140 else if (ComplexReferenceParentType.Container == row.ParentType)
141 {
142 Debug.Assert(String.IsNullOrEmpty(payload.Container));
143 payload.Container = row.ParentId;
144 }
145 else if (ComplexReferenceParentType.Layout == row.ParentType)
146 {
147 payload.LayoutOnly = true;
148 }
149 }
150 }
151
152 List<FileTransfer> fileTransfers = new List<FileTransfer>();
153 string layoutDirectory = Path.GetDirectoryName(this.OutputPath);
154
155 // Process the explicitly authored payloads.
156 ISet<string> processedPayloads;
157 {
158 ProcessPayloadsCommand command = new ProcessPayloadsCommand();
159 command.Payloads = payloads.Values;
160 command.DefaultPackaging = bundleRow.DefaultPackagingType;
161 command.LayoutDirectory = layoutDirectory;
162 command.Execute();
163
164 fileTransfers.AddRange(command.FileTransfers);
165
166 processedPayloads = new HashSet<string>(payloads.Keys);
167 }
168
169 IDictionary<string, PackageFacade> facades;
170 {
171 GetPackageFacadesCommand command = new GetPackageFacadesCommand();
172 command.PackageTable = chainPackageTable;
173 command.ExePackageTable = this.Output.Tables["WixBundleExePackage"];
174 command.MsiPackageTable = this.Output.Tables["WixBundleMsiPackage"];
175 command.MspPackageTable = this.Output.Tables["WixBundleMspPackage"];
176 command.MsuPackageTable = this.Output.Tables["WixBundleMsuPackage"];
177 command.Execute();
178
179 facades = command.PackageFacades;
180 }
181
182 // Process each package facade. Note this is likely to add payloads and other rows to tables so
183 // note that any indexes created above may be out of date now.
184 foreach (PackageFacade facade in facades.Values)
185 {
186 switch (facade.Package.Type)
187 {
188 case WixBundlePackageType.Exe:
189 {
190 ProcessExePackageCommand command = new ProcessExePackageCommand();
191 command.AuthoredPayloads = payloads;
192 command.Facade = facade;
193 command.Execute();
194
195 // ? variableCache.Add(String.Concat("packageManufacturer.", facade.Package.WixChainItemId), facade.ExePackage.Manufacturer);
196 }
197 break;
198
199 case WixBundlePackageType.Msi:
200 {
201 ProcessMsiPackageCommand command = new ProcessMsiPackageCommand();
202 command.AuthoredPayloads = payloads;
203 command.Facade = facade;
204 command.FileManager = this.FileManagers.First();
205 command.MsiFeatureTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleMsiFeature"]);
206 command.MsiPropertyTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleMsiProperty"]);
207 command.PayloadTable = this.Output.Tables["WixBundlePayload"];
208 command.RelatedPackageTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleRelatedPackage"]);
209 command.Execute();
210
211 if (null != variableCache)
212 {
213 variableCache.Add(String.Concat("packageLanguage.", facade.Package.WixChainItemId), facade.MsiPackage.ProductLanguage.ToString());
214
215 if (null != facade.MsiPackage.Manufacturer)
216 {
217 variableCache.Add(String.Concat("packageManufacturer.", facade.Package.WixChainItemId), facade.MsiPackage.Manufacturer);
218 }
219 }
220
221 }
222 break;
223
224 case WixBundlePackageType.Msp:
225 {
226 ProcessMspPackageCommand command = new ProcessMspPackageCommand();
227 command.AuthoredPayloads = payloads;
228 command.Facade = facade;
229 command.WixBundlePatchTargetCodeTable = this.Output.EnsureTable(this.TableDefinitions["WixBundlePatchTargetCode"]);
230 command.Execute();
231 }
232 break;
233
234 case WixBundlePackageType.Msu:
235 {
236 ProcessMsuPackageCommand command = new ProcessMsuPackageCommand();
237 command.Facade = facade;
238 command.Execute();
239 }
240 break;
241 }
242
243 if (null != variableCache)
244 {
245 BindBundleCommand.PopulatePackageVariableCache(facade.Package, variableCache);
246 }
247 }
248
249 // Reindex the payloads now that all the payloads (minus the manifest payloads that will be created later)
250 // are present.
251 payloads = new RowDictionary<WixBundlePayloadRow>(this.Output.Tables["WixBundlePayload"]);
252
253 // Process the payloads that were added by processing the packages.
254 {
255 ProcessPayloadsCommand command = new ProcessPayloadsCommand();
256 command.Payloads = payloads.Values.Where(r => !processedPayloads.Contains(r.Id)).ToList();
257 command.DefaultPackaging = bundleRow.DefaultPackagingType;
258 command.LayoutDirectory = layoutDirectory;
259 command.Execute();
260
261 fileTransfers.AddRange(command.FileTransfers);
262
263 processedPayloads = null;
264 }
265
266 // Set the package metadata from the payloads now that we have the complete payload information.
267 ILookup<string, WixBundlePayloadRow> payloadsByPackage = payloads.Values.ToLookup(p => p.Package);
268
269 {
270 foreach (PackageFacade facade in facades.Values)
271 {
272 facade.Package.Size = 0;
273
274 IEnumerable<WixBundlePayloadRow> packagePayloads = payloadsByPackage[facade.Package.WixChainItemId];
275
276 foreach (WixBundlePayloadRow payload in packagePayloads)
277 {
278 facade.Package.Size += payload.FileSize;
279 }
280
281 if (!facade.Package.InstallSize.HasValue)
282 {
283 facade.Package.InstallSize = facade.Package.Size;
284
285 }
286
287 WixBundlePayloadRow packagePayload = payloads[facade.Package.PackagePayload];
288
289 if (String.IsNullOrEmpty(facade.Package.Description))
290 {
291 facade.Package.Description = packagePayload.Description;
292 }
293
294 if (String.IsNullOrEmpty(facade.Package.DisplayName))
295 {
296 facade.Package.DisplayName = packagePayload.DisplayName;
297 }
298 }
299 }
300
301
302 // Give the UX payloads their embedded IDs...
303 int uxPayloadIndex = 0;
304 {
305 foreach (WixBundlePayloadRow payload in payloads.Values.Where(p => Compiler.BurnUXContainerId == p.Container))
306 {
307 // In theory, UX payloads could be embedded in the UX CAB, external to the bundle EXE, or even
308 // downloaded. The current engine requires the UX to be fully present before any downloading starts,
309 // so that rules out downloading. Also, the burn engine does not currently copy external UX payloads
310 // into the temporary UX directory correctly, so we don't allow external either.
311 if (PackagingType.Embedded != payload.Packaging)
312 {
313 Messaging.Instance.OnMessage(WixWarnings.UxPayloadsOnlySupportEmbedding(payload.SourceLineNumbers, payload.FullFileName));
314 payload.Packaging = PackagingType.Embedded;
315 }
316
317 payload.EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnUXContainerEmbeddedIdFormat, uxPayloadIndex);
318 ++uxPayloadIndex;
319 }
320
321 if (0 == uxPayloadIndex)
322 {
323 // If we didn't get any UX payloads, it's an error!
324 throw new WixException(WixErrors.MissingBundleInformation("BootstrapperApplication"));
325 }
326
327 // Give the embedded payloads without an embedded id yet an embedded id.
328 int payloadIndex = 0;
329 foreach (WixBundlePayloadRow payload in payloads.Values)
330 {
331 Debug.Assert(PackagingType.Unknown != payload.Packaging);
332
333 if (PackagingType.Embedded == payload.Packaging && String.IsNullOrEmpty(payload.EmbeddedId))
334 {
335 payload.EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnAttachedContainerEmbeddedIdFormat, payloadIndex);
336 ++payloadIndex;
337 }
338 }
339 }
340
341 // Determine patches to automatically slipstream.
342 {
343 AutomaticallySlipstreamPatchesCommand command = new AutomaticallySlipstreamPatchesCommand();
344 command.PackageFacades = facades.Values;
345 command.SlipstreamMspTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleSlipstreamMsp"]);
346 command.WixBundlePatchTargetCodeTable = this.Output.EnsureTable(this.TableDefinitions["WixBundlePatchTargetCode"]);
347 command.Execute();
348 }
349
350 // If catalog files exist, non-embedded payloads should validate with the catalogs.
351 IEnumerable<WixBundleCatalogRow> catalogs = this.Output.Tables["WixBundleCatalog"].RowsAs<WixBundleCatalogRow>();
352
353 if (catalogs.Any())
354 {
355 VerifyPayloadsWithCatalogCommand command = new VerifyPayloadsWithCatalogCommand();
356 command.Catalogs = catalogs;
357 command.Payloads = payloads.Values;
358 command.Execute();
359 }
360
361 if (Messaging.Instance.EncounteredError)
362 {
363 return;
364 }
365
366 IEnumerable<PackageFacade> orderedFacades;
367 IEnumerable<WixBundleRollbackBoundaryRow> boundaries;
368 {
369 OrderPackagesAndRollbackBoundariesCommand command = new OrderPackagesAndRollbackBoundariesCommand();
370 command.Boundaries = new RowDictionary<WixBundleRollbackBoundaryRow>(this.Output.Tables["WixBundleRollbackBoundary"]);
371 command.PackageFacades = facades;
372 command.WixGroupTable = wixGroupTable;
373 command.Execute();
374
375 orderedFacades = command.OrderedPackageFacades;
376 boundaries = command.UsedRollbackBoundaries;
377 }
378
379 // Resolve any delayed fields before generating the manifest.
380 if (delayedFields.Any())
381 {
382 ResolveDelayedFieldsCommand resolveDelayedFieldsCommand = new ResolveDelayedFieldsCommand();
383 resolveDelayedFieldsCommand.OutputType = this.Output.Type;
384 resolveDelayedFieldsCommand.DelayedFields = delayedFields;
385 resolveDelayedFieldsCommand.ModularizationGuid = null;
386 resolveDelayedFieldsCommand.VariableCache = variableCache;
387 resolveDelayedFieldsCommand.Execute();
388 }
389
390 // Set the overridable bundle provider key.
391 this.SetBundleProviderKey(this.Output, bundleRow);
392
393 // Import or generate dependency providers for packages in the manifest.
394 this.ProcessDependencyProviders(this.Output, facades);
395
396 // Update the bundle per-machine/per-user scope based on the chained packages.
397 this.ResolveBundleInstallScope(bundleRow, orderedFacades);
398
399 // Generate the core-defined BA manifest tables...
400 {
401 CreateBootstrapperApplicationManifestCommand command = new CreateBootstrapperApplicationManifestCommand();
402 command.BundleRow = bundleRow;
403 command.ChainPackages = orderedFacades;
404 command.LastUXPayloadIndex = uxPayloadIndex;
405 command.MsiFeatures = this.Output.Tables["WixBundleMsiFeature"].RowsAs<WixBundleMsiFeatureRow>();
406 command.Output = this.Output;
407 command.Payloads = payloads;
408 command.TableDefinitions = this.TableDefinitions;
409 command.TempFilesLocation = this.TempFilesLocation;
410 command.Execute();
411
412 WixBundlePayloadRow baManifestPayload = command.BootstrapperApplicationManifestPayloadRow;
413 payloads.Add(baManifestPayload);
414 }
415
416 foreach (BinderExtension extension in this.Extensions)
417 {
418 extension.Finish(Output);
419 }
420
421 // Create all the containers except the UX container first so the manifest (that goes in the UX container)
422 // can contain all size and hash information about the non-UX containers.
423 RowDictionary<WixBundleContainerRow> containers = new RowDictionary<WixBundleContainerRow>(this.Output.Tables["WixBundleContainer"]);
424
425 ILookup<string, WixBundlePayloadRow> payloadsByContainer = payloads.Values.ToLookup(p => p.Container);
426
427 int attachedContainerIndex = 1; // count starts at one because UX container is "0".
428
429 IEnumerable<WixBundlePayloadRow> uxContainerPayloads = Enumerable.Empty<WixBundlePayloadRow>();
430
431 foreach (WixBundleContainerRow container in containers.Values)
432 {
433 IEnumerable<WixBundlePayloadRow> containerPayloads = payloadsByContainer[container.Id];
434
435 if (!containerPayloads.Any())
436 {
437 if (container.Id != Compiler.BurnDefaultAttachedContainerId)
438 {
439 // TODO: display warning that we're ignoring container that ended up with no paylods in it.
440 }
441 }
442 else if (Compiler.BurnUXContainerId == container.Id)
443 {
444 container.WorkingPath = Path.Combine(this.TempFilesLocation, container.Name);
445 container.AttachedContainerIndex = 0;
446
447 // Gather the list of UX payloads but ensure the BootstrapperApplication Payload is the first
448 // in the list since that is the Payload that Burn attempts to load.
449 List<WixBundlePayloadRow> uxPayloads = new List<WixBundlePayloadRow>();
450
451 string baPayloadId = baRow.FieldAsString(0);
452
453 foreach (WixBundlePayloadRow uxPayload in containerPayloads)
454 {
455 if (uxPayload.Id == baPayloadId)
456 {
457 uxPayloads.Insert(0, uxPayload);
458 }
459 else
460 {
461 uxPayloads.Add(uxPayload);
462 }
463 }
464
465 uxContainerPayloads = uxPayloads;
466 }
467 else
468 {
469 container.WorkingPath = Path.Combine(this.TempFilesLocation, container.Name);
470
471 // Add detached containers to the list of file transfers.
472 if (ContainerType.Detached == container.Type)
473 {
474 FileTransfer transfer;
475 if (FileTransfer.TryCreate(container.WorkingPath, Path.Combine(layoutDirectory, container.Name), true, "Container", container.SourceLineNumbers, out transfer))
476 {
477 transfer.Built = true;
478 fileTransfers.Add(transfer);
479 }
480 }
481 else // update the attached container index.
482 {
483 Debug.Assert(ContainerType.Attached == container.Type);
484
485 container.AttachedContainerIndex = attachedContainerIndex;
486 ++attachedContainerIndex;
487 }
488
489 this.CreateContainer(container, containerPayloads, null);
490 }
491 }
492
493 // Create the bundle manifest then UX container.
494 string manifestPath = Path.Combine(this.TempFilesLocation, "bundle-manifest.xml");
495 {
496 CreateBurnManifestCommand command = new CreateBurnManifestCommand();
497 command.FileManagers = this.FileManagers;
498 command.Output = this.Output;
499
500 command.BundleInfo = bundleRow;
501 command.Chain = chainRow;
502 command.Containers = containers;
503 command.Catalogs = catalogs;
504 command.ExecutableName = Path.GetFileName(this.OutputPath);
505 command.OrderedPackages = orderedFacades;
506 command.OutputPath = manifestPath;
507 command.RollbackBoundaries = boundaries;
508 command.OrderedSearches = orderedSearches;
509 command.Payloads = payloads;
510 command.UXContainerPayloads = uxContainerPayloads;
511 command.Execute();
512 }
513
514 WixBundleContainerRow uxContainer = containers[Compiler.BurnUXContainerId];
515 this.CreateContainer(uxContainer, uxContainerPayloads, manifestPath);
516
517 // Copy the burn.exe to a writable location then mark it to be moved to its final build location. Note
518 // that today, the x64 Burn uses the x86 stub.
519 string stubPlatform = (Platform.X64 == bundleRow.Platform) ? "x86" : bundleRow.Platform.ToString();
520
521 string stubFile = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), stubPlatform, "burn.exe");
522 string bundleTempPath = Path.Combine(this.TempFilesLocation, Path.GetFileName(this.OutputPath));
523
524 Messaging.Instance.OnMessage(WixVerboses.GeneratingBundle(bundleTempPath, stubFile));
525
526 string bundleFilename = Path.GetFileName(this.OutputPath);
527 if ("setup.exe".Equals(bundleFilename, StringComparison.OrdinalIgnoreCase))
528 {
529 Messaging.Instance.OnMessage(WixErrors.InsecureBundleFilename(bundleFilename));
530 }
531
532 FileTransfer bundleTransfer;
533 if (FileTransfer.TryCreate(bundleTempPath, this.OutputPath, true, "Bundle", bundleRow.SourceLineNumbers, out bundleTransfer))
534 {
535 bundleTransfer.Built = true;
536 fileTransfers.Add(bundleTransfer);
537 }
538
539 File.Copy(stubFile, bundleTempPath, true);
540 File.SetAttributes(bundleTempPath, FileAttributes.Normal);
541
542 this.UpdateBurnResources(bundleTempPath, this.OutputPath, bundleRow);
543
544 // Update the .wixburn section to point to at the UX and attached container(s) then attach the containers
545 // if they should be attached.
546 using (BurnWriter writer = BurnWriter.Open(bundleTempPath))
547 {
548 FileInfo burnStubFile = new FileInfo(bundleTempPath);
549 writer.InitializeBundleSectionData(burnStubFile.Length, bundleRow.BundleId);
550
551 // Always attach the UX container first
552 writer.AppendContainer(uxContainer.WorkingPath, BurnWriter.Container.UX);
553
554 // Now append all other attached containers
555 foreach (WixBundleContainerRow container in containers.Values)
556 {
557 if (ContainerType.Attached == container.Type)
558 {
559 // The container was only created if it had payloads.
560 if (!String.IsNullOrEmpty(container.WorkingPath) && Compiler.BurnUXContainerId != container.Id)
561 {
562 writer.AppendContainer(container.WorkingPath, BurnWriter.Container.Attached);
563 }
564 }
565 }
566 }
567
568 if (null != this.PdbFile)
569 {
570 Pdb pdb = new Pdb();
571 pdb.Output = Output;
572 pdb.Save(this.PdbFile);
573 }
574
575 this.FileTransfers = fileTransfers;
576 this.ContentFilePaths = payloads.Values.Where(p => p.ContentFile).Select(p => p.FullFileName).ToList();
577 }
578
579 private Table GetRequiredTable(string tableName)
580 {
581 Table table = this.Output.Tables[tableName];
582 if (null == table || 0 == table.Rows.Count)
583 {
584 throw new WixException(WixErrors.MissingBundleInformation(tableName));
585 }
586
587 return table;
588 }
589
590 private Row GetSingleRowTable(string tableName)
591 {
592 Table table = this.Output.Tables[tableName];
593 if (null == table || 1 != table.Rows.Count)
594 {
595 throw new WixException(WixErrors.MissingBundleInformation(tableName));
596 }
597
598 return table.Rows[0];
599 }
600
601 private List<WixSearchInfo> OrderSearches()
602 {
603 Dictionary<string, WixSearchInfo> allSearches = new Dictionary<string, WixSearchInfo>();
604 Table wixFileSearchTable = this.Output.Tables["WixFileSearch"];
605 if (null != wixFileSearchTable && 0 < wixFileSearchTable.Rows.Count)
606 {
607 foreach (Row row in wixFileSearchTable.Rows)
608 {
609 WixFileSearchInfo fileSearchInfo = new WixFileSearchInfo(row);
610 allSearches.Add(fileSearchInfo.Id, fileSearchInfo);
611 }
612 }
613
614 Table wixRegistrySearchTable = this.Output.Tables["WixRegistrySearch"];
615 if (null != wixRegistrySearchTable && 0 < wixRegistrySearchTable.Rows.Count)
616 {
617 foreach (Row row in wixRegistrySearchTable.Rows)
618 {
619 WixRegistrySearchInfo registrySearchInfo = new WixRegistrySearchInfo(row);
620 allSearches.Add(registrySearchInfo.Id, registrySearchInfo);
621 }
622 }
623
624 Table wixComponentSearchTable = this.Output.Tables["WixComponentSearch"];
625 if (null != wixComponentSearchTable && 0 < wixComponentSearchTable.Rows.Count)
626 {
627 foreach (Row row in wixComponentSearchTable.Rows)
628 {
629 WixComponentSearchInfo componentSearchInfo = new WixComponentSearchInfo(row);
630 allSearches.Add(componentSearchInfo.Id, componentSearchInfo);
631 }
632 }
633
634 Table wixProductSearchTable = this.Output.Tables["WixProductSearch"];
635 if (null != wixProductSearchTable && 0 < wixProductSearchTable.Rows.Count)
636 {
637 foreach (Row row in wixProductSearchTable.Rows)
638 {
639 WixProductSearchInfo productSearchInfo = new WixProductSearchInfo(row);
640 allSearches.Add(productSearchInfo.Id, productSearchInfo);
641 }
642 }
643
644 // Merge in the variable/condition info and get the canonical ordering for
645 // the searches.
646 List<WixSearchInfo> orderedSearches = new List<WixSearchInfo>();
647 Table wixSearchTable = this.Output.Tables["WixSearch"];
648 if (null != wixSearchTable && 0 < wixSearchTable.Rows.Count)
649 {
650 orderedSearches.Capacity = wixSearchTable.Rows.Count;
651 foreach (Row row in wixSearchTable.Rows)
652 {
653 WixSearchInfo searchInfo = allSearches[(string)row[0]];
654 searchInfo.AddWixSearchRowInfo(row);
655 orderedSearches.Add(searchInfo);
656 }
657 }
658
659 return orderedSearches;
660 }
661
662 /// <summary>
663 /// Populates the variable cache with specific package properties.
664 /// </summary>
665 /// <param name="package">The package with properties to cache.</param>
666 /// <param name="variableCache">The property cache.</param>
667 private static void PopulatePackageVariableCache(WixBundlePackageRow package, IDictionary<string, string> variableCache)
668 {
669 string id = package.WixChainItemId;
670
671 variableCache.Add(String.Concat("packageDescription.", id), package.Description);
672 //variableCache.Add(String.Concat("packageLanguage.", id), package.Language);
673 //variableCache.Add(String.Concat("packageManufacturer.", id), package.Manufacturer);
674 variableCache.Add(String.Concat("packageName.", id), package.DisplayName);
675 variableCache.Add(String.Concat("packageVersion.", id), package.Version);
676 }
677
678 private void CreateContainer(WixBundleContainerRow container, IEnumerable<WixBundlePayloadRow> containerPayloads, string manifestFile)
679 {
680 CreateContainerCommand command = new CreateContainerCommand();
681 command.DefaultCompressionLevel = this.DefaultCompressionLevel;
682 command.Payloads = containerPayloads;
683 command.ManifestFile = manifestFile;
684 command.OutputPath = container.WorkingPath;
685 command.Execute();
686
687 container.Hash = command.Hash;
688 container.Size = command.Size;
689 }
690
691 private void ResolveBundleInstallScope(WixBundleRow bundleInfo, IEnumerable<PackageFacade> facades)
692 {
693 foreach (PackageFacade facade in facades)
694 {
695 if (bundleInfo.PerMachine && YesNoDefaultType.No == facade.Package.PerMachine)
696 {
697 Messaging.Instance.OnMessage(WixVerboses.SwitchingToPerUserPackage(facade.Package.SourceLineNumbers, facade.Package.WixChainItemId));
698
699 bundleInfo.PerMachine = false;
700 break;
701 }
702 }
703
704 foreach (PackageFacade facade in facades)
705 {
706 // Update package scope from bundle scope if default.
707 if (YesNoDefaultType.Default == facade.Package.PerMachine)
708 {
709 facade.Package.PerMachine = bundleInfo.PerMachine ? YesNoDefaultType.Yes : YesNoDefaultType.No;
710 }
711
712 // We will only register packages in the same scope as the bundle. Warn if any packages with providers
713 // are in a different scope and not permanent (permanents typically don't need a ref-count).
714 if (!bundleInfo.PerMachine && YesNoDefaultType.Yes == facade.Package.PerMachine && !facade.Package.Permanent && 0 < facade.Provides.Count)
715 {
716 Messaging.Instance.OnMessage(WixWarnings.NoPerMachineDependencies(facade.Package.SourceLineNumbers, facade.Package.WixChainItemId));
717 }
718 }
719 }
720
721 private void UpdateBurnResources(string bundleTempPath, string outputPath, WixBundleRow bundleInfo)
722 {
723 WixToolset.Dtf.Resources.ResourceCollection resources = new WixToolset.Dtf.Resources.ResourceCollection();
724 WixToolset.Dtf.Resources.VersionResource version = new WixToolset.Dtf.Resources.VersionResource("#1", 1033);
725
726 version.Load(bundleTempPath);
727 resources.Add(version);
728
729 // Ensure the bundle info provides a full four part version.
730 Version fourPartVersion = new Version(bundleInfo.Version);
731 int major = (fourPartVersion.Major < 0) ? 0 : fourPartVersion.Major;
732 int minor = (fourPartVersion.Minor < 0) ? 0 : fourPartVersion.Minor;
733 int build = (fourPartVersion.Build < 0) ? 0 : fourPartVersion.Build;
734 int revision = (fourPartVersion.Revision < 0) ? 0 : fourPartVersion.Revision;
735
736 if (UInt16.MaxValue < major || UInt16.MaxValue < minor || UInt16.MaxValue < build || UInt16.MaxValue < revision)
737 {
738 throw new WixException(WixErrors.InvalidModuleOrBundleVersion(bundleInfo.SourceLineNumbers, "Bundle", bundleInfo.Version));
739 }
740
741 fourPartVersion = new Version(major, minor, build, revision);
742 version.FileVersion = fourPartVersion;
743 version.ProductVersion = fourPartVersion;
744
745 WixToolset.Dtf.Resources.VersionStringTable strings = version[1033];
746 strings["LegalCopyright"] = bundleInfo.Copyright;
747 strings["OriginalFilename"] = Path.GetFileName(outputPath);
748 strings["FileVersion"] = bundleInfo.Version; // string versions do not have to be four parts.
749 strings["ProductVersion"] = bundleInfo.Version; // string versions do not have to be four parts.
750
751 if (!String.IsNullOrEmpty(bundleInfo.Name))
752 {
753 strings["ProductName"] = bundleInfo.Name;
754 strings["FileDescription"] = bundleInfo.Name;
755 }
756
757 if (!String.IsNullOrEmpty(bundleInfo.Publisher))
758 {
759 strings["CompanyName"] = bundleInfo.Publisher;
760 }
761 else
762 {
763 strings["CompanyName"] = String.Empty;
764 }
765
766 if (!String.IsNullOrEmpty(bundleInfo.IconPath))
767 {
768 Dtf.Resources.GroupIconResource iconGroup = new Dtf.Resources.GroupIconResource("#1", 1033);
769 iconGroup.ReadFromFile(bundleInfo.IconPath);
770 resources.Add(iconGroup);
771
772 foreach (Dtf.Resources.Resource icon in iconGroup.Icons)
773 {
774 resources.Add(icon);
775 }
776 }
777
778 if (!String.IsNullOrEmpty(bundleInfo.SplashScreenBitmapPath))
779 {
780 Dtf.Resources.BitmapResource bitmap = new Dtf.Resources.BitmapResource("#1", 1033);
781 bitmap.ReadFromFile(bundleInfo.SplashScreenBitmapPath);
782 resources.Add(bitmap);
783 }
784
785 resources.Save(bundleTempPath);
786 }
787
788 #region DependencyExtension
789 /// <summary>
790 /// Imports authored dependency providers for each package in the manifest,
791 /// and generates dependency providers for certain package types that do not
792 /// have a provider defined.
793 /// </summary>
794 /// <param name="bundle">The <see cref="Output"/> object for the bundle.</param>
795 /// <param name="facades">An indexed collection of chained packages.</param>
796 private void ProcessDependencyProviders(Output bundle, IDictionary<string, PackageFacade> facades)
797 {
798 // First import any authored dependencies. These may merge with imported provides from MSI packages.
799 Table wixDependencyProviderTable = bundle.Tables["WixDependencyProvider"];
800 if (null != wixDependencyProviderTable && 0 < wixDependencyProviderTable.Rows.Count)
801 {
802 // Add package information for each dependency provider authored into the manifest.
803 foreach (Row wixDependencyProviderRow in wixDependencyProviderTable.Rows)
804 {
805 string packageId = (string)wixDependencyProviderRow[1];
806
807 PackageFacade facade = null;
808 if (facades.TryGetValue(packageId, out facade))
809 {
810 ProvidesDependency dependency = new ProvidesDependency(wixDependencyProviderRow);
811
812 if (String.IsNullOrEmpty(dependency.Key))
813 {
814 switch (facade.Package.Type)
815 {
816 // The WixDependencyExtension allows an empty Key for MSIs and MSPs.
817 case WixBundlePackageType.Msi:
818 dependency.Key = facade.MsiPackage.ProductCode;
819 break;
820 case WixBundlePackageType.Msp:
821 dependency.Key = facade.MspPackage.PatchCode;
822 break;
823 }
824 }
825
826 if (String.IsNullOrEmpty(dependency.Version))
827 {
828 dependency.Version = facade.Package.Version;
829 }
830
831 // If the version is still missing, a version could not be harvested from the package and was not authored.
832 if (String.IsNullOrEmpty(dependency.Version))
833 {
834 Messaging.Instance.OnMessage(WixErrors.MissingDependencyVersion(facade.Package.WixChainItemId));
835 }
836
837 if (String.IsNullOrEmpty(dependency.DisplayName))
838 {
839 dependency.DisplayName = facade.Package.DisplayName;
840 }
841
842 if (!facade.Provides.Merge(dependency))
843 {
844 Messaging.Instance.OnMessage(WixErrors.DuplicateProviderDependencyKey(dependency.Key, facade.Package.WixChainItemId));
845 }
846 }
847 }
848 }
849
850 // Generate providers for MSI packages that still do not have providers.
851 foreach (PackageFacade facade in facades.Values)
852 {
853 string key = null;
854
855 if (WixBundlePackageType.Msi == facade.Package.Type && 0 == facade.Provides.Count)
856 {
857 key = facade.MsiPackage.ProductCode;
858 }
859 else if (WixBundlePackageType.Msp == facade.Package.Type && 0 == facade.Provides.Count)
860 {
861 key = facade.MspPackage.PatchCode;
862 }
863
864 if (!String.IsNullOrEmpty(key))
865 {
866 ProvidesDependency dependency = new ProvidesDependency(key, facade.Package.Version, facade.Package.DisplayName, 0);
867
868 if (!facade.Provides.Merge(dependency))
869 {
870 Messaging.Instance.OnMessage(WixErrors.DuplicateProviderDependencyKey(dependency.Key, facade.Package.WixChainItemId));
871 }
872 }
873 }
874 }
875
876 /// <summary>
877 /// Sets the provider key for the bundle.
878 /// </summary>
879 /// <param name="bundle">The <see cref="Output"/> object for the bundle.</param>
880 /// <param name="bundleInfo">The <see cref="BundleInfo"/> containing the provider key and other information for the bundle.</param>
881 private void SetBundleProviderKey(Output bundle, WixBundleRow bundleInfo)
882 {
883 // From DependencyCommon.cs in the WixDependencyExtension.
884 const int ProvidesAttributesBundle = 0x10000;
885
886 Table wixDependencyProviderTable = bundle.Tables["WixDependencyProvider"];
887 if (null != wixDependencyProviderTable && 0 < wixDependencyProviderTable.Rows.Count)
888 {
889 // Search the WixDependencyProvider table for the single bundle provider key.
890 foreach (Row wixDependencyProviderRow in wixDependencyProviderTable.Rows)
891 {
892 object attributes = wixDependencyProviderRow[5];
893 if (null != attributes && 0 != (ProvidesAttributesBundle & (int)attributes))
894 {
895 bundleInfo.ProviderKey = (string)wixDependencyProviderRow[2];
896 break;
897 }
898 }
899 }
900
901 // Defaults to the bundle ID as the provider key.
902 }
903 #endregion
904 }
905}
diff --git a/src/WixToolset.Core/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core/Bind/BindDatabaseCommand.cs
new file mode 100644
index 00000000..93af2e9a
--- /dev/null
+++ b/src/WixToolset.Core/Bind/BindDatabaseCommand.cs
@@ -0,0 +1,1311 @@
1// Copyright (c) .NET Foundation and 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.Bind
4{
5 using System;
6 using System.Collections;
7 using System.Collections.Generic;
8 using System.Diagnostics;
9 using System.Globalization;
10 using System.IO;
11 using System.Linq;
12 using WixToolset.Bind.Databases;
13 using WixToolset.Data;
14 using WixToolset.Data.Rows;
15 using WixToolset.Extensibility;
16 using WixToolset.Msi;
17
18 /// <summary>
19 /// Binds a databse.
20 /// </summary>
21 internal class BindDatabaseCommand : ICommand
22 {
23 // As outlined in RFC 4122, this is our namespace for generating name-based (version 3) UUIDs.
24 private static readonly Guid WixComponentGuidNamespace = new Guid("{3064E5C6-FB63-4FE9-AC49-E446A792EFA5}");
25
26 public int Codepage { private get; set; }
27
28 public int CabbingThreadCount { private get; set; }
29
30 public CompressionLevel DefaultCompressionLevel { private get; set; }
31
32 public bool DeltaBinaryPatch { get; set; }
33
34 public IEnumerable<IBinderExtension> Extensions { private get; set; }
35
36 public BinderFileManagerCore FileManagerCore { private get; set; }
37
38 public IEnumerable<IBinderFileManager> FileManagers { private get; set; }
39
40 public IEnumerable<InspectorExtension> InspectorExtensions { private get; set; }
41
42 public Localizer Localizer { private get; set; }
43
44 public string PdbFile { private get; set; }
45
46 public Output Output { private get; set; }
47
48 public string OutputPath { private get; set; }
49
50 public bool SuppressAddingValidationRows { private get; set; }
51
52 public bool SuppressLayout { private get; set; }
53
54 public TableDefinitionCollection TableDefinitions { private get; set; }
55
56 public string TempFilesLocation { private get; set; }
57
58 public Validator Validator { private get; set; }
59
60 public WixVariableResolver WixVariableResolver { private get; set; }
61
62 public IEnumerable<FileTransfer> FileTransfers { get; private set; }
63
64 public IEnumerable<string> ContentFilePaths { get; private set; }
65
66 public void Execute()
67 {
68 List<FileTransfer> fileTransfers = new List<FileTransfer>();
69
70 HashSet<string> suppressedTableNames = new HashSet<string>();
71
72 // Localize fields, resolve wix variables, and resolve file paths.
73 ExtractEmbeddedFiles filesWithEmbeddedFiles = new ExtractEmbeddedFiles();
74
75 IEnumerable<DelayedField> delayedFields;
76 {
77 ResolveFieldsCommand command = new ResolveFieldsCommand();
78 command.Tables = this.Output.Tables;
79 command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles;
80 command.FileManagerCore = this.FileManagerCore;
81 command.FileManagers = this.FileManagers;
82 command.SupportDelayedResolution = true;
83 command.TempFilesLocation = this.TempFilesLocation;
84 command.WixVariableResolver = this.WixVariableResolver;
85 command.Execute();
86
87 delayedFields = command.DelayedFields;
88 }
89
90 if (OutputType.Patch == this.Output.Type)
91 {
92 foreach (SubStorage transform in this.Output.SubStorages)
93 {
94 ResolveFieldsCommand command = new ResolveFieldsCommand();
95 command.Tables = transform.Data.Tables;
96 command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles;
97 command.FileManagerCore = this.FileManagerCore;
98 command.FileManagers = this.FileManagers;
99 command.SupportDelayedResolution = false;
100 command.TempFilesLocation = this.TempFilesLocation;
101 command.WixVariableResolver = this.WixVariableResolver;
102 command.Execute();
103 }
104 }
105
106 // If there are any fields to resolve later, create the cache to populate during bind.
107 IDictionary<string, string> variableCache = null;
108 if (delayedFields.Any())
109 {
110 variableCache = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
111 }
112
113 this.LocalizeUI(this.Output.Tables);
114
115 // Process the summary information table before the other tables.
116 bool compressed;
117 bool longNames;
118 int installerVersion;
119 string modularizationGuid;
120 {
121 BindSummaryInfoCommand command = new BindSummaryInfoCommand();
122 command.Output = this.Output;
123 command.Execute();
124
125 compressed = command.Compressed;
126 longNames = command.LongNames;
127 installerVersion = command.InstallerVersion;
128 modularizationGuid = command.ModularizationGuid;
129 }
130
131 // Stop processing if an error previously occurred.
132 if (Messaging.Instance.EncounteredError)
133 {
134 return;
135 }
136
137 // Modularize identifiers and add tables with real streams to the import tables.
138 if (OutputType.Module == this.Output.Type)
139 {
140 // Gather all the suppress modularization identifiers
141 HashSet<string> suppressModularizationIdentifiers = null;
142 Table wixSuppressModularizationTable = this.Output.Tables["WixSuppressModularization"];
143 if (null != wixSuppressModularizationTable)
144 {
145 suppressModularizationIdentifiers = new HashSet<string>(wixSuppressModularizationTable.Rows.Select(row => (string)row[0]));
146 }
147
148 foreach (Table table in this.Output.Tables)
149 {
150 table.Modularize(modularizationGuid, suppressModularizationIdentifiers);
151 }
152 }
153
154 // This must occur after all variables and source paths have been resolved and after modularization.
155 List<FileFacade> fileFacades;
156 {
157 GetFileFacadesCommand command = new GetFileFacadesCommand();
158 command.FileTable = this.Output.Tables["File"];
159 command.WixFileTable = this.Output.Tables["WixFile"];
160 command.WixDeltaPatchFileTable = this.Output.Tables["WixDeltaPatchFile"];
161 command.WixDeltaPatchSymbolPathsTable = this.Output.Tables["WixDeltaPatchSymbolPaths"];
162 command.Execute();
163
164 fileFacades = command.FileFacades;
165 }
166
167 ////if (OutputType.Patch == this.Output.Type)
168 ////{
169 //// foreach (SubStorage substorage in this.Output.SubStorages)
170 //// {
171 //// Output transform = substorage.Data;
172
173 //// ResolveFieldsCommand command = new ResolveFieldsCommand();
174 //// command.Tables = transform.Tables;
175 //// command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles;
176 //// command.FileManagerCore = this.FileManagerCore;
177 //// command.FileManagers = this.FileManagers;
178 //// command.SupportDelayedResolution = false;
179 //// command.TempFilesLocation = this.TempFilesLocation;
180 //// command.WixVariableResolver = this.WixVariableResolver;
181 //// command.Execute();
182
183 //// this.MergeUnrealTables(transform.Tables);
184 //// }
185 ////}
186
187 {
188 CreateSpecialPropertiesCommand command = new CreateSpecialPropertiesCommand();
189 command.PropertyTable = this.Output.Tables["Property"];
190 command.WixPropertyTable = this.Output.Tables["WixProperty"];
191 command.Execute();
192 }
193
194 if (Messaging.Instance.EncounteredError)
195 {
196 return;
197 }
198
199 // Add binder variables for all properties.
200 Table propertyTable = this.Output.Tables["Property"];
201 if (null != propertyTable)
202 {
203 foreach (PropertyRow propertyRow in propertyTable.Rows)
204 {
205 // Set the ProductCode if it is to be generated.
206 if (OutputType.Product == this.Output.Type && "ProductCode".Equals(propertyRow.Property, StringComparison.Ordinal) && "*".Equals(propertyRow.Value, StringComparison.Ordinal))
207 {
208 propertyRow.Value = Common.GenerateGuid();
209
210 // Update the target ProductCode in any instance transforms.
211 foreach (SubStorage subStorage in this.Output.SubStorages)
212 {
213 Output subStorageOutput = subStorage.Data;
214 if (OutputType.Transform != subStorageOutput.Type)
215 {
216 continue;
217 }
218
219 Table instanceSummaryInformationTable = subStorageOutput.Tables["_SummaryInformation"];
220 foreach (Row row in instanceSummaryInformationTable.Rows)
221 {
222 if ((int)SummaryInformation.Transform.ProductCodes == row.FieldAsInteger(0))
223 {
224 row[1] = row.FieldAsString(1).Replace("*", propertyRow.Value);
225 break;
226 }
227 }
228 }
229 }
230
231 // Add the property name and value to the variableCache.
232 if (null != variableCache)
233 {
234 string key = String.Concat("property.", Demodularize(this.Output.Type, modularizationGuid, propertyRow.Property));
235 variableCache[key] = propertyRow.Value;
236 }
237 }
238 }
239
240 // Extract files that come from cabinet files (this does not extract files from merge modules).
241 {
242 ExtractEmbeddedFilesCommand command = new ExtractEmbeddedFilesCommand();
243 command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles;
244 command.Execute();
245 }
246
247 if (OutputType.Product == this.Output.Type)
248 {
249 // Retrieve files and their information from merge modules.
250 Table wixMergeTable = this.Output.Tables["WixMerge"];
251
252 if (null != wixMergeTable)
253 {
254 ExtractMergeModuleFilesCommand command = new ExtractMergeModuleFilesCommand();
255 command.FileFacades = fileFacades;
256 command.FileTable = this.Output.Tables["File"];
257 command.WixFileTable = this.Output.Tables["WixFile"];
258 command.WixMergeTable = wixMergeTable;
259 command.OutputInstallerVersion = installerVersion;
260 command.SuppressLayout = this.SuppressLayout;
261 command.TempFilesLocation = this.TempFilesLocation;
262 command.Execute();
263
264 fileFacades.AddRange(command.MergeModulesFileFacades);
265 }
266 }
267 else if (OutputType.Patch == this.Output.Type)
268 {
269 // Merge transform data into the output object.
270 IEnumerable<FileFacade> filesFromTransform = this.CopyFromTransformData(this.Output);
271
272 fileFacades.AddRange(filesFromTransform);
273 }
274
275 // stop processing if an error previously occurred
276 if (Messaging.Instance.EncounteredError)
277 {
278 return;
279 }
280
281 Messaging.Instance.OnMessage(WixVerboses.UpdatingFileInformation());
282
283 // Gather information about files that did not come from merge modules (i.e. rows with a reference to the File table).
284 {
285 UpdateFileFacadesCommand command = new UpdateFileFacadesCommand();
286 command.FileFacades = fileFacades;
287 command.UpdateFileFacades = fileFacades.Where(f => !f.FromModule);
288 command.ModularizationGuid = modularizationGuid;
289 command.Output = this.Output;
290 command.OverwriteHash = true;
291 command.TableDefinitions = this.TableDefinitions;
292 command.VariableCache = variableCache;
293 command.Execute();
294 }
295
296 // Set generated component guids.
297 this.SetComponentGuids(this.Output);
298
299 // With the Component Guids set now we can create instance transforms.
300 this.CreateInstanceTransforms(this.Output);
301
302 this.ValidateComponentGuids(this.Output);
303
304 this.UpdateControlText(this.Output);
305
306 if (delayedFields.Any())
307 {
308 ResolveDelayedFieldsCommand command = new ResolveDelayedFieldsCommand();
309 command.OutputType = this.Output.Type;
310 command.DelayedFields = delayedFields;
311 command.ModularizationGuid = null;
312 command.VariableCache = variableCache;
313 command.Execute();
314 }
315
316 // Assign files to media.
317 RowDictionary<MediaRow> assignedMediaRows;
318 Dictionary<MediaRow, IEnumerable<FileFacade>> filesByCabinetMedia;
319 IEnumerable<FileFacade> uncompressedFiles;
320 {
321 AssignMediaCommand command = new AssignMediaCommand();
322 command.FilesCompressed = compressed;
323 command.FileFacades = fileFacades;
324 command.Output = this.Output;
325 command.TableDefinitions = this.TableDefinitions;
326 command.Execute();
327
328 assignedMediaRows = command.MediaRows;
329 filesByCabinetMedia = command.FileFacadesByCabinetMedia;
330 uncompressedFiles = command.UncompressedFileFacades;
331 }
332
333 // Update file sequence.
334 this.UpdateMediaSequences(this.Output.Type, fileFacades, assignedMediaRows);
335
336 // stop processing if an error previously occurred
337 if (Messaging.Instance.EncounteredError)
338 {
339 return;
340 }
341
342 // Extended binder extensions can be called now that fields are resolved.
343 {
344 Table updatedFiles = this.Output.EnsureTable(this.TableDefinitions["WixBindUpdatedFiles"]);
345
346 foreach (BinderExtension extension in this.Extensions)
347 {
348 extension.AfterResolvedFields(this.Output);
349 }
350
351 List<FileFacade> updatedFileFacades = new List<FileFacade>();
352
353 foreach (Row updatedFile in updatedFiles.Rows)
354 {
355 string updatedId = updatedFile.FieldAsString(0);
356
357 FileFacade updatedFacade = fileFacades.First(f => f.File.File.Equals(updatedId));
358
359 updatedFileFacades.Add(updatedFacade);
360 }
361
362 if (updatedFileFacades.Any())
363 {
364 UpdateFileFacadesCommand command = new UpdateFileFacadesCommand();
365 command.FileFacades = fileFacades;
366 command.UpdateFileFacades = updatedFileFacades;
367 command.ModularizationGuid = modularizationGuid;
368 command.Output = this.Output;
369 command.OverwriteHash = true;
370 command.TableDefinitions = this.TableDefinitions;
371 command.VariableCache = variableCache;
372 command.Execute();
373 }
374 }
375
376 // stop processing if an error previously occurred
377 if (Messaging.Instance.EncounteredError)
378 {
379 return;
380 }
381
382 Directory.CreateDirectory(this.TempFilesLocation);
383
384 if (OutputType.Patch == this.Output.Type && this.DeltaBinaryPatch)
385 {
386 CreateDeltaPatchesCommand command = new CreateDeltaPatchesCommand();
387 command.FileFacades = fileFacades;
388 command.WixPatchIdTable = this.Output.Tables["WixPatchId"];
389 command.TempFilesLocation = this.TempFilesLocation;
390 command.Execute();
391 }
392
393 // create cabinet files and process uncompressed files
394 string layoutDirectory = Path.GetDirectoryName(this.OutputPath);
395 if (!this.SuppressLayout || OutputType.Module == this.Output.Type)
396 {
397 Messaging.Instance.OnMessage(WixVerboses.CreatingCabinetFiles());
398
399 CreateCabinetsCommand command = new CreateCabinetsCommand();
400 command.CabbingThreadCount = this.CabbingThreadCount;
401 command.DefaultCompressionLevel = this.DefaultCompressionLevel;
402 command.Output = this.Output;
403 command.FileManagers = this.FileManagers;
404 command.LayoutDirectory = layoutDirectory;
405 command.Compressed = compressed;
406 command.FileRowsByCabinet = filesByCabinetMedia;
407 command.ResolveMedia = this.ResolveMedia;
408 command.TableDefinitions = this.TableDefinitions;
409 command.TempFilesLocation = this.TempFilesLocation;
410 command.WixMediaTable = this.Output.Tables["WixMedia"];
411 command.Execute();
412
413 fileTransfers.AddRange(command.FileTransfers);
414 }
415
416 if (OutputType.Patch == this.Output.Type)
417 {
418 // copy output data back into the transforms
419 this.CopyToTransformData(this.Output);
420 }
421
422 // stop processing if an error previously occurred
423 if (Messaging.Instance.EncounteredError)
424 {
425 return;
426 }
427
428 // add back suppressed tables which must be present prior to merging in modules
429 if (OutputType.Product == this.Output.Type)
430 {
431 Table wixMergeTable = this.Output.Tables["WixMerge"];
432
433 if (null != wixMergeTable && 0 < wixMergeTable.Rows.Count)
434 {
435 foreach (SequenceTable sequence in Enum.GetValues(typeof(SequenceTable)))
436 {
437 string sequenceTableName = sequence.ToString();
438 Table sequenceTable = this.Output.Tables[sequenceTableName];
439
440 if (null == sequenceTable)
441 {
442 sequenceTable = this.Output.EnsureTable(this.TableDefinitions[sequenceTableName]);
443 }
444
445 if (0 == sequenceTable.Rows.Count)
446 {
447 suppressedTableNames.Add(sequenceTableName);
448 }
449 }
450 }
451 }
452
453 foreach (BinderExtension extension in this.Extensions)
454 {
455 extension.Finish(this.Output);
456 }
457
458 // generate database file
459 Messaging.Instance.OnMessage(WixVerboses.GeneratingDatabase());
460 string tempDatabaseFile = Path.Combine(this.TempFilesLocation, Path.GetFileName(this.OutputPath));
461 this.GenerateDatabase(this.Output, tempDatabaseFile, false, false);
462
463 FileTransfer transfer;
464 if (FileTransfer.TryCreate(tempDatabaseFile, this.OutputPath, true, this.Output.Type.ToString(), null, out transfer)) // note where this database needs to move in the future
465 {
466 transfer.Built = true;
467 fileTransfers.Add(transfer);
468 }
469
470 // stop processing if an error previously occurred
471 if (Messaging.Instance.EncounteredError)
472 {
473 return;
474 }
475
476 // Output the output to a file
477 Pdb pdb = new Pdb();
478 pdb.Output = this.Output;
479 if (!String.IsNullOrEmpty(this.PdbFile))
480 {
481 pdb.Save(this.PdbFile);
482 }
483
484 // Merge modules.
485 if (OutputType.Product == this.Output.Type)
486 {
487 Messaging.Instance.OnMessage(WixVerboses.MergingModules());
488
489 MergeModulesCommand command = new MergeModulesCommand();
490 command.FileFacades = fileFacades;
491 command.Output = this.Output;
492 command.OutputPath = tempDatabaseFile;
493 command.SuppressedTableNames = suppressedTableNames;
494 command.Execute();
495
496 // stop processing if an error previously occurred
497 if (Messaging.Instance.EncounteredError)
498 {
499 return;
500 }
501 }
502
503 // inspect the MSI prior to running ICEs
504 InspectorCore inspectorCore = new InspectorCore();
505 foreach (InspectorExtension inspectorExtension in this.InspectorExtensions)
506 {
507 inspectorExtension.Core = inspectorCore;
508 inspectorExtension.InspectDatabase(tempDatabaseFile, pdb);
509
510 inspectorExtension.Core = null; // reset.
511 }
512
513 if (Messaging.Instance.EncounteredError)
514 {
515 return;
516 }
517
518 // validate the output if there is an MSI validator
519 if (null != this.Validator)
520 {
521 Stopwatch stopwatch = Stopwatch.StartNew();
522
523 // set the output file for source line information
524 this.Validator.Output = this.Output;
525
526 Messaging.Instance.OnMessage(WixVerboses.ValidatingDatabase());
527
528 this.Validator.Validate(tempDatabaseFile);
529
530 stopwatch.Stop();
531 Messaging.Instance.OnMessage(WixVerboses.ValidatedDatabase(stopwatch.ElapsedMilliseconds));
532
533 // Stop processing if an error occurred.
534 if (Messaging.Instance.EncounteredError)
535 {
536 return;
537 }
538 }
539
540 // Process uncompressed files.
541 if (!Messaging.Instance.EncounteredError && !this.SuppressLayout && uncompressedFiles.Any())
542 {
543 ProcessUncompressedFilesCommand command = new ProcessUncompressedFilesCommand();
544 command.Compressed = compressed;
545 command.FileFacades = uncompressedFiles;
546 command.LayoutDirectory = layoutDirectory;
547 command.LongNamesInImage = longNames;
548 command.MediaRows = assignedMediaRows;
549 command.ResolveMedia = this.ResolveMedia;
550 command.DatabasePath = tempDatabaseFile;
551 command.WixMediaTable = this.Output.Tables["WixMedia"];
552 command.Execute();
553
554 fileTransfers.AddRange(command.FileTransfers);
555 }
556
557 this.FileTransfers = fileTransfers;
558 this.ContentFilePaths = fileFacades.Select(r => r.WixFile.Source).ToList();
559 }
560
561 /// <summary>
562 /// Localize dialogs and controls.
563 /// </summary>
564 /// <param name="tables">The tables to localize.</param>
565 private void LocalizeUI(TableIndexedCollection tables)
566 {
567 Table dialogTable = tables["Dialog"];
568 if (null != dialogTable)
569 {
570 foreach (Row row in dialogTable.Rows)
571 {
572 string dialog = (string)row[0];
573 LocalizedControl localizedControl = this.Localizer.GetLocalizedControl(dialog, null);
574 if (null != localizedControl)
575 {
576 if (CompilerConstants.IntegerNotSet != localizedControl.X)
577 {
578 row[1] = localizedControl.X;
579 }
580
581 if (CompilerConstants.IntegerNotSet != localizedControl.Y)
582 {
583 row[2] = localizedControl.Y;
584 }
585
586 if (CompilerConstants.IntegerNotSet != localizedControl.Width)
587 {
588 row[3] = localizedControl.Width;
589 }
590
591 if (CompilerConstants.IntegerNotSet != localizedControl.Height)
592 {
593 row[4] = localizedControl.Height;
594 }
595
596 row[5] = (int)row[5] | localizedControl.Attributes;
597
598 if (!String.IsNullOrEmpty(localizedControl.Text))
599 {
600 row[6] = localizedControl.Text;
601 }
602 }
603 }
604 }
605
606 Table controlTable = tables["Control"];
607 if (null != controlTable)
608 {
609 foreach (Row row in controlTable.Rows)
610 {
611 string dialog = (string)row[0];
612 string control = (string)row[1];
613 LocalizedControl localizedControl = this.Localizer.GetLocalizedControl(dialog, control);
614 if (null != localizedControl)
615 {
616 if (CompilerConstants.IntegerNotSet != localizedControl.X)
617 {
618 row[3] = localizedControl.X.ToString();
619 }
620
621 if (CompilerConstants.IntegerNotSet != localizedControl.Y)
622 {
623 row[4] = localizedControl.Y.ToString();
624 }
625
626 if (CompilerConstants.IntegerNotSet != localizedControl.Width)
627 {
628 row[5] = localizedControl.Width.ToString();
629 }
630
631 if (CompilerConstants.IntegerNotSet != localizedControl.Height)
632 {
633 row[6] = localizedControl.Height.ToString();
634 }
635
636 row[7] = (int)row[7] | localizedControl.Attributes;
637
638 if (!String.IsNullOrEmpty(localizedControl.Text))
639 {
640 row[9] = localizedControl.Text;
641 }
642 }
643 }
644 }
645 }
646
647 /// <summary>
648 /// Copy file data between transform substorages and the patch output object
649 /// </summary>
650 /// <param name="output">The output to bind.</param>
651 /// <param name="allFileRows">True if copying from transform to patch, false the other way.</param>
652 private IEnumerable<FileFacade> CopyFromTransformData(Output output)
653 {
654 CopyTransformDataCommand command = new CopyTransformDataCommand();
655 command.CopyOutFileRows = true;
656 command.FileManagerCore = this.FileManagerCore;
657 command.FileManagers = this.FileManagers;
658 command.Output = output;
659 command.TableDefinitions = this.TableDefinitions;
660 command.Execute();
661
662 return command.FileFacades;
663 }
664
665 /// <summary>
666 /// Copy file data between transform substorages and the patch output object
667 /// </summary>
668 /// <param name="output">The output to bind.</param>
669 /// <param name="allFileRows">True if copying from transform to patch, false the other way.</param>
670 private void CopyToTransformData(Output output)
671 {
672 CopyTransformDataCommand command = new CopyTransformDataCommand();
673 command.CopyOutFileRows = false;
674 command.FileManagerCore = this.FileManagerCore;
675 command.FileManagers = this.FileManagers;
676 command.Output = output;
677 command.TableDefinitions = this.TableDefinitions;
678 command.Execute();
679 }
680
681 /// <summary>
682 /// Takes an id, and demodularizes it (if possible).
683 /// </summary>
684 /// <remarks>
685 /// If the output type is a module, returns a demodularized version of an id. Otherwise, returns the id.
686 /// </remarks>
687 /// <param name="outputType">The type of the output to bind.</param>
688 /// <param name="modularizationGuid">The modularization GUID.</param>
689 /// <param name="id">The id to demodularize.</param>
690 /// <returns>The demodularized id.</returns>
691 internal static string Demodularize(OutputType outputType, string modularizationGuid, string id)
692 {
693 if (OutputType.Module == outputType && id.EndsWith(String.Concat(".", modularizationGuid), StringComparison.Ordinal))
694 {
695 id = id.Substring(0, id.Length - 37);
696 }
697
698 return id;
699 }
700
701 private void UpdateMediaSequences(OutputType outputType, IEnumerable<FileFacade> fileFacades, RowDictionary<MediaRow> mediaRows)
702 {
703 // Calculate sequence numbers and media disk id layout for all file media information objects.
704 if (OutputType.Module == outputType)
705 {
706 int lastSequence = 0;
707 foreach (FileFacade facade in fileFacades) // TODO: Sort these rows directory path and component id and maybe file size or file extension and other creative ideas to get optimal install speed out of MSI.
708 {
709 facade.File.Sequence = ++lastSequence;
710 }
711 }
712 else
713 {
714 int lastSequence = 0;
715 MediaRow mediaRow = null;
716 Dictionary<int, List<FileFacade>> patchGroups = new Dictionary<int, List<FileFacade>>();
717
718 // sequence the non-patch-added files
719 foreach (FileFacade facade in fileFacades) // TODO: Sort these rows directory path and component id and maybe file size or file extension and other creative ideas to get optimal install speed out of MSI.
720 {
721 if (null == mediaRow)
722 {
723 mediaRow = mediaRows.Get(facade.WixFile.DiskId);
724 if (OutputType.Patch == outputType)
725 {
726 // patch Media cannot start at zero
727 lastSequence = mediaRow.LastSequence;
728 }
729 }
730 else if (mediaRow.DiskId != facade.WixFile.DiskId)
731 {
732 mediaRow.LastSequence = lastSequence;
733 mediaRow = mediaRows.Get(facade.WixFile.DiskId);
734 }
735
736 if (0 < facade.WixFile.PatchGroup)
737 {
738 List<FileFacade> patchGroup = patchGroups[facade.WixFile.PatchGroup];
739
740 if (null == patchGroup)
741 {
742 patchGroup = new List<FileFacade>();
743 patchGroups.Add(facade.WixFile.PatchGroup, patchGroup);
744 }
745
746 patchGroup.Add(facade);
747 }
748 else
749 {
750 facade.File.Sequence = ++lastSequence;
751 }
752 }
753
754 if (null != mediaRow)
755 {
756 mediaRow.LastSequence = lastSequence;
757 mediaRow = null;
758 }
759
760 // sequence the patch-added files
761 foreach (List<FileFacade> patchGroup in patchGroups.Values)
762 {
763 foreach (FileFacade facade in patchGroup)
764 {
765 if (null == mediaRow)
766 {
767 mediaRow = mediaRows.Get(facade.WixFile.DiskId);
768 }
769 else if (mediaRow.DiskId != facade.WixFile.DiskId)
770 {
771 mediaRow.LastSequence = lastSequence;
772 mediaRow = mediaRows.Get(facade.WixFile.DiskId);
773 }
774
775 facade.File.Sequence = ++lastSequence;
776 }
777 }
778
779 if (null != mediaRow)
780 {
781 mediaRow.LastSequence = lastSequence;
782 }
783 }
784 }
785
786 /// <summary>
787 /// Set the guids for components with generatable guids.
788 /// </summary>
789 /// <param name="output">Internal representation of the database to operate on.</param>
790 private void SetComponentGuids(Output output)
791 {
792 Table componentTable = output.Tables["Component"];
793 if (null != componentTable)
794 {
795 Hashtable registryKeyRows = null;
796 Hashtable directories = null;
797 Hashtable componentIdGenSeeds = null;
798 Dictionary<string, List<FileRow>> fileRows = null;
799
800 // find components with generatable guids
801 foreach (ComponentRow componentRow in componentTable.Rows)
802 {
803 // component guid will be generated
804 if ("*" == componentRow.Guid)
805 {
806 if (null == componentRow.KeyPath || componentRow.IsOdbcDataSourceKeyPath)
807 {
808 Messaging.Instance.OnMessage(WixErrors.IllegalComponentWithAutoGeneratedGuid(componentRow.SourceLineNumbers));
809 }
810 else if (componentRow.IsRegistryKeyPath)
811 {
812 if (null == registryKeyRows)
813 {
814 Table registryTable = output.Tables["Registry"];
815
816 registryKeyRows = new Hashtable(registryTable.Rows.Count);
817
818 foreach (Row registryRow in registryTable.Rows)
819 {
820 registryKeyRows.Add((string)registryRow[0], registryRow);
821 }
822 }
823
824 Row foundRow = registryKeyRows[componentRow.KeyPath] as Row;
825
826 string bitness = componentRow.Is64Bit ? "64" : String.Empty;
827 if (null != foundRow)
828 {
829 string regkey = String.Concat(bitness, foundRow[1], "\\", foundRow[2], "\\", foundRow[3]);
830 componentRow.Guid = Uuid.NewUuid(BindDatabaseCommand.WixComponentGuidNamespace, regkey.ToLowerInvariant()).ToString("B").ToUpperInvariant();
831 }
832 }
833 else // must be a File KeyPath
834 {
835 // if the directory table hasn't been loaded into an indexed hash
836 // of directory ids to target names do that now.
837 if (null == directories)
838 {
839 Table directoryTable = output.Tables["Directory"];
840
841 int numDirectoryTableRows = (null != directoryTable) ? directoryTable.Rows.Count : 0;
842
843 directories = new Hashtable(numDirectoryTableRows);
844
845 // get the target paths for all directories
846 if (null != directoryTable)
847 {
848 foreach (Row row in directoryTable.Rows)
849 {
850 // if the directory Id already exists, we will skip it here since
851 // checking for duplicate primary keys is done later when importing tables
852 // into database
853 if (directories.ContainsKey(row[0]))
854 {
855 continue;
856 }
857
858 string targetName = Installer.GetName((string)row[2], false, true);
859 directories.Add(row[0], new ResolvedDirectory((string)row[1], targetName));
860 }
861 }
862 }
863
864 // if the component id generation seeds have not been indexed
865 // from the WixDirectory table do that now.
866 if (null == componentIdGenSeeds)
867 {
868 Table wixDirectoryTable = output.Tables["WixDirectory"];
869
870 int numWixDirectoryRows = (null != wixDirectoryTable) ? wixDirectoryTable.Rows.Count : 0;
871
872 componentIdGenSeeds = new Hashtable(numWixDirectoryRows);
873
874 // if there are any WixDirectory rows, build up the Component Guid
875 // generation seeds indexed by Directory/@Id.
876 if (null != wixDirectoryTable)
877 {
878 foreach (Row row in wixDirectoryTable.Rows)
879 {
880 componentIdGenSeeds.Add(row[0], (string)row[1]);
881 }
882 }
883 }
884
885 // if the file rows have not been indexed by File.Component yet
886 // then do that now
887 if (null == fileRows)
888 {
889 Table fileTable = output.Tables["File"];
890
891 int numFileRows = (null != fileTable) ? fileTable.Rows.Count : 0;
892
893 fileRows = new Dictionary<string, List<FileRow>>(numFileRows);
894
895 if (null != fileTable)
896 {
897 foreach (FileRow file in fileTable.Rows)
898 {
899 List<FileRow> files;
900 if (!fileRows.TryGetValue(file.Component, out files))
901 {
902 files = new List<FileRow>();
903 fileRows.Add(file.Component, files);
904 }
905
906 files.Add(file);
907 }
908 }
909 }
910
911 // validate component meets all the conditions to have a generated guid
912 List<FileRow> currentComponentFiles = fileRows[componentRow.Component];
913 int numFilesInComponent = currentComponentFiles.Count;
914 string path = null;
915
916 foreach (FileRow fileRow in currentComponentFiles)
917 {
918 if (fileRow.File == componentRow.KeyPath)
919 {
920 // calculate the key file's canonical target path
921 string directoryPath = Binder.GetDirectoryPath(directories, componentIdGenSeeds, componentRow.Directory, true);
922 string fileName = Installer.GetName(fileRow.FileName, false, true).ToLower(CultureInfo.InvariantCulture);
923 path = Path.Combine(directoryPath, fileName);
924
925 // find paths that are not canonicalized
926 if (path.StartsWith(@"PersonalFolder\my pictures", StringComparison.Ordinal) ||
927 path.StartsWith(@"ProgramFilesFolder\common files", StringComparison.Ordinal) ||
928 path.StartsWith(@"ProgramMenuFolder\startup", StringComparison.Ordinal) ||
929 path.StartsWith("TARGETDIR", StringComparison.Ordinal) ||
930 path.StartsWith(@"StartMenuFolder\programs", StringComparison.Ordinal) ||
931 path.StartsWith(@"WindowsFolder\fonts", StringComparison.Ordinal))
932 {
933 Messaging.Instance.OnMessage(WixErrors.IllegalPathForGeneratedComponentGuid(componentRow.SourceLineNumbers, fileRow.Component, path));
934 }
935
936 // if component has more than one file, the key path must be versioned
937 if (1 < numFilesInComponent && String.IsNullOrEmpty(fileRow.Version))
938 {
939 Messaging.Instance.OnMessage(WixErrors.IllegalGeneratedGuidComponentUnversionedKeypath(componentRow.SourceLineNumbers));
940 }
941 }
942 else
943 {
944 // not a key path, so it must be an unversioned file if component has more than one file
945 if (1 < numFilesInComponent && !String.IsNullOrEmpty(fileRow.Version))
946 {
947 Messaging.Instance.OnMessage(WixErrors.IllegalGeneratedGuidComponentVersionedNonkeypath(componentRow.SourceLineNumbers));
948 }
949 }
950 }
951
952 // if the rules were followed, reward with a generated guid
953 if (!Messaging.Instance.EncounteredError)
954 {
955 componentRow.Guid = Uuid.NewUuid(BindDatabaseCommand.WixComponentGuidNamespace, path).ToString("B").ToUpperInvariant();
956 }
957 }
958 }
959 }
960 }
961 }
962
963 /// <summary>
964 /// Creates instance transform substorages in the output.
965 /// </summary>
966 /// <param name="output">Output containing instance transform definitions.</param>
967 private void CreateInstanceTransforms(Output output)
968 {
969 // Create and add substorages for instance transforms.
970 Table wixInstanceTransformsTable = output.Tables["WixInstanceTransforms"];
971 if (null != wixInstanceTransformsTable && 0 <= wixInstanceTransformsTable.Rows.Count)
972 {
973 string targetProductCode = null;
974 string targetUpgradeCode = null;
975 string targetProductVersion = null;
976
977 Table targetSummaryInformationTable = output.Tables["_SummaryInformation"];
978 Table targetPropertyTable = output.Tables["Property"];
979
980 // Get the data from target database
981 foreach (Row propertyRow in targetPropertyTable.Rows)
982 {
983 if ("ProductCode" == (string)propertyRow[0])
984 {
985 targetProductCode = (string)propertyRow[1];
986 }
987 else if ("ProductVersion" == (string)propertyRow[0])
988 {
989 targetProductVersion = (string)propertyRow[1];
990 }
991 else if ("UpgradeCode" == (string)propertyRow[0])
992 {
993 targetUpgradeCode = (string)propertyRow[1];
994 }
995 }
996
997 // Index the Instance Component Rows.
998 Dictionary<string, ComponentRow> instanceComponentGuids = new Dictionary<string, ComponentRow>();
999 Table targetInstanceComponentTable = output.Tables["WixInstanceComponent"];
1000 if (null != targetInstanceComponentTable && 0 < targetInstanceComponentTable.Rows.Count)
1001 {
1002 foreach (Row row in targetInstanceComponentTable.Rows)
1003 {
1004 // Build up all the instances, we'll get the Components rows from the real Component table.
1005 instanceComponentGuids.Add((string)row[0], null);
1006 }
1007
1008 Table targetComponentTable = output.Tables["Component"];
1009 foreach (ComponentRow componentRow in targetComponentTable.Rows)
1010 {
1011 string component = (string)componentRow[0];
1012 if (instanceComponentGuids.ContainsKey(component))
1013 {
1014 instanceComponentGuids[component] = componentRow;
1015 }
1016 }
1017 }
1018
1019 // Generate the instance transforms
1020 foreach (Row instanceRow in wixInstanceTransformsTable.Rows)
1021 {
1022 string instanceId = (string)instanceRow[0];
1023
1024 Output instanceTransform = new Output(instanceRow.SourceLineNumbers);
1025 instanceTransform.Type = OutputType.Transform;
1026 instanceTransform.Codepage = output.Codepage;
1027
1028 Table instanceSummaryInformationTable = instanceTransform.EnsureTable(this.TableDefinitions["_SummaryInformation"]);
1029 string targetPlatformAndLanguage = null;
1030
1031 foreach (Row summaryInformationRow in targetSummaryInformationTable.Rows)
1032 {
1033 if (7 == (int)summaryInformationRow[0]) // PID_TEMPLATE
1034 {
1035 targetPlatformAndLanguage = (string)summaryInformationRow[1];
1036 }
1037
1038 // Copy the row's data to the transform.
1039 Row copyOfSummaryRow = instanceSummaryInformationTable.CreateRow(null);
1040 copyOfSummaryRow[0] = summaryInformationRow[0];
1041 copyOfSummaryRow[1] = summaryInformationRow[1];
1042 }
1043
1044 // Modify the appropriate properties.
1045 Table propertyTable = instanceTransform.EnsureTable(this.TableDefinitions["Property"]);
1046
1047 // Change the ProductCode property
1048 string productCode = (string)instanceRow[2];
1049 if ("*" == productCode)
1050 {
1051 productCode = Common.GenerateGuid();
1052 }
1053
1054 Row productCodeRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers);
1055 productCodeRow.Operation = RowOperation.Modify;
1056 productCodeRow.Fields[1].Modified = true;
1057 productCodeRow[0] = "ProductCode";
1058 productCodeRow[1] = productCode;
1059
1060 // Change the instance property
1061 Row instanceIdRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers);
1062 instanceIdRow.Operation = RowOperation.Modify;
1063 instanceIdRow.Fields[1].Modified = true;
1064 instanceIdRow[0] = (string)instanceRow[1];
1065 instanceIdRow[1] = instanceId;
1066
1067 if (null != instanceRow[3])
1068 {
1069 // Change the ProductName property
1070 Row productNameRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers);
1071 productNameRow.Operation = RowOperation.Modify;
1072 productNameRow.Fields[1].Modified = true;
1073 productNameRow[0] = "ProductName";
1074 productNameRow[1] = (string)instanceRow[3];
1075 }
1076
1077 if (null != instanceRow[4])
1078 {
1079 // Change the UpgradeCode property
1080 Row upgradeCodeRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers);
1081 upgradeCodeRow.Operation = RowOperation.Modify;
1082 upgradeCodeRow.Fields[1].Modified = true;
1083 upgradeCodeRow[0] = "UpgradeCode";
1084 upgradeCodeRow[1] = instanceRow[4];
1085
1086 // Change the Upgrade table
1087 Table targetUpgradeTable = output.Tables["Upgrade"];
1088 if (null != targetUpgradeTable && 0 <= targetUpgradeTable.Rows.Count)
1089 {
1090 string upgradeId = (string)instanceRow[4];
1091 Table upgradeTable = instanceTransform.EnsureTable(this.TableDefinitions["Upgrade"]);
1092 foreach (Row row in targetUpgradeTable.Rows)
1093 {
1094 // In case they are upgrading other codes to this new product, leave the ones that don't match the
1095 // Product.UpgradeCode intact.
1096 if (targetUpgradeCode == (string)row[0])
1097 {
1098 Row upgradeRow = upgradeTable.CreateRow(null);
1099 upgradeRow.Operation = RowOperation.Add;
1100 upgradeRow.Fields[0].Modified = true;
1101 // I was hoping to be able to RowOperation.Modify, but that didn't appear to function.
1102 // upgradeRow.Fields[0].PreviousData = (string)row[0];
1103
1104 // Inserting a new Upgrade record with the updated UpgradeCode
1105 upgradeRow[0] = upgradeId;
1106 upgradeRow[1] = row[1];
1107 upgradeRow[2] = row[2];
1108 upgradeRow[3] = row[3];
1109 upgradeRow[4] = row[4];
1110 upgradeRow[5] = row[5];
1111 upgradeRow[6] = row[6];
1112
1113 // Delete the old row
1114 Row upgradeRemoveRow = upgradeTable.CreateRow(null);
1115 upgradeRemoveRow.Operation = RowOperation.Delete;
1116 upgradeRemoveRow[0] = row[0];
1117 upgradeRemoveRow[1] = row[1];
1118 upgradeRemoveRow[2] = row[2];
1119 upgradeRemoveRow[3] = row[3];
1120 upgradeRemoveRow[4] = row[4];
1121 upgradeRemoveRow[5] = row[5];
1122 upgradeRemoveRow[6] = row[6];
1123 }
1124 }
1125 }
1126 }
1127
1128 // If there are instance Components generate new GUIDs for them.
1129 if (0 < instanceComponentGuids.Count)
1130 {
1131 Table componentTable = instanceTransform.EnsureTable(this.TableDefinitions["Component"]);
1132 foreach (ComponentRow targetComponentRow in instanceComponentGuids.Values)
1133 {
1134 string guid = targetComponentRow.Guid;
1135 if (!String.IsNullOrEmpty(guid))
1136 {
1137 Row instanceComponentRow = componentTable.CreateRow(targetComponentRow.SourceLineNumbers);
1138 instanceComponentRow.Operation = RowOperation.Modify;
1139 instanceComponentRow.Fields[1].Modified = true;
1140 instanceComponentRow[0] = targetComponentRow[0];
1141 instanceComponentRow[1] = Uuid.NewUuid(BindDatabaseCommand.WixComponentGuidNamespace, String.Concat(guid, instanceId)).ToString("B").ToUpper(CultureInfo.InvariantCulture);
1142 instanceComponentRow[2] = targetComponentRow[2];
1143 instanceComponentRow[3] = targetComponentRow[3];
1144 instanceComponentRow[4] = targetComponentRow[4];
1145 instanceComponentRow[5] = targetComponentRow[5];
1146 }
1147 }
1148 }
1149
1150 // Update the summary information
1151 Hashtable summaryRows = new Hashtable(instanceSummaryInformationTable.Rows.Count);
1152 foreach (Row row in instanceSummaryInformationTable.Rows)
1153 {
1154 summaryRows[row[0]] = row;
1155
1156 if ((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == (int)row[0])
1157 {
1158 row[1] = targetPlatformAndLanguage;
1159 }
1160 else if ((int)SummaryInformation.Transform.ProductCodes == (int)row[0])
1161 {
1162 row[1] = String.Concat(targetProductCode, targetProductVersion, ';', productCode, targetProductVersion, ';', targetUpgradeCode);
1163 }
1164 else if ((int)SummaryInformation.Transform.ValidationFlags == (int)row[0])
1165 {
1166 row[1] = 0;
1167 }
1168 else if ((int)SummaryInformation.Transform.Security == (int)row[0])
1169 {
1170 row[1] = "4";
1171 }
1172 }
1173
1174 if (!summaryRows.Contains((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage))
1175 {
1176 Row summaryRow = instanceSummaryInformationTable.CreateRow(null);
1177 summaryRow[0] = (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage;
1178 summaryRow[1] = targetPlatformAndLanguage;
1179 }
1180 else if (!summaryRows.Contains((int)SummaryInformation.Transform.ValidationFlags))
1181 {
1182 Row summaryRow = instanceSummaryInformationTable.CreateRow(null);
1183 summaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags;
1184 summaryRow[1] = "0";
1185 }
1186 else if (!summaryRows.Contains((int)SummaryInformation.Transform.Security))
1187 {
1188 Row summaryRow = instanceSummaryInformationTable.CreateRow(null);
1189 summaryRow[0] = (int)SummaryInformation.Transform.Security;
1190 summaryRow[1] = "4";
1191 }
1192
1193 output.SubStorages.Add(new SubStorage(instanceId, instanceTransform));
1194 }
1195 }
1196 }
1197
1198 /// <summary>
1199 /// Validate that there are no duplicate GUIDs in the output.
1200 /// </summary>
1201 /// <remarks>
1202 /// Duplicate GUIDs without conditions are an error condition; with conditions, it's a
1203 /// warning, as the conditions might be mutually exclusive.
1204 /// </remarks>
1205 private void ValidateComponentGuids(Output output)
1206 {
1207 Table componentTable = output.Tables["Component"];
1208 if (null != componentTable)
1209 {
1210 Dictionary<string, bool> componentGuidConditions = new Dictionary<string, bool>(componentTable.Rows.Count);
1211
1212 foreach (ComponentRow row in componentTable.Rows)
1213 {
1214 // we don't care about unmanaged components and if there's a * GUID remaining,
1215 // there's already an error that prevented it from being replaced with a real GUID.
1216 if (!String.IsNullOrEmpty(row.Guid) && "*" != row.Guid)
1217 {
1218 bool thisComponentHasCondition = !String.IsNullOrEmpty(row.Condition);
1219 bool allComponentsHaveConditions = thisComponentHasCondition;
1220
1221 if (componentGuidConditions.ContainsKey(row.Guid))
1222 {
1223 allComponentsHaveConditions = componentGuidConditions[row.Guid] && thisComponentHasCondition;
1224
1225 if (allComponentsHaveConditions)
1226 {
1227 Messaging.Instance.OnMessage(WixWarnings.DuplicateComponentGuidsMustHaveMutuallyExclusiveConditions(row.SourceLineNumbers, row.Component, row.Guid));
1228 }
1229 else
1230 {
1231 Messaging.Instance.OnMessage(WixErrors.DuplicateComponentGuids(row.SourceLineNumbers, row.Component, row.Guid));
1232 }
1233 }
1234
1235 componentGuidConditions[row.Guid] = allComponentsHaveConditions;
1236 }
1237 }
1238 }
1239 }
1240
1241 /// <summary>
1242 /// Update Control and BBControl text by reading from files when necessary.
1243 /// </summary>
1244 /// <param name="output">Internal representation of the msi database to operate upon.</param>
1245 private void UpdateControlText(Output output)
1246 {
1247 UpdateControlTextCommand command = new UpdateControlTextCommand();
1248 command.BBControlTable = output.Tables["BBControl"];
1249 command.WixBBControlTable = output.Tables["WixBBControl"];
1250 command.ControlTable = output.Tables["Control"];
1251 command.WixControlTable = output.Tables["WixControl"];
1252 command.Execute();
1253 }
1254
1255 private string ResolveMedia(MediaRow mediaRow, string mediaLayoutDirectory, string layoutDirectory)
1256 {
1257 string layout = null;
1258
1259 foreach (IBinderFileManager fileManager in this.FileManagers)
1260 {
1261 layout = fileManager.ResolveMedia(mediaRow, mediaLayoutDirectory, layoutDirectory);
1262 if (!String.IsNullOrEmpty(layout))
1263 {
1264 break;
1265 }
1266 }
1267
1268 // If no binder file manager resolved the layout, do the default behavior.
1269 if (String.IsNullOrEmpty(layout))
1270 {
1271 if (String.IsNullOrEmpty(mediaLayoutDirectory))
1272 {
1273 layout = layoutDirectory;
1274 }
1275 else if (Path.IsPathRooted(mediaLayoutDirectory))
1276 {
1277 layout = mediaLayoutDirectory;
1278 }
1279 else
1280 {
1281 layout = Path.Combine(layoutDirectory, mediaLayoutDirectory);
1282 }
1283 }
1284
1285 return layout;
1286 }
1287
1288 /// <summary>
1289 /// Creates the MSI/MSM/PCP database.
1290 /// </summary>
1291 /// <param name="output">Output to create database for.</param>
1292 /// <param name="databaseFile">The database file to create.</param>
1293 /// <param name="keepAddedColumns">Whether to keep columns added in a transform.</param>
1294 /// <param name="useSubdirectory">Whether to use a subdirectory based on the <paramref name="databaseFile"/> file name for intermediate files.</param>
1295 private void GenerateDatabase(Output output, string databaseFile, bool keepAddedColumns, bool useSubdirectory)
1296 {
1297 GenerateDatabaseCommand command = new GenerateDatabaseCommand();
1298 command.Extensions = this.Extensions;
1299 command.FileManagers = this.FileManagers;
1300 command.Output = output;
1301 command.OutputPath = databaseFile;
1302 command.KeepAddedColumns = keepAddedColumns;
1303 command.UseSubDirectory = useSubdirectory;
1304 command.SuppressAddingValidationRows = this.SuppressAddingValidationRows;
1305 command.TableDefinitions = this.TableDefinitions;
1306 command.TempFilesLocation = this.TempFilesLocation;
1307 command.Codepage = this.Codepage;
1308 command.Execute();
1309 }
1310 }
1311}
diff --git a/src/WixToolset.Core/Bind/BindTransformCommand.cs b/src/WixToolset.Core/Bind/BindTransformCommand.cs
new file mode 100644
index 00000000..e909f191
--- /dev/null
+++ b/src/WixToolset.Core/Bind/BindTransformCommand.cs
@@ -0,0 +1,473 @@
1// Copyright (c) .NET Foundation and 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.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.Extensibility;
11 using WixToolset.Msi;
12 using WixToolset.Core.Native;
13
14 internal class BindTransformCommand : ICommand
15 {
16 public IEnumerable<IBinderExtension> Extensions { private get; set; }
17
18 public IEnumerable<IBinderFileManager> FileManagers { private get; set; }
19
20 public TableDefinitionCollection TableDefinitions { private get; set; }
21
22 public string TempFilesLocation { private get; set; }
23
24 public Output Transform { private get; set; }
25
26 public string OutputPath { private get; set; }
27
28 public void Execute()
29 {
30 int transformFlags = 0;
31
32 Output targetOutput = new Output(null);
33 Output updatedOutput = new Output(null);
34
35 // TODO: handle added columns
36
37 // to generate a localized transform, both the target and updated
38 // databases need to have the same code page. the only reason to
39 // set different code pages is to support localized primary key
40 // columns, but that would only support deleting rows. if this
41 // becomes necessary, define a PreviousCodepage property on the
42 // Output class and persist this throughout transform generation.
43 targetOutput.Codepage = this.Transform.Codepage;
44 updatedOutput.Codepage = this.Transform.Codepage;
45
46 // remove certain Property rows which will be populated from summary information values
47 string targetUpgradeCode = null;
48 string updatedUpgradeCode = null;
49
50 Table propertyTable = this.Transform.Tables["Property"];
51 if (null != propertyTable)
52 {
53 for (int i = propertyTable.Rows.Count - 1; i >= 0; i--)
54 {
55 Row row = propertyTable.Rows[i];
56
57 if ("ProductCode" == (string)row[0] || "ProductLanguage" == (string)row[0] || "ProductVersion" == (string)row[0] || "UpgradeCode" == (string)row[0])
58 {
59 propertyTable.Rows.RemoveAt(i);
60
61 if ("UpgradeCode" == (string)row[0])
62 {
63 updatedUpgradeCode = (string)row[1];
64 }
65 }
66 }
67 }
68
69 Table targetSummaryInfo = targetOutput.EnsureTable(this.TableDefinitions["_SummaryInformation"]);
70 Table updatedSummaryInfo = updatedOutput.EnsureTable(this.TableDefinitions["_SummaryInformation"]);
71 Table targetPropertyTable = targetOutput.EnsureTable(this.TableDefinitions["Property"]);
72 Table updatedPropertyTable = updatedOutput.EnsureTable(this.TableDefinitions["Property"]);
73
74 // process special summary information values
75 foreach (Row row in this.Transform.Tables["_SummaryInformation"].Rows)
76 {
77 if ((int)SummaryInformation.Transform.CodePage == (int)row[0])
78 {
79 // convert from a web name if provided
80 string codePage = (string)row.Fields[1].Data;
81 if (null == codePage)
82 {
83 codePage = "0";
84 }
85 else
86 {
87 codePage = Common.GetValidCodePage(codePage).ToString(CultureInfo.InvariantCulture);
88 }
89
90 string previousCodePage = (string)row.Fields[1].PreviousData;
91 if (null == previousCodePage)
92 {
93 previousCodePage = "0";
94 }
95 else
96 {
97 previousCodePage = Common.GetValidCodePage(previousCodePage).ToString(CultureInfo.InvariantCulture);
98 }
99
100 Row targetCodePageRow = targetSummaryInfo.CreateRow(null);
101 targetCodePageRow[0] = 1; // PID_CODEPAGE
102 targetCodePageRow[1] = previousCodePage;
103
104 Row updatedCodePageRow = updatedSummaryInfo.CreateRow(null);
105 updatedCodePageRow[0] = 1; // PID_CODEPAGE
106 updatedCodePageRow[1] = codePage;
107 }
108 else if ((int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0] ||
109 (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == (int)row[0])
110 {
111 // the target language
112 string[] propertyData = ((string)row[1]).Split(';');
113 string lang = 2 == propertyData.Length ? propertyData[1] : "0";
114
115 Table tempSummaryInfo = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0] ? targetSummaryInfo : updatedSummaryInfo;
116 Table tempPropertyTable = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0] ? targetPropertyTable : updatedPropertyTable;
117
118 Row productLanguageRow = tempPropertyTable.CreateRow(null);
119 productLanguageRow[0] = "ProductLanguage";
120 productLanguageRow[1] = lang;
121
122 // set the platform;language on the MSI to be generated
123 Row templateRow = tempSummaryInfo.CreateRow(null);
124 templateRow[0] = 7; // PID_TEMPLATE
125 templateRow[1] = (string)row[1];
126 }
127 else if ((int)SummaryInformation.Transform.ProductCodes == (int)row[0])
128 {
129 string[] propertyData = ((string)row[1]).Split(';');
130
131 Row targetProductCodeRow = targetPropertyTable.CreateRow(null);
132 targetProductCodeRow[0] = "ProductCode";
133 targetProductCodeRow[1] = propertyData[0].Substring(0, 38);
134
135 Row targetProductVersionRow = targetPropertyTable.CreateRow(null);
136 targetProductVersionRow[0] = "ProductVersion";
137 targetProductVersionRow[1] = propertyData[0].Substring(38);
138
139 Row updatedProductCodeRow = updatedPropertyTable.CreateRow(null);
140 updatedProductCodeRow[0] = "ProductCode";
141 updatedProductCodeRow[1] = propertyData[1].Substring(0, 38);
142
143 Row updatedProductVersionRow = updatedPropertyTable.CreateRow(null);
144 updatedProductVersionRow[0] = "ProductVersion";
145 updatedProductVersionRow[1] = propertyData[1].Substring(38);
146
147 // UpgradeCode is optional and may not exists in the target
148 // or upgraded databases, so do not include a null-valued
149 // UpgradeCode property.
150
151 targetUpgradeCode = propertyData[2];
152 if (!String.IsNullOrEmpty(targetUpgradeCode))
153 {
154 Row targetUpgradeCodeRow = targetPropertyTable.CreateRow(null);
155 targetUpgradeCodeRow[0] = "UpgradeCode";
156 targetUpgradeCodeRow[1] = targetUpgradeCode;
157
158 // If the target UpgradeCode is specified, an updated
159 // UpgradeCode is required.
160 if (String.IsNullOrEmpty(updatedUpgradeCode))
161 {
162 updatedUpgradeCode = targetUpgradeCode;
163 }
164 }
165
166 if (!String.IsNullOrEmpty(updatedUpgradeCode))
167 {
168 Row updatedUpgradeCodeRow = updatedPropertyTable.CreateRow(null);
169 updatedUpgradeCodeRow[0] = "UpgradeCode";
170 updatedUpgradeCodeRow[1] = updatedUpgradeCode;
171 }
172 }
173 else if ((int)SummaryInformation.Transform.ValidationFlags == (int)row[0])
174 {
175 transformFlags = Convert.ToInt32(row[1], CultureInfo.InvariantCulture);
176 }
177 else if ((int)SummaryInformation.Transform.Reserved11 == (int)row[0])
178 {
179 // PID_LASTPRINTED should be null for transforms
180 row.Operation = RowOperation.None;
181 }
182 else
183 {
184 // add everything else as is
185 Row targetRow = targetSummaryInfo.CreateRow(null);
186 targetRow[0] = row[0];
187 targetRow[1] = row[1];
188
189 Row updatedRow = updatedSummaryInfo.CreateRow(null);
190 updatedRow[0] = row[0];
191 updatedRow[1] = row[1];
192 }
193 }
194
195 // Validate that both databases have an UpgradeCode if the
196 // authoring transform will validate the UpgradeCode; otherwise,
197 // MsiCreateTransformSummaryinfo() will fail with 1620.
198 if (((int)TransformFlags.ValidateUpgradeCode & transformFlags) != 0 &&
199 (String.IsNullOrEmpty(targetUpgradeCode) || String.IsNullOrEmpty(updatedUpgradeCode)))
200 {
201 Messaging.Instance.OnMessage(WixErrors.BothUpgradeCodesRequired());
202 }
203
204 string emptyFile = null;
205
206 foreach (Table table in this.Transform.Tables)
207 {
208 // Ignore unreal tables when building transforms except the _Stream table.
209 // These tables are ignored when generating the database so there is no reason
210 // to process them here.
211 if (table.Definition.Unreal && "_Streams" != table.Name)
212 {
213 continue;
214 }
215
216 // process table operations
217 switch (table.Operation)
218 {
219 case TableOperation.Add:
220 updatedOutput.EnsureTable(table.Definition);
221 break;
222 case TableOperation.Drop:
223 targetOutput.EnsureTable(table.Definition);
224 continue;
225 default:
226 targetOutput.EnsureTable(table.Definition);
227 updatedOutput.EnsureTable(table.Definition);
228 break;
229 }
230
231 // process row operations
232 foreach (Row row in table.Rows)
233 {
234 switch (row.Operation)
235 {
236 case RowOperation.Add:
237 Table updatedTable = updatedOutput.EnsureTable(table.Definition);
238 updatedTable.Rows.Add(row);
239 continue;
240 case RowOperation.Delete:
241 Table targetTable = targetOutput.EnsureTable(table.Definition);
242 targetTable.Rows.Add(row);
243
244 // fill-in non-primary key values
245 foreach (Field field in row.Fields)
246 {
247 if (!field.Column.PrimaryKey)
248 {
249 if (ColumnType.Number == field.Column.Type && !field.Column.IsLocalizable)
250 {
251 field.Data = field.Column.MinValue;
252 }
253 else if (ColumnType.Object == field.Column.Type)
254 {
255 if (null == emptyFile)
256 {
257 emptyFile = Path.Combine(this.TempFilesLocation, "empty");
258 }
259
260 field.Data = emptyFile;
261 }
262 else
263 {
264 field.Data = "0";
265 }
266 }
267 }
268 continue;
269 }
270
271 // Assure that the file table's sequence is populated
272 if ("File" == table.Name)
273 {
274 foreach (Row fileRow in table.Rows)
275 {
276 if (null == fileRow[7])
277 {
278 if (RowOperation.Add == fileRow.Operation)
279 {
280 Messaging.Instance.OnMessage(WixErrors.InvalidAddedFileRowWithoutSequence(fileRow.SourceLineNumbers, (string)fileRow[0]));
281 break;
282 }
283
284 // Set to 1 to prevent invalid IDT file from being generated
285 fileRow[7] = 1;
286 }
287 }
288 }
289
290 // process modified and unmodified rows
291 bool modifiedRow = false;
292 Row targetRow = new Row(null, table.Definition);
293 Row updatedRow = row;
294 for (int i = 0; i < row.Fields.Length; i++)
295 {
296 Field updatedField = row.Fields[i];
297
298 if (updatedField.Modified)
299 {
300 // set a different value in the target row to ensure this value will be modified during transform generation
301 if (ColumnType.Number == updatedField.Column.Type && !updatedField.Column.IsLocalizable)
302 {
303 if (null == updatedField.Data || 1 != (int)updatedField.Data)
304 {
305 targetRow[i] = 1;
306 }
307 else
308 {
309 targetRow[i] = 2;
310 }
311 }
312 else if (ColumnType.Object == updatedField.Column.Type)
313 {
314 if (null == emptyFile)
315 {
316 emptyFile = Path.Combine(this.TempFilesLocation, "empty");
317 }
318
319 targetRow[i] = emptyFile;
320 }
321 else
322 {
323 if ("0" != (string)updatedField.Data)
324 {
325 targetRow[i] = "0";
326 }
327 else
328 {
329 targetRow[i] = "1";
330 }
331 }
332
333 modifiedRow = true;
334 }
335 else if (ColumnType.Object == updatedField.Column.Type)
336 {
337 ObjectField objectField = (ObjectField)updatedField;
338
339 // create an empty file for comparing against
340 if (null == objectField.PreviousData)
341 {
342 if (null == emptyFile)
343 {
344 emptyFile = Path.Combine(this.TempFilesLocation, "empty");
345 }
346
347 targetRow[i] = emptyFile;
348 modifiedRow = true;
349 }
350 else if (!this.CompareFiles(objectField.PreviousData, (string)objectField.Data))
351 {
352 targetRow[i] = objectField.PreviousData;
353 modifiedRow = true;
354 }
355 }
356 else // unmodified
357 {
358 if (null != updatedField.Data)
359 {
360 targetRow[i] = updatedField.Data;
361 }
362 }
363 }
364
365 // modified rows and certain special rows go in the target and updated msi databases
366 if (modifiedRow ||
367 ("Property" == table.Name &&
368 ("ProductCode" == (string)row[0] ||
369 "ProductLanguage" == (string)row[0] ||
370 "ProductVersion" == (string)row[0] ||
371 "UpgradeCode" == (string)row[0])))
372 {
373 Table targetTable = targetOutput.EnsureTable(table.Definition);
374 targetTable.Rows.Add(targetRow);
375
376 Table updatedTable = updatedOutput.EnsureTable(table.Definition);
377 updatedTable.Rows.Add(updatedRow);
378 }
379 }
380 }
381
382 foreach (BinderExtension extension in this.Extensions)
383 {
384 extension.Finish(this.Transform);
385 }
386
387 // Any errors encountered up to this point can cause errors during generation.
388 if (Messaging.Instance.EncounteredError)
389 {
390 return;
391 }
392
393 string transformFileName = Path.GetFileNameWithoutExtension(this.OutputPath);
394 string targetDatabaseFile = Path.Combine(this.TempFilesLocation, String.Concat(transformFileName, "_target.msi"));
395 string updatedDatabaseFile = Path.Combine(this.TempFilesLocation, String.Concat(transformFileName, "_updated.msi"));
396
397 try
398 {
399 if (!String.IsNullOrEmpty(emptyFile))
400 {
401 using (FileStream fileStream = File.Create(emptyFile))
402 {
403 }
404 }
405
406 this.GenerateDatabase(targetOutput, targetDatabaseFile, false);
407 this.GenerateDatabase(updatedOutput, updatedDatabaseFile, true);
408
409 // make sure the directory exists
410 Directory.CreateDirectory(Path.GetDirectoryName(this.OutputPath));
411
412 // create the transform file
413 using (Database targetDatabase = new Database(targetDatabaseFile, OpenDatabase.ReadOnly))
414 {
415 using (Database updatedDatabase = new Database(updatedDatabaseFile, OpenDatabase.ReadOnly))
416 {
417 if (updatedDatabase.GenerateTransform(targetDatabase, this.OutputPath))
418 {
419 updatedDatabase.CreateTransformSummaryInfo(targetDatabase, this.OutputPath, (TransformErrorConditions)(transformFlags & 0xFFFF), (TransformValidations)((transformFlags >> 16) & 0xFFFF));
420 }
421 else
422 {
423 Messaging.Instance.OnMessage(WixErrors.NoDifferencesInTransform(this.Transform.SourceLineNumbers));
424 }
425 }
426 }
427 }
428 finally
429 {
430 if (!String.IsNullOrEmpty(emptyFile))
431 {
432 File.Delete(emptyFile);
433 }
434 }
435 }
436
437 private bool CompareFiles(string targetFile, string updatedFile)
438 {
439 bool? compared = null;
440 foreach (IBinderFileManager fileManager in this.FileManagers)
441 {
442 compared = fileManager.CompareFiles(targetFile, updatedFile);
443 if (compared.HasValue)
444 {
445 break;
446 }
447 }
448
449 if (!compared.HasValue)
450 {
451 throw new InvalidOperationException(); // TODO: something needs to be said here that none of the binder file managers returned a result.
452 }
453
454 return compared.Value;
455 }
456
457 private void GenerateDatabase(Output output, string outputPath, bool keepAddedColumns)
458 {
459 GenerateDatabaseCommand command = new GenerateDatabaseCommand();
460 command.Codepage = output.Codepage;
461 command.Extensions = this.Extensions;
462 command.FileManagers = this.FileManagers;
463 command.KeepAddedColumns = keepAddedColumns;
464 command.Output = output;
465 command.OutputPath = outputPath;
466 command.TableDefinitions = this.TableDefinitions;
467 command.TempFilesLocation = this.TempFilesLocation;
468 command.SuppressAddingValidationRows = true;
469 command.UseSubDirectory = true;
470 command.Execute();
471 }
472 }
473}
diff --git a/src/WixToolset.Core/Bind/Bundles/AutomaticallySlipstreamPatchesCommand.cs b/src/WixToolset.Core/Bind/Bundles/AutomaticallySlipstreamPatchesCommand.cs
new file mode 100644
index 00000000..eb02a983
--- /dev/null
+++ b/src/WixToolset.Core/Bind/Bundles/AutomaticallySlipstreamPatchesCommand.cs
@@ -0,0 +1,112 @@
1// Copyright (c) .NET Foundation and 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.Bind.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.Rows;
11
12 internal class AutomaticallySlipstreamPatchesCommand : ICommand
13 {
14 public IEnumerable<PackageFacade> PackageFacades { private get; set; }
15
16 public Table WixBundlePatchTargetCodeTable { private get; set; }
17
18 public Table SlipstreamMspTable { private get; set; }
19
20 public void Execute()
21 {
22 List<WixBundleMsiPackageRow> msiPackages = new List<WixBundleMsiPackageRow>();
23 Dictionary<string, List<WixBundlePatchTargetCodeRow>> targetsProductCode = new Dictionary<string, List<WixBundlePatchTargetCodeRow>>();
24 Dictionary<string, List<WixBundlePatchTargetCodeRow>> targetsUpgradeCode = new Dictionary<string, List<WixBundlePatchTargetCodeRow>>();
25
26 foreach (PackageFacade facade in this.PackageFacades)
27 {
28 if (WixBundlePackageType.Msi == facade.Package.Type)
29 {
30 // Keep track of all MSI packages.
31 msiPackages.Add(facade.MsiPackage);
32 }
33 else if (WixBundlePackageType.Msp == facade.Package.Type && facade.MspPackage.Slipstream)
34 {
35 IEnumerable<WixBundlePatchTargetCodeRow> patchTargetCodeRows = this.WixBundlePatchTargetCodeTable.RowsAs<WixBundlePatchTargetCodeRow>().Where(r => r.MspPackageId == facade.Package.WixChainItemId);
36
37 // Index target ProductCodes and UpgradeCodes for slipstreamed MSPs.
38 foreach (WixBundlePatchTargetCodeRow row in patchTargetCodeRows)
39 {
40 if (row.TargetsProductCode)
41 {
42 List<WixBundlePatchTargetCodeRow> rows;
43 if (!targetsProductCode.TryGetValue(row.TargetCode, out rows))
44 {
45 rows = new List<WixBundlePatchTargetCodeRow>();
46 targetsProductCode.Add(row.TargetCode, rows);
47 }
48
49 rows.Add(row);
50 }
51 else if (row.TargetsUpgradeCode)
52 {
53 List<WixBundlePatchTargetCodeRow> rows;
54 if (!targetsUpgradeCode.TryGetValue(row.TargetCode, out rows))
55 {
56 rows = new List<WixBundlePatchTargetCodeRow>();
57 targetsUpgradeCode.Add(row.TargetCode, rows);
58 }
59 }
60 }
61 }
62 }
63
64 RowIndexedList<Row> slipstreamMspRows = new RowIndexedList<Row>(SlipstreamMspTable);
65
66 // Loop through the MSI and slipstream patches targeting it.
67 foreach (WixBundleMsiPackageRow msi in msiPackages)
68 {
69 List<WixBundlePatchTargetCodeRow> rows;
70 if (targetsProductCode.TryGetValue(msi.ProductCode, out rows))
71 {
72 foreach (WixBundlePatchTargetCodeRow row in rows)
73 {
74 Debug.Assert(row.TargetsProductCode);
75 Debug.Assert(!row.TargetsUpgradeCode);
76
77 Row slipstreamMspRow = SlipstreamMspTable.CreateRow(row.SourceLineNumbers, false);
78 slipstreamMspRow[0] = msi.ChainPackageId;
79 slipstreamMspRow[1] = row.MspPackageId;
80
81 if (slipstreamMspRows.TryAdd(slipstreamMspRow))
82 {
83 SlipstreamMspTable.Rows.Add(slipstreamMspRow);
84 }
85 }
86
87 rows = null;
88 }
89
90 if (!String.IsNullOrEmpty(msi.UpgradeCode) && targetsUpgradeCode.TryGetValue(msi.UpgradeCode, out rows))
91 {
92 foreach (WixBundlePatchTargetCodeRow row in rows)
93 {
94 Debug.Assert(!row.TargetsProductCode);
95 Debug.Assert(row.TargetsUpgradeCode);
96
97 Row slipstreamMspRow = SlipstreamMspTable.CreateRow(row.SourceLineNumbers, false);
98 slipstreamMspRow[0] = msi.ChainPackageId;
99 slipstreamMspRow[1] = row.MspPackageId;
100
101 if (slipstreamMspRows.TryAdd(slipstreamMspRow))
102 {
103 SlipstreamMspTable.Rows.Add(slipstreamMspRow);
104 }
105 }
106
107 rows = null;
108 }
109 }
110 }
111 }
112}
diff --git a/src/WixToolset.Core/Bind/Bundles/BurnCommon.cs b/src/WixToolset.Core/Bind/Bundles/BurnCommon.cs
new file mode 100644
index 00000000..8cb07791
--- /dev/null
+++ b/src/WixToolset.Core/Bind/Bundles/BurnCommon.cs
@@ -0,0 +1,378 @@
1// Copyright (c) .NET Foundation and 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.Bind.Bundles
4{
5 using System;
6 using System.Diagnostics;
7 using System.IO;
8 using WixToolset.Data;
9
10 /// <summary>
11 /// Common functionality for Burn PE Writer & Reader for the WiX toolset.
12 /// </summary>
13 /// <remarks>This class encapsulates common functionality related to
14 /// bundled/chained setup packages.</remarks>
15 /// <example>
16 /// </example>
17 internal abstract class BurnCommon : IDisposable
18 {
19 public const string BurnNamespace = "http://wixtoolset.org/schemas/v4/2008/Burn";
20 public const string BurnUXContainerEmbeddedIdFormat = "u{0}";
21 public const string BurnUXContainerPayloadIdFormat = "p{0}";
22 public const string BurnAttachedContainerEmbeddedIdFormat = "a{0}";
23
24 // See WinNT.h for details about the PE format, including the
25 // structure and offsets for IMAGE_DOS_HEADER, IMAGE_NT_HEADERS32,
26 // IMAGE_FILE_HEADER, etc.
27 protected const UInt32 IMAGE_DOS_HEADER_SIZE = 64;
28 protected const UInt32 IMAGE_DOS_HEADER_OFFSET_MAGIC = 0;
29 protected const UInt32 IMAGE_DOS_HEADER_OFFSET_NTHEADER = 60;
30
31 protected const UInt32 IMAGE_NT_HEADER_SIZE = 24; // signature DWORD (4) + IMAGE_FILE_HEADER (20)
32 protected const UInt32 IMAGE_NT_HEADER_OFFSET_SIGNATURE = 0;
33 protected const UInt32 IMAGE_NT_HEADER_OFFSET_NUMBEROFSECTIONS = 6;
34 protected const UInt32 IMAGE_NT_HEADER_OFFSET_SIZEOFOPTIONALHEADER = 20;
35
36 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.
37 protected const UInt32 IMAGE_OPTIONAL_NEGATIVE_OFFSET_CERTIFICATETABLE = (IMAGE_DATA_DIRECTORY_SIZE * (IMAGE_NUMBEROF_DIRECTORY_ENTRIES - IMAGE_DIRECTORY_ENTRY_SECURITY));
38
39 protected const UInt32 IMAGE_SECTION_HEADER_SIZE = 40;
40 protected const UInt32 IMAGE_SECTION_HEADER_OFFSET_NAME = 0;
41 protected const UInt32 IMAGE_SECTION_HEADER_OFFSET_VIRTUALSIZE = 8;
42 protected const UInt32 IMAGE_SECTION_HEADER_OFFSET_SIZEOFRAWDATA = 16;
43 protected const UInt32 IMAGE_SECTION_HEADER_OFFSET_POINTERTORAWDATA = 20;
44
45 protected const UInt32 IMAGE_DATA_DIRECTORY_SIZE = 8; // struct of two DWORDs.
46 protected const UInt32 IMAGE_DIRECTORY_ENTRY_SECURITY = 4;
47 protected const UInt32 IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16;
48
49 protected const UInt16 IMAGE_DOS_SIGNATURE = 0x5A4D;
50 protected const UInt32 IMAGE_NT_SIGNATURE = 0x00004550;
51 protected const UInt64 IMAGE_SECTION_WIXBURN_NAME = 0x6E7275627869772E; // ".wixburn", as a qword.
52
53 // The ".wixburn" section contains:
54 // 0- 3: magic number
55 // 4- 7: version
56 // 8-23: bundle GUID
57 // 24-27: engine (stub) size
58 // 28-31: original checksum
59 // 32-35: original signature offset
60 // 36-39: original signature size
61 // 40-43: container type (1 = CAB)
62 // 44-47: container count
63 // 48-51: byte count of manifest + UX container
64 // 52-55: byte count of attached container
65 protected const UInt32 BURN_SECTION_OFFSET_MAGIC = 0;
66 protected const UInt32 BURN_SECTION_OFFSET_VERSION = 4;
67 protected const UInt32 BURN_SECTION_OFFSET_BUNDLEGUID = 8;
68 protected const UInt32 BURN_SECTION_OFFSET_STUBSIZE = 24;
69 protected const UInt32 BURN_SECTION_OFFSET_ORIGINALCHECKSUM = 28;
70 protected const UInt32 BURN_SECTION_OFFSET_ORIGINALSIGNATUREOFFSET = 32;
71 protected const UInt32 BURN_SECTION_OFFSET_ORIGINALSIGNATURESIZE = 36;
72 protected const UInt32 BURN_SECTION_OFFSET_FORMAT = 40;
73 protected const UInt32 BURN_SECTION_OFFSET_COUNT = 44;
74 protected const UInt32 BURN_SECTION_OFFSET_UXSIZE = 48;
75 protected const UInt32 BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE = 52;
76 protected const UInt32 BURN_SECTION_SIZE = BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE + 4; // last field + sizeof(DWORD)
77
78 protected const UInt32 BURN_SECTION_MAGIC = 0x00f14300;
79 protected const UInt32 BURN_SECTION_VERSION = 0x00000002;
80
81 protected string fileExe;
82 protected UInt32 peOffset = UInt32.MaxValue;
83 protected UInt16 sections = UInt16.MaxValue;
84 protected UInt32 firstSectionOffset = UInt32.MaxValue;
85 protected UInt32 checksumOffset;
86 protected UInt32 certificateTableSignatureOffset;
87 protected UInt32 certificateTableSignatureSize;
88 protected UInt32 wixburnDataOffset = UInt32.MaxValue;
89
90 // TODO: does this enum exist in another form somewhere?
91 /// <summary>
92 /// The types of attached containers that BurnWriter supports.
93 /// </summary>
94 public enum Container
95 {
96 Nothing = 0,
97 UX,
98 Attached
99 }
100
101 /// <summary>
102 /// Creates a BurnCommon for re-writing a PE file.
103 /// </summary>
104 /// <param name="fileExe">File to modify in-place.</param>
105 /// <param name="bundleGuid">GUID for the bundle.</param>
106 public BurnCommon(string fileExe)
107 {
108 this.fileExe = fileExe;
109 }
110
111 public UInt32 Checksum { get; protected set; }
112 public UInt32 SignatureOffset { get; protected set; }
113 public UInt32 SignatureSize { get; protected set; }
114 public UInt32 Version { get; protected set; }
115 public UInt32 StubSize { get; protected set; }
116 public UInt32 OriginalChecksum { get; protected set; }
117 public UInt32 OriginalSignatureOffset { get; protected set; }
118 public UInt32 OriginalSignatureSize { get; protected set; }
119 public UInt32 EngineSize { get; protected set; }
120 public UInt32 ContainerCount { get; protected set; }
121 public UInt32 UXAddress { get; protected set; }
122 public UInt32 UXSize { get; protected set; }
123 public UInt32 AttachedContainerAddress { get; protected set; }
124 public UInt32 AttachedContainerSize { get; protected set; }
125
126 public void Dispose()
127 {
128 Dispose(true);
129
130 GC.SuppressFinalize(this);
131 }
132
133 /// <summary>
134 /// Copies one stream to another.
135 /// </summary>
136 /// <param name="input">Input stream.</param>
137 /// <param name="output">Output stream.</param>
138 /// <param name="size">Optional count of bytes to copy. 0 indicates whole input stream from current should be copied.</param>
139 protected static int CopyStream(Stream input, Stream output, int size)
140 {
141 byte[] bytes = new byte[4096];
142 int total = 0;
143 int read = 0;
144 do
145 {
146 read = Math.Min(bytes.Length, size - total);
147 read = input.Read(bytes, 0, read);
148 if (0 == read)
149 {
150 break;
151 }
152
153 output.Write(bytes, 0, read);
154 total += read;
155 } while (0 == size || total < size);
156
157 return total;
158 }
159
160 /// <summary>
161 /// Initialize the common information about a Burn engine.
162 /// </summary>
163 /// <param name="reader">Binary reader open against a Burn engine.</param>
164 /// <returns>True if initialized.</returns>
165 protected bool Initialize(BinaryReader reader)
166 {
167 if (!GetWixburnSectionInfo(reader))
168 {
169 return false;
170 }
171
172 reader.BaseStream.Seek(this.wixburnDataOffset, SeekOrigin.Begin);
173 byte[] bytes = reader.ReadBytes((int)BURN_SECTION_SIZE);
174 UInt32 uint32 = 0;
175
176 uint32 = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_MAGIC);
177 if (BURN_SECTION_MAGIC != uint32)
178 {
179 Messaging.Instance.OnMessage(WixErrors.InvalidBundle(this.fileExe));
180 return false;
181 }
182
183 this.Version = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_VERSION);
184 if (BURN_SECTION_VERSION != this.Version)
185 {
186 Messaging.Instance.OnMessage(WixErrors.BundleTooNew(this.fileExe, this.Version));
187 return false;
188 }
189
190 uint32 = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_FORMAT); // We only know how to deal with CABs right now
191 if (1 != uint32)
192 {
193 Messaging.Instance.OnMessage(WixErrors.InvalidBundle(this.fileExe));
194 return false;
195 }
196
197 this.StubSize = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_STUBSIZE);
198 this.OriginalChecksum = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_ORIGINALCHECKSUM);
199 this.OriginalSignatureOffset = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_ORIGINALSIGNATUREOFFSET);
200 this.OriginalSignatureSize = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_ORIGINALSIGNATURESIZE);
201
202 this.ContainerCount = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_COUNT);
203 this.UXAddress = this.StubSize;
204 this.UXSize = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_UXSIZE);
205
206 // If there is an original signature use that to determine the engine size.
207 if (0 < this.OriginalSignatureOffset)
208 {
209 this.EngineSize = this.OriginalSignatureOffset + this.OriginalSignatureSize;
210 }
211 else if (0 < this.SignatureOffset && 2 > this.ContainerCount) // if there is a signature and no attached containers, use the current signature.
212 {
213 this.EngineSize = this.SignatureOffset + this.SignatureSize;
214 }
215 else // just use the stub and UX container as the size of the engine.
216 {
217 this.EngineSize = this.StubSize + this.UXSize;
218 }
219
220 this.AttachedContainerAddress = this.ContainerCount > 1 ? this.EngineSize : 0;
221 this.AttachedContainerSize = this.ContainerCount > 1 ? BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE) : 0;
222
223 return true;
224 }
225
226 protected virtual void Dispose(bool disposing)
227 {
228 }
229
230 /// <summary>
231 /// Finds the ".wixburn" section in the current exe.
232 /// </summary>
233 /// <returns>true if the ".wixburn" section is successfully found; false otherwise</returns>
234 private bool GetWixburnSectionInfo(BinaryReader reader)
235 {
236 if (UInt32.MaxValue == this.wixburnDataOffset)
237 {
238 if (!EnsureNTHeader(reader))
239 {
240 return false;
241 }
242
243 UInt32 wixburnSectionOffset = UInt32.MaxValue;
244 byte[] bytes = new byte[IMAGE_SECTION_HEADER_SIZE];
245
246 reader.BaseStream.Seek(this.firstSectionOffset, SeekOrigin.Begin);
247 for (UInt16 sectionIndex = 0; sectionIndex < this.sections; ++sectionIndex)
248 {
249 reader.Read(bytes, 0, bytes.Length);
250
251 if (IMAGE_SECTION_WIXBURN_NAME == BurnCommon.ReadUInt64(bytes, IMAGE_SECTION_HEADER_OFFSET_NAME))
252 {
253 wixburnSectionOffset = this.firstSectionOffset + (IMAGE_SECTION_HEADER_SIZE * sectionIndex);
254 break;
255 }
256 }
257
258 if (UInt32.MaxValue == wixburnSectionOffset)
259 {
260 Messaging.Instance.OnMessage(WixErrors.StubMissingWixburnSection(this.fileExe));
261 return false;
262 }
263
264 // we need 56 bytes for the manifest header, which is always going to fit in
265 // the smallest alignment (512 bytes), but just to be paranoid...
266 if (BURN_SECTION_SIZE > BurnCommon.ReadUInt32(bytes, IMAGE_SECTION_HEADER_OFFSET_SIZEOFRAWDATA))
267 {
268 Messaging.Instance.OnMessage(WixErrors.StubWixburnSectionTooSmall(this.fileExe));
269 return false;
270 }
271
272 this.wixburnDataOffset = BurnCommon.ReadUInt32(bytes, IMAGE_SECTION_HEADER_OFFSET_POINTERTORAWDATA);
273 }
274
275 return true;
276 }
277
278 /// <summary>
279 /// Checks for a valid Windows PE signature (IMAGE_NT_SIGNATURE) in the current exe.
280 /// </summary>
281 /// <returns>true if the exe is a Windows executable; false otherwise</returns>
282 private bool EnsureNTHeader(BinaryReader reader)
283 {
284 if (UInt32.MaxValue == this.firstSectionOffset)
285 {
286 if (!EnsureDosHeader(reader))
287 {
288 return false;
289 }
290
291 reader.BaseStream.Seek(this.peOffset, SeekOrigin.Begin);
292 byte[] bytes = reader.ReadBytes((int)IMAGE_NT_HEADER_SIZE);
293
294 // Verify the NT signature...
295 if (IMAGE_NT_SIGNATURE != BurnCommon.ReadUInt32(bytes, IMAGE_NT_HEADER_OFFSET_SIGNATURE))
296 {
297 Messaging.Instance.OnMessage(WixErrors.InvalidStubExe(this.fileExe));
298 return false;
299 }
300
301 ushort sizeOptionalHeader = BurnCommon.ReadUInt16(bytes, IMAGE_NT_HEADER_OFFSET_SIZEOFOPTIONALHEADER);
302
303 this.sections = BurnCommon.ReadUInt16(bytes, IMAGE_NT_HEADER_OFFSET_NUMBEROFSECTIONS);
304 this.firstSectionOffset = this.peOffset + IMAGE_NT_HEADER_SIZE + sizeOptionalHeader;
305
306 this.checksumOffset = this.peOffset + IMAGE_NT_HEADER_SIZE + IMAGE_OPTIONAL_OFFSET_CHECKSUM;
307 this.certificateTableSignatureOffset = this.peOffset + IMAGE_NT_HEADER_SIZE + sizeOptionalHeader - IMAGE_OPTIONAL_NEGATIVE_OFFSET_CERTIFICATETABLE;
308 this.certificateTableSignatureSize = this.certificateTableSignatureOffset + 4; // size is in the DWORD after the offset.
309
310 bytes = reader.ReadBytes(sizeOptionalHeader);
311 this.Checksum = BurnCommon.ReadUInt32(bytes, IMAGE_OPTIONAL_OFFSET_CHECKSUM);
312 this.SignatureOffset = BurnCommon.ReadUInt32(bytes, sizeOptionalHeader - IMAGE_OPTIONAL_NEGATIVE_OFFSET_CERTIFICATETABLE);
313 this.SignatureSize = BurnCommon.ReadUInt32(bytes, sizeOptionalHeader - IMAGE_OPTIONAL_NEGATIVE_OFFSET_CERTIFICATETABLE + 4);
314 }
315
316 return true;
317 }
318
319 /// <summary>
320 /// Checks for a valid DOS header in the current exe.
321 /// </summary>
322 /// <returns>true if the exe starts with a DOS stub; false otherwise</returns>
323 private bool EnsureDosHeader(BinaryReader reader)
324 {
325 if (UInt32.MaxValue == this.peOffset)
326 {
327 byte[] bytes = reader.ReadBytes((int)IMAGE_DOS_HEADER_SIZE);
328
329 // Verify the DOS 'MZ' signature.
330 if (IMAGE_DOS_SIGNATURE != BurnCommon.ReadUInt16(bytes, IMAGE_DOS_HEADER_OFFSET_MAGIC))
331 {
332 Messaging.Instance.OnMessage(WixErrors.InvalidStubExe(this.fileExe));
333 return false;
334 }
335
336 this.peOffset = BurnCommon.ReadUInt32(bytes, IMAGE_DOS_HEADER_OFFSET_NTHEADER);
337 }
338
339 return true;
340 }
341
342 /// <summary>
343 /// Reads a UInt16 value in little-endian format from an offset in an array of bytes.
344 /// </summary>
345 /// <param name="bytes">Array from which to read.</param>
346 /// <param name="offset">Beginning offset from which to read.</param>
347 /// <returns>value at offset</returns>
348 private static UInt16 ReadUInt16(byte[] bytes, UInt32 offset)
349 {
350 Debug.Assert(offset + 2 <= bytes.Length);
351 return (UInt16)(bytes[offset] + (bytes[offset + 1] << 8));
352 }
353
354 /// <summary>
355 /// Reads a UInt32 value in little-endian format from an offset in an array of bytes.
356 /// </summary>
357 /// <param name="bytes">Array from which to read.</param>
358 /// <param name="offset">Beginning offset from which to read.</param>
359 /// <returns>value at offset</returns>
360 private static UInt32 ReadUInt32(byte[] bytes, UInt32 offset)
361 {
362 Debug.Assert(offset + 4 <= bytes.Length);
363 return (UInt32)(bytes[offset] + (bytes[offset + 1] << 8) + (bytes[offset + 2] << 16) + (bytes[offset + 3] << 24));
364 }
365
366 /// <summary>
367 /// Reads a UInt64 value in little-endian format from an offset in an array of bytes.
368 /// </summary>
369 /// <param name="bytes">Array from which to read.</param>
370 /// <param name="offset">Beginning offset from which to read.</param>
371 /// <returns>value at offset</returns>
372 private static UInt64 ReadUInt64(byte[] bytes, UInt32 offset)
373 {
374 Debug.Assert(offset + 8 <= bytes.Length);
375 return BurnCommon.ReadUInt32(bytes, offset) + ((UInt64)(BurnCommon.ReadUInt32(bytes, offset + 4)) << 32);
376 }
377 }
378}
diff --git a/src/WixToolset.Core/Bind/Bundles/BurnReader.cs b/src/WixToolset.Core/Bind/Bundles/BurnReader.cs
new file mode 100644
index 00000000..f6d7a197
--- /dev/null
+++ b/src/WixToolset.Core/Bind/Bundles/BurnReader.cs
@@ -0,0 +1,210 @@
1// Copyright (c) .NET Foundation and 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.Bind.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.Cab;
11
12 /// <summary>
13 /// Burn PE reader for the WiX toolset.
14 /// </summary>
15 /// <remarks>This class encapsulates reading from a stub EXE with containers attached
16 /// for dissecting bundled/chained setup packages.</remarks>
17 /// <example>
18 /// using (BurnReader reader = BurnReader.Open(fileExe, this.core, guid))
19 /// {
20 /// reader.ExtractUXContainer(file1, tempFolder);
21 /// }
22 /// </example>
23 internal class BurnReader : BurnCommon
24 {
25 private bool disposed;
26
27 private bool invalidBundle;
28 private BinaryReader binaryReader;
29 private List<DictionaryEntry> attachedContainerPayloadNames;
30
31 /// <summary>
32 /// Creates a BurnReader for reading a PE file.
33 /// </summary>
34 /// <param name="fileExe">File to read.</param>
35 private BurnReader(string fileExe)
36 : base(fileExe)
37 {
38 this.attachedContainerPayloadNames = new List<DictionaryEntry>();
39 }
40
41 /// <summary>
42 /// Gets the underlying stream.
43 /// </summary>
44 public Stream Stream
45 {
46 get
47 {
48 return (null != this.binaryReader) ? this.binaryReader.BaseStream : null;
49 }
50 }
51
52 /// <summary>
53 /// Opens a Burn reader.
54 /// </summary>
55 /// <param name="fileExe">Path to file.</param>
56 /// <returns>Burn reader.</returns>
57 public static BurnReader Open(string fileExe)
58 {
59 BurnReader reader = new BurnReader(fileExe);
60
61 reader.binaryReader = new BinaryReader(File.Open(fileExe, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete));
62 if (!reader.Initialize(reader.binaryReader))
63 {
64 reader.invalidBundle = true;
65 }
66
67 return reader;
68 }
69
70 /// <summary>
71 /// Gets the UX container from the exe and extracts its contents to the output directory.
72 /// </summary>
73 /// <param name="outputDirectory">Directory to write extracted files to.</param>
74 /// <returns>True if successful, false otherwise</returns>
75 public bool ExtractUXContainer(string outputDirectory, string tempDirectory)
76 {
77 // No UX container to extract
78 if (this.UXAddress == 0 || this.UXSize == 0)
79 {
80 return false;
81 }
82
83 if (this.invalidBundle)
84 {
85 return false;
86 }
87
88 Directory.CreateDirectory(outputDirectory);
89 string tempCabPath = Path.Combine(tempDirectory, "ux.cab");
90 string manifestOriginalPath = Path.Combine(outputDirectory, "0");
91 string manifestPath = Path.Combine(outputDirectory, "manifest.xml");
92
93 this.binaryReader.BaseStream.Seek(this.UXAddress, SeekOrigin.Begin);
94 using (Stream tempCab = File.Open(tempCabPath, FileMode.Create, FileAccess.Write))
95 {
96 BurnCommon.CopyStream(this.binaryReader.BaseStream, tempCab, (int)this.UXSize);
97 }
98
99 using (WixExtractCab extract = new WixExtractCab())
100 {
101 extract.Extract(tempCabPath, outputDirectory);
102 }
103
104 Directory.CreateDirectory(Path.GetDirectoryName(manifestPath));
105 File.Delete(manifestPath);
106 File.Move(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 File.Delete(destinationPath);
125 File.Move(sourcePath, destinationPath);
126 }
127
128 foreach (XmlNode payload in payloads)
129 {
130 XmlNode sourcePathNode = payload.Attributes.GetNamedItem("SourcePath");
131 XmlNode filePathNode = payload.Attributes.GetNamedItem("FilePath");
132 XmlNode packagingNode = payload.Attributes.GetNamedItem("Packaging");
133
134 string sourcePath = sourcePathNode.Value;
135 string destinationPath = filePathNode.Value;
136 string packaging = packagingNode.Value;
137
138 if (packaging.Equals("embedded", StringComparison.OrdinalIgnoreCase))
139 {
140 this.attachedContainerPayloadNames.Add(new DictionaryEntry(sourcePath, destinationPath));
141 }
142 }
143
144 return true;
145 }
146
147 /// <summary>
148 /// Gets the attached container from the exe and extracts its contents to the output directory.
149 /// </summary>
150 /// <param name="outputDirectory">Directory to write extracted files to.</param>
151 /// <returns>True if successful, false otherwise</returns>
152 public bool ExtractAttachedContainer(string outputDirectory, string tempDirectory)
153 {
154 // No attached container to extract
155 if (this.AttachedContainerAddress == 0 || this.AttachedContainerSize == 0)
156 {
157 return false;
158 }
159
160 if (this.invalidBundle)
161 {
162 return false;
163 }
164
165 Directory.CreateDirectory(outputDirectory);
166 string tempCabPath = Path.Combine(tempDirectory, "attached.cab");
167
168 this.binaryReader.BaseStream.Seek(this.AttachedContainerAddress, SeekOrigin.Begin);
169 using (Stream tempCab = File.Open(tempCabPath, FileMode.Create, FileAccess.Write))
170 {
171 BurnCommon.CopyStream(this.binaryReader.BaseStream, tempCab, (int)this.AttachedContainerSize);
172 }
173
174 using (WixExtractCab extract = new WixExtractCab())
175 {
176 extract.Extract(tempCabPath, outputDirectory);
177 }
178
179 foreach (DictionaryEntry entry in this.attachedContainerPayloadNames)
180 {
181 string sourcePath = Path.Combine(outputDirectory, (string)entry.Key);
182 string destinationPath = Path.Combine(outputDirectory, (string)entry.Value);
183
184 Directory.CreateDirectory(Path.GetDirectoryName(destinationPath));
185 File.Delete(destinationPath);
186 File.Move(sourcePath, destinationPath);
187 }
188
189 return true;
190 }
191
192 /// <summary>
193 /// Dispose object.
194 /// </summary>
195 /// <param name="disposing">True when releasing managed objects.</param>
196 protected override void Dispose(bool disposing)
197 {
198 if (!this.disposed)
199 {
200 if (disposing && this.binaryReader != null)
201 {
202 this.binaryReader.Close();
203 this.binaryReader = null;
204 }
205
206 this.disposed = true;
207 }
208 }
209 }
210}
diff --git a/src/WixToolset.Core/Bind/Bundles/BurnWriter.cs b/src/WixToolset.Core/Bind/Bundles/BurnWriter.cs
new file mode 100644
index 00000000..bc0baf46
--- /dev/null
+++ b/src/WixToolset.Core/Bind/Bundles/BurnWriter.cs
@@ -0,0 +1,239 @@
1// Copyright (c) .NET Foundation and 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.Bind.Bundles
4{
5 using System;
6 using System.Diagnostics;
7 using System.IO;
8 using WixToolset.Data;
9
10 /// <summary>
11 /// Burn PE writer for the WiX toolset.
12 /// </summary>
13 /// <remarks>This class encapsulates reading/writing to a stub EXE for
14 /// creating bundled/chained setup packages.</remarks>
15 /// <example>
16 /// using (BurnWriter writer = new BurnWriter(fileExe, this.core, guid))
17 /// {
18 /// writer.AppendContainer(file1, BurnWriter.Container.UX);
19 /// writer.AppendContainer(file2, BurnWriter.Container.Attached);
20 /// }
21 /// </example>
22 internal class BurnWriter : BurnCommon
23 {
24 private bool disposed;
25 private bool invalidBundle;
26 private BinaryWriter binaryWriter;
27
28 /// <summary>
29 /// Creates a BurnWriter for re-writing a PE file.
30 /// </summary>
31 /// <param name="fileExe">File to modify in-place.</param>
32 /// <param name="bundleGuid">GUID for the bundle.</param>
33 private BurnWriter(string fileExe)
34 : base(fileExe)
35 {
36 }
37
38 /// <summary>
39 /// Opens a Burn writer.
40 /// </summary>
41 /// <param name="fileExe">Path to file.</param>
42 /// <returns>Burn writer.</returns>
43 public static BurnWriter Open(string fileExe)
44 {
45 BurnWriter writer = new BurnWriter(fileExe);
46
47 using (BinaryReader binaryReader = new BinaryReader(File.Open(fileExe, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete)))
48 {
49 if (!writer.Initialize(binaryReader))
50 {
51 writer.invalidBundle = true;
52 }
53 }
54
55 if (!writer.invalidBundle)
56 {
57 writer.binaryWriter = new BinaryWriter(File.Open(fileExe, FileMode.Open, FileAccess.ReadWrite, FileShare.Read | FileShare.Delete));
58 }
59
60 return writer;
61 }
62
63 /// <summary>
64 /// Update the ".wixburn" section data.
65 /// </summary>
66 /// <param name="stubSize">Size of the stub engine "burn.exe".</param>
67 /// <param name="bundleId">Unique identifier for this bundle.</param>
68 /// <returns></returns>
69 public bool InitializeBundleSectionData(long stubSize, Guid bundleId)
70 {
71 if (this.invalidBundle)
72 {
73 return false;
74 }
75
76 this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_MAGIC, BURN_SECTION_MAGIC);
77 this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_VERSION, BURN_SECTION_VERSION);
78
79 Messaging.Instance.OnMessage(WixVerboses.BundleGuid(bundleId.ToString("B")));
80 this.binaryWriter.BaseStream.Seek(this.wixburnDataOffset + BURN_SECTION_OFFSET_BUNDLEGUID, SeekOrigin.Begin);
81 this.binaryWriter.Write(bundleId.ToByteArray());
82
83 this.StubSize = (uint)stubSize;
84
85 this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_STUBSIZE, this.StubSize);
86 this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALCHECKSUM, 0);
87 this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALSIGNATUREOFFSET, 0);
88 this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALSIGNATURESIZE, 0);
89 this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_FORMAT, 1); // Hard-coded to CAB for now.
90 this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_COUNT, 0);
91 this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_UXSIZE, 0);
92 this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE, 0);
93 this.binaryWriter.BaseStream.Flush();
94
95 this.EngineSize = this.StubSize;
96
97 return true;
98 }
99
100 /// <summary>
101 /// Appends a UX or Attached container to the exe and updates the ".wixburn" section data to point to it.
102 /// </summary>
103 /// <param name="fileContainer">File path to append to the current exe.</param>
104 /// <param name="container">Container section represented by the fileContainer.</param>
105 /// <returns>true if the container data is successfully appended; false otherwise</returns>
106 public bool AppendContainer(string fileContainer, BurnCommon.Container container)
107 {
108 using (FileStream reader = File.OpenRead(fileContainer))
109 {
110 return this.AppendContainer(reader, reader.Length, container);
111 }
112 }
113
114 /// <summary>
115 /// Appends a UX or Attached container to the exe and updates the ".wixburn" section data to point to it.
116 /// </summary>
117 /// <param name="containerStream">File stream to append to the current exe.</param>
118 /// <param name="containerSize">Size of container to append.</param>
119 /// <param name="container">Container section represented by the fileContainer.</param>
120 /// <returns>true if the container data is successfully appended; false otherwise</returns>
121 public bool AppendContainer(Stream containerStream, long containerSize, BurnCommon.Container container)
122 {
123 UInt32 burnSectionCount = 0;
124 UInt32 burnSectionOffsetSize = 0;
125
126 switch (container)
127 {
128 case Container.UX:
129 burnSectionCount = 1;
130 burnSectionOffsetSize = BURN_SECTION_OFFSET_UXSIZE;
131 // TODO: verify that the size in the section data is 0 or the same size.
132 this.EngineSize += (uint)containerSize;
133 this.UXSize = (uint)containerSize;
134 break;
135
136 case Container.Attached:
137 burnSectionCount = 2;
138 burnSectionOffsetSize = BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE;
139 // TODO: verify that the size in the section data is 0 or the same size.
140 this.AttachedContainerSize = (uint)containerSize;
141 break;
142
143 default:
144 Debug.Assert(false);
145 return false;
146 }
147
148 return AppendContainer(containerStream, (UInt32)containerSize, burnSectionOffsetSize, burnSectionCount);
149 }
150
151 public void RememberThenResetSignature()
152 {
153 if (this.invalidBundle)
154 {
155 return;
156 }
157
158 this.OriginalChecksum = this.Checksum;
159 this.OriginalSignatureOffset = this.SignatureOffset;
160 this.OriginalSignatureSize = this.SignatureSize;
161
162 this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALCHECKSUM, this.OriginalChecksum);
163 this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALSIGNATUREOFFSET, this.OriginalSignatureOffset);
164 this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALSIGNATURESIZE, this.OriginalSignatureSize);
165
166 this.Checksum = 0;
167 this.SignatureOffset = 0;
168 this.SignatureSize = 0;
169
170 this.WriteToOffset(this.checksumOffset, this.Checksum);
171 this.WriteToOffset(this.certificateTableSignatureOffset, this.SignatureOffset);
172 this.WriteToOffset(this.certificateTableSignatureSize, this.SignatureSize);
173 }
174
175 /// <summary>
176 /// Dispose object.
177 /// </summary>
178 /// <param name="disposing">True when releasing managed objects.</param>
179 protected override void Dispose(bool disposing)
180 {
181 if (!this.disposed)
182 {
183 if (disposing && this.binaryWriter != null)
184 {
185 this.binaryWriter.Close();
186 this.binaryWriter = null;
187 }
188
189 this.disposed = true;
190 }
191 }
192
193 /// <summary>
194 /// Appends a container to the exe and updates the ".wixburn" section data to point to it.
195 /// </summary>
196 /// <param name="containerStream">File stream to append to the current exe.</param>
197 /// <param name="burnSectionOffsetSize">Offset of size field for this container in ".wixburn" section data.</param>
198 /// <returns>true if the container data is successfully appended; false otherwise</returns>
199 private bool AppendContainer(Stream containerStream, UInt32 containerSize, UInt32 burnSectionOffsetSize, UInt32 burnSectionCount)
200 {
201 if (this.invalidBundle)
202 {
203 return false;
204 }
205
206 // Update the ".wixburn" section data
207 this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_COUNT, burnSectionCount);
208 this.WriteToBurnSectionOffset(burnSectionOffsetSize, containerSize);
209
210 // Append the container to the end of the existing bits.
211 this.binaryWriter.BaseStream.Seek(0, SeekOrigin.End);
212 BurnCommon.CopyStream(containerStream, this.binaryWriter.BaseStream, (int)containerSize);
213 this.binaryWriter.BaseStream.Flush();
214
215 return true;
216 }
217
218 /// <summary>
219 /// Writes the value to an offset in the Burn section data.
220 /// </summary>
221 /// <param name="offset">Offset in to the Burn section data.</param>
222 /// <param name="value">Value to write.</param>
223 private void WriteToBurnSectionOffset(uint offset, uint value)
224 {
225 this.WriteToOffset(this.wixburnDataOffset + offset, value);
226 }
227
228 /// <summary>
229 /// Writes the value to an offset in the Burn stub.
230 /// </summary>
231 /// <param name="offset">Offset in to the Burn stub.</param>
232 /// <param name="value">Value to write.</param>
233 private void WriteToOffset(uint offset, uint value)
234 {
235 this.binaryWriter.BaseStream.Seek((int)offset, SeekOrigin.Begin);
236 this.binaryWriter.Write(value);
237 }
238 }
239}
diff --git a/src/WixToolset.Core/Bind/Bundles/CreateBootstrapperApplicationManifestCommand.cs b/src/WixToolset.Core/Bind/Bundles/CreateBootstrapperApplicationManifestCommand.cs
new file mode 100644
index 00000000..1040b394
--- /dev/null
+++ b/src/WixToolset.Core/Bind/Bundles/CreateBootstrapperApplicationManifestCommand.cs
@@ -0,0 +1,241 @@
1// Copyright (c) .NET Foundation and 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.Bind.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.Text;
11 using System.Xml;
12 using WixToolset.Data;
13 using WixToolset.Data.Rows;
14
15 internal class CreateBootstrapperApplicationManifestCommand : ICommand
16 {
17 public WixBundleRow BundleRow { private get; set; }
18
19 public IEnumerable<PackageFacade> ChainPackages { private get; set; }
20
21 public int LastUXPayloadIndex { private get; set; }
22
23 public IEnumerable<WixBundleMsiFeatureRow> MsiFeatures { private get; set; }
24
25 public Output Output { private get; set; }
26
27 public RowDictionary<WixBundlePayloadRow> Payloads { private get; set; }
28
29 public TableDefinitionCollection TableDefinitions { private get; set; }
30
31 public string TempFilesLocation { private get; set; }
32
33 public WixBundlePayloadRow BootstrapperApplicationManifestPayloadRow { get; private set; }
34
35 public void Execute()
36 {
37 this.GenerateBAManifestBundleTables();
38
39 this.GenerateBAManifestMsiFeatureTables();
40
41 this.GenerateBAManifestPackageTables();
42
43 this.GenerateBAManifestPayloadTables();
44
45 string baManifestPath = Path.Combine(this.TempFilesLocation, "wix-badata.xml");
46
47 this.CreateBootstrapperApplicationManifest(baManifestPath);
48
49 this.BootstrapperApplicationManifestPayloadRow = this.CreateBootstrapperApplicationManifestPayloadRow(baManifestPath);
50 }
51
52 private void GenerateBAManifestBundleTables()
53 {
54 Table wixBundlePropertiesTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleProperties"]);
55
56 Row row = wixBundlePropertiesTable.CreateRow(this.BundleRow.SourceLineNumbers);
57 row[0] = this.BundleRow.Name;
58 row[1] = this.BundleRow.LogPathVariable;
59 row[2] = (YesNoDefaultType.Yes == this.BundleRow.Compressed) ? "yes" : "no";
60 row[3] = this.BundleRow.BundleId.ToString("B");
61 row[4] = this.BundleRow.UpgradeCode;
62 row[5] = this.BundleRow.PerMachine ? "yes" : "no";
63 }
64
65 private void GenerateBAManifestPackageTables()
66 {
67 Table wixPackagePropertiesTable = this.Output.EnsureTable(this.TableDefinitions["WixPackageProperties"]);
68
69 foreach (PackageFacade package in this.ChainPackages)
70 {
71 WixBundlePayloadRow packagePayload = this.Payloads[package.Package.PackagePayload];
72
73 Row row = wixPackagePropertiesTable.CreateRow(package.Package.SourceLineNumbers);
74 row[0] = package.Package.WixChainItemId;
75 row[1] = (YesNoType.Yes == package.Package.Vital) ? "yes" : "no";
76 row[2] = package.Package.DisplayName;
77 row[3] = package.Package.Description;
78 row[4] = package.Package.Size.ToString(CultureInfo.InvariantCulture); // TODO: DownloadSize (compressed) (what does this mean when it's embedded?)
79 row[5] = package.Package.Size.ToString(CultureInfo.InvariantCulture); // Package.Size (uncompressed)
80 row[6] = package.Package.InstallSize.Value.ToString(CultureInfo.InvariantCulture); // InstallSize (required disk space)
81 row[7] = package.Package.Type.ToString();
82 row[8] = package.Package.Permanent ? "yes" : "no";
83 row[9] = package.Package.LogPathVariable;
84 row[10] = package.Package.RollbackLogPathVariable;
85 row[11] = (PackagingType.Embedded == packagePayload.Packaging) ? "yes" : "no";
86
87 if (WixBundlePackageType.Msi == package.Package.Type)
88 {
89 row[12] = package.MsiPackage.DisplayInternalUI ? "yes" : "no";
90
91 if (!String.IsNullOrEmpty(package.MsiPackage.ProductCode))
92 {
93 row[13] = package.MsiPackage.ProductCode;
94 }
95
96 if (!String.IsNullOrEmpty(package.MsiPackage.UpgradeCode))
97 {
98 row[14] = package.MsiPackage.UpgradeCode;
99 }
100 }
101 else if (WixBundlePackageType.Msp == package.Package.Type)
102 {
103 row[12] = package.MspPackage.DisplayInternalUI ? "yes" : "no";
104
105 if (!String.IsNullOrEmpty(package.MspPackage.PatchCode))
106 {
107 row[13] = package.MspPackage.PatchCode;
108 }
109 }
110
111 if (!String.IsNullOrEmpty(package.Package.Version))
112 {
113 row[15] = package.Package.Version;
114 }
115
116 if (!String.IsNullOrEmpty(package.Package.InstallCondition))
117 {
118 row[16] = package.Package.InstallCondition;
119 }
120
121 switch (package.Package.Cache)
122 {
123 case YesNoAlwaysType.No:
124 row[17] = "no";
125 break;
126 case YesNoAlwaysType.Yes:
127 row[17] = "yes";
128 break;
129 case YesNoAlwaysType.Always:
130 row[17] = "always";
131 break;
132 }
133 }
134 }
135
136 private void GenerateBAManifestMsiFeatureTables()
137 {
138 Table wixPackageFeatureInfoTable = this.Output.EnsureTable(this.TableDefinitions["WixPackageFeatureInfo"]);
139
140 foreach (WixBundleMsiFeatureRow feature in this.MsiFeatures)
141 {
142 Row row = wixPackageFeatureInfoTable.CreateRow(feature.SourceLineNumbers);
143 row[0] = feature.ChainPackageId;
144 row[1] = feature.Name;
145 row[2] = Convert.ToString(feature.Size, CultureInfo.InvariantCulture);
146 row[3] = feature.Parent;
147 row[4] = feature.Title;
148 row[5] = feature.Description;
149 row[6] = Convert.ToString(feature.Display, CultureInfo.InvariantCulture);
150 row[7] = Convert.ToString(feature.Level, CultureInfo.InvariantCulture);
151 row[8] = feature.Directory;
152 row[9] = Convert.ToString(feature.Attributes, CultureInfo.InvariantCulture);
153 }
154
155 }
156
157 private void GenerateBAManifestPayloadTables()
158 {
159 Table wixPayloadPropertiesTable = this.Output.EnsureTable(this.TableDefinitions["WixPayloadProperties"]);
160
161 foreach (WixBundlePayloadRow payload in this.Payloads.Values)
162 {
163 WixPayloadPropertiesRow row = (WixPayloadPropertiesRow)wixPayloadPropertiesTable.CreateRow(payload.SourceLineNumbers);
164 row.Id = payload.Id;
165 row.Package = payload.Package;
166 row.Container = payload.Container;
167 row.Name = payload.Name;
168 row.Size = payload.FileSize.ToString();
169 row.DownloadUrl = payload.DownloadUrl;
170 row.LayoutOnly = payload.LayoutOnly ? "yes" : "no";
171 }
172 }
173
174 private void CreateBootstrapperApplicationManifest(string path)
175 {
176 using (XmlTextWriter writer = new XmlTextWriter(path, Encoding.Unicode))
177 {
178 writer.Formatting = Formatting.Indented;
179 writer.WriteStartDocument();
180 writer.WriteStartElement("BootstrapperApplicationData", "http://wixtoolset.org/schemas/v4/2010/BootstrapperApplicationData");
181
182 foreach (Table table in this.Output.Tables)
183 {
184 if (table.Definition.BootstrapperApplicationData)
185 {
186 // We simply assert that the table (and field) name is valid, because
187 // this is up to the extension developer to get right. An author will
188 // only affect the attribute value, and that will get properly escaped.
189#if DEBUG
190 Debug.Assert(Common.IsIdentifier(table.Name));
191 foreach (ColumnDefinition column in table.Definition.Columns)
192 {
193 Debug.Assert(Common.IsIdentifier(column.Name));
194 }
195#endif // DEBUG
196
197 foreach (Row row in table.Rows)
198 {
199 writer.WriteStartElement(table.Name);
200
201 foreach (Field field in row.Fields)
202 {
203 if (null != field.Data)
204 {
205 writer.WriteAttributeString(field.Column.Name, field.Data.ToString());
206 }
207 }
208
209 writer.WriteEndElement();
210 }
211 }
212 }
213
214 writer.WriteEndElement();
215 writer.WriteEndDocument();
216 }
217 }
218
219 private WixBundlePayloadRow CreateBootstrapperApplicationManifestPayloadRow(string baManifestPath)
220 {
221 Table payloadTable = this.Output.EnsureTable(this.TableDefinitions["WixBundlePayload"]);
222 WixBundlePayloadRow row = (WixBundlePayloadRow)payloadTable.CreateRow(this.BundleRow.SourceLineNumbers);
223 row.Id = Common.GenerateIdentifier("ux", "BootstrapperApplicationData.xml");
224 row.Name = "BootstrapperApplicationData.xml";
225 row.SourceFile = baManifestPath;
226 row.Compressed = YesNoDefaultType.Yes;
227 row.UnresolvedSourceFile = baManifestPath;
228 row.Container = Compiler.BurnUXContainerId;
229 row.EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnUXContainerEmbeddedIdFormat, this.LastUXPayloadIndex);
230 row.Packaging = PackagingType.Embedded;
231
232 FileInfo fileInfo = new FileInfo(row.SourceFile);
233
234 row.FileSize = (int)fileInfo.Length;
235
236 row.Hash = Common.GetFileHash(fileInfo.FullName);
237
238 return row;
239 }
240 }
241}
diff --git a/src/WixToolset.Core/Bind/Bundles/CreateBurnManifestCommand.cs b/src/WixToolset.Core/Bind/Bundles/CreateBurnManifestCommand.cs
new file mode 100644
index 00000000..7bc708a3
--- /dev/null
+++ b/src/WixToolset.Core/Bind/Bundles/CreateBurnManifestCommand.cs
@@ -0,0 +1,686 @@
1// Copyright (c) .NET Foundation and 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.Bind.Bundles
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.Xml;
12 using WixToolset.Data;
13 using WixToolset.Data.Rows;
14 using WixToolset.Extensibility;
15
16 internal class CreateBurnManifestCommand : ICommand
17 {
18 public IEnumerable<IBinderFileManager> FileManagers { private get; set; }
19
20 public Output Output { private get; set; }
21
22 public string ExecutableName { private get; set; }
23
24 public WixBundleRow BundleInfo { private get; set; }
25
26 public WixChainRow Chain { private get; set; }
27
28 public string OutputPath { private get; set; }
29
30 public IEnumerable<WixBundleRollbackBoundaryRow> RollbackBoundaries { private get; set; }
31
32 public IEnumerable<PackageFacade> OrderedPackages { private get; set; }
33
34 public IEnumerable<WixSearchInfo> OrderedSearches { private get; set; }
35
36 public Dictionary<string, WixBundlePayloadRow> Payloads { private get; set; }
37
38 public Dictionary<string, WixBundleContainerRow> Containers { private get; set; }
39
40 public IEnumerable<WixBundlePayloadRow> UXContainerPayloads { private get; set; }
41
42 public IEnumerable<WixBundleCatalogRow> Catalogs { private get; set; }
43
44 public void Execute()
45 {
46 using (XmlTextWriter writer = new XmlTextWriter(this.OutputPath, Encoding.UTF8))
47 {
48 writer.WriteStartDocument();
49
50 writer.WriteStartElement("BurnManifest", BurnCommon.BurnNamespace);
51
52 // Write the condition, if there is one
53 if (null != this.BundleInfo.Condition)
54 {
55 writer.WriteElementString("Condition", this.BundleInfo.Condition);
56 }
57
58 // Write the log element if default logging wasn't disabled.
59 if (!String.IsNullOrEmpty(this.BundleInfo.LogPrefix))
60 {
61 writer.WriteStartElement("Log");
62 if (!String.IsNullOrEmpty(this.BundleInfo.LogPathVariable))
63 {
64 writer.WriteAttributeString("PathVariable", this.BundleInfo.LogPathVariable);
65 }
66 writer.WriteAttributeString("Prefix", this.BundleInfo.LogPrefix);
67 writer.WriteAttributeString("Extension", this.BundleInfo.LogExtension);
68 writer.WriteEndElement();
69 }
70
71
72 // Get update if specified.
73 WixBundleUpdateRow updateRow = this.Output.Tables["WixBundleUpdate"].RowsAs<WixBundleUpdateRow>().FirstOrDefault();
74
75 if (null != updateRow)
76 {
77 writer.WriteStartElement("Update");
78 writer.WriteAttributeString("Location", updateRow.Location);
79 writer.WriteEndElement(); // </Update>
80 }
81
82 // Write the RelatedBundle elements
83
84 // For the related bundles with duplicated identifiers the second instance is ignored (i.e. the Duplicates
85 // enumeration in the index row list is not used).
86 RowIndexedList<WixRelatedBundleRow> relatedBundles = new RowIndexedList<WixRelatedBundleRow>(this.Output.Tables["WixRelatedBundle"]);
87
88 foreach (WixRelatedBundleRow relatedBundle in relatedBundles)
89 {
90 writer.WriteStartElement("RelatedBundle");
91 writer.WriteAttributeString("Id", relatedBundle.Id);
92 writer.WriteAttributeString("Action", Convert.ToString(relatedBundle.Action, CultureInfo.InvariantCulture));
93 writer.WriteEndElement();
94 }
95
96 // Write the variables
97 IEnumerable<WixBundleVariableRow> variables = this.Output.Tables["WixBundleVariable"].RowsAs<WixBundleVariableRow>();
98
99 foreach (WixBundleVariableRow variable in variables)
100 {
101 writer.WriteStartElement("Variable");
102 writer.WriteAttributeString("Id", variable.Id);
103 if (null != variable.Type)
104 {
105 writer.WriteAttributeString("Value", variable.Value);
106 writer.WriteAttributeString("Type", variable.Type);
107 }
108 writer.WriteAttributeString("Hidden", variable.Hidden ? "yes" : "no");
109 writer.WriteAttributeString("Persisted", variable.Persisted ? "yes" : "no");
110 writer.WriteEndElement();
111 }
112
113 // Write the searches
114 foreach (WixSearchInfo searchinfo in this.OrderedSearches)
115 {
116 searchinfo.WriteXml(writer);
117 }
118
119 // write the UX element
120 writer.WriteStartElement("UX");
121 if (!String.IsNullOrEmpty(this.BundleInfo.SplashScreenBitmapPath))
122 {
123 writer.WriteAttributeString("SplashScreen", "yes");
124 }
125
126 // write the UX allPayloads...
127 foreach (WixBundlePayloadRow payload in this.UXContainerPayloads)
128 {
129 writer.WriteStartElement("Payload");
130 this.WriteBurnManifestPayloadAttributes(writer, payload, true, this.Payloads);
131 writer.WriteEndElement();
132 }
133
134 writer.WriteEndElement(); // </UX>
135
136 // write the catalog elements
137 if (this.Catalogs.Any())
138 {
139 foreach (WixBundleCatalogRow catalog in this.Catalogs)
140 {
141 writer.WriteStartElement("Catalog");
142 writer.WriteAttributeString("Id", catalog.Id);
143 writer.WriteAttributeString("Payload", catalog.Payload);
144 writer.WriteEndElement();
145 }
146 }
147
148 foreach (WixBundleContainerRow container in this.Containers.Values)
149 {
150 if (!String.IsNullOrEmpty(container.WorkingPath) && Compiler.BurnUXContainerId != container.Id)
151 {
152 writer.WriteStartElement("Container");
153 this.WriteBurnManifestContainerAttributes(writer, this.ExecutableName, container);
154 writer.WriteEndElement();
155 }
156 }
157
158 foreach (WixBundlePayloadRow payload in this.Payloads.Values)
159 {
160 if (PackagingType.Embedded == payload.Packaging && Compiler.BurnUXContainerId != payload.Container)
161 {
162 writer.WriteStartElement("Payload");
163 this.WriteBurnManifestPayloadAttributes(writer, payload, true, this.Payloads);
164 writer.WriteEndElement();
165 }
166 else if (PackagingType.External == payload.Packaging)
167 {
168 writer.WriteStartElement("Payload");
169 this.WriteBurnManifestPayloadAttributes(writer, payload, false, this.Payloads);
170 writer.WriteEndElement();
171 }
172 }
173
174 foreach (WixBundleRollbackBoundaryRow rollbackBoundary in this.RollbackBoundaries)
175 {
176 writer.WriteStartElement("RollbackBoundary");
177 writer.WriteAttributeString("Id", rollbackBoundary.ChainPackageId);
178 writer.WriteAttributeString("Vital", YesNoType.Yes == rollbackBoundary.Vital ? "yes" : "no");
179 writer.WriteAttributeString("Transaction", YesNoType.Yes == rollbackBoundary.Transaction ? "yes" : "no");
180 writer.WriteEndElement();
181 }
182
183 // Write the registration information...
184 writer.WriteStartElement("Registration");
185
186 writer.WriteAttributeString("Id", this.BundleInfo.BundleId.ToString("B"));
187 writer.WriteAttributeString("ExecutableName", this.ExecutableName);
188 writer.WriteAttributeString("PerMachine", this.BundleInfo.PerMachine ? "yes" : "no");
189 writer.WriteAttributeString("Tag", this.BundleInfo.Tag);
190 writer.WriteAttributeString("Version", this.BundleInfo.Version);
191 writer.WriteAttributeString("ProviderKey", this.BundleInfo.ProviderKey);
192
193 writer.WriteStartElement("Arp");
194 writer.WriteAttributeString("Register", (0 < this.BundleInfo.DisableModify && this.BundleInfo.DisableRemove) ? "no" : "yes"); // do not register if disabled modify and remove.
195 writer.WriteAttributeString("DisplayName", this.BundleInfo.Name);
196 writer.WriteAttributeString("DisplayVersion", this.BundleInfo.Version);
197
198 if (!String.IsNullOrEmpty(this.BundleInfo.Publisher))
199 {
200 writer.WriteAttributeString("Publisher", this.BundleInfo.Publisher);
201 }
202
203 if (!String.IsNullOrEmpty(this.BundleInfo.HelpLink))
204 {
205 writer.WriteAttributeString("HelpLink", this.BundleInfo.HelpLink);
206 }
207
208 if (!String.IsNullOrEmpty(this.BundleInfo.HelpTelephone))
209 {
210 writer.WriteAttributeString("HelpTelephone", this.BundleInfo.HelpTelephone);
211 }
212
213 if (!String.IsNullOrEmpty(this.BundleInfo.AboutUrl))
214 {
215 writer.WriteAttributeString("AboutUrl", this.BundleInfo.AboutUrl);
216 }
217
218 if (!String.IsNullOrEmpty(this.BundleInfo.UpdateUrl))
219 {
220 writer.WriteAttributeString("UpdateUrl", this.BundleInfo.UpdateUrl);
221 }
222
223 if (!String.IsNullOrEmpty(this.BundleInfo.ParentName))
224 {
225 writer.WriteAttributeString("ParentDisplayName", this.BundleInfo.ParentName);
226 }
227
228 if (1 == this.BundleInfo.DisableModify)
229 {
230 writer.WriteAttributeString("DisableModify", "yes");
231 }
232 else if (2 == this.BundleInfo.DisableModify)
233 {
234 writer.WriteAttributeString("DisableModify", "button");
235 }
236
237 if (this.BundleInfo.DisableRemove)
238 {
239 writer.WriteAttributeString("DisableRemove", "yes");
240 }
241 writer.WriteEndElement(); // </Arp>
242
243 // Get update registration if specified.
244 WixUpdateRegistrationRow updateRegistrationInfo = this.Output.Tables["WixUpdateRegistration"].RowsAs<WixUpdateRegistrationRow>().FirstOrDefault();
245
246 if (null != updateRegistrationInfo)
247 {
248 writer.WriteStartElement("Update"); // <Update>
249 writer.WriteAttributeString("Manufacturer", updateRegistrationInfo.Manufacturer);
250
251 if (!String.IsNullOrEmpty(updateRegistrationInfo.Department))
252 {
253 writer.WriteAttributeString("Department", updateRegistrationInfo.Department);
254 }
255
256 if (!String.IsNullOrEmpty(updateRegistrationInfo.ProductFamily))
257 {
258 writer.WriteAttributeString("ProductFamily", updateRegistrationInfo.ProductFamily);
259 }
260
261 writer.WriteAttributeString("Name", updateRegistrationInfo.Name);
262 writer.WriteAttributeString("Classification", updateRegistrationInfo.Classification);
263 writer.WriteEndElement(); // </Update>
264 }
265
266 IEnumerable<Row> bundleTags = this.Output.Tables["WixBundleTag"].RowsAs<Row>();
267
268 foreach (Row row in bundleTags)
269 {
270 writer.WriteStartElement("SoftwareTag");
271 writer.WriteAttributeString("Filename", (string)row[0]);
272 writer.WriteAttributeString("Regid", (string)row[1]);
273 writer.WriteCData((string)row[4]);
274 writer.WriteEndElement();
275 }
276
277 writer.WriteEndElement(); // </Register>
278
279 // write the Chain...
280 writer.WriteStartElement("Chain");
281 if (this.Chain.DisableRollback)
282 {
283 writer.WriteAttributeString("DisableRollback", "yes");
284 }
285
286 if (this.Chain.DisableSystemRestore)
287 {
288 writer.WriteAttributeString("DisableSystemRestore", "yes");
289 }
290
291 if (this.Chain.ParallelCache)
292 {
293 writer.WriteAttributeString("ParallelCache", "yes");
294 }
295
296 // Index a few tables by package.
297 ILookup<string, WixBundlePatchTargetCodeRow> targetCodesByPatch = this.Output.Tables["WixBundlePatchTargetCode"].RowsAs<WixBundlePatchTargetCodeRow>().ToLookup(r => r.MspPackageId);
298 ILookup<string, WixBundleMsiFeatureRow> msiFeaturesByPackage = this.Output.Tables["WixBundleMsiFeature"].RowsAs<WixBundleMsiFeatureRow>().ToLookup(r => r.ChainPackageId);
299 ILookup<string, WixBundleMsiPropertyRow> msiPropertiesByPackage = this.Output.Tables["WixBundleMsiProperty"].RowsAs<WixBundleMsiPropertyRow>().ToLookup(r => r.ChainPackageId);
300 ILookup<string, WixBundlePayloadRow> payloadsByPackage = this.Payloads.Values.ToLookup(p => p.Package);
301 ILookup<string, WixBundleRelatedPackageRow> relatedPackagesByPackage = this.Output.Tables["WixBundleRelatedPackage"].RowsAs<WixBundleRelatedPackageRow>().ToLookup(r => r.ChainPackageId);
302 ILookup<string, WixBundleSlipstreamMspRow> slipstreamMspsByPackage = this.Output.Tables["WixBundleSlipstreamMsp"].RowsAs<WixBundleSlipstreamMspRow>().ToLookup(r => r.ChainPackageId);
303 ILookup<string, WixBundlePackageExitCodeRow> exitCodesByPackage = this.Output.Tables["WixBundlePackageExitCode"].RowsAs<WixBundlePackageExitCodeRow>().ToLookup(r => r.ChainPackageId);
304 ILookup<string, WixBundlePackageCommandLineRow> commandLinesByPackage = this.Output.Tables["WixBundlePackageCommandLine"].RowsAs<WixBundlePackageCommandLineRow>().ToLookup(r => r.ChainPackageId);
305
306 // Build up the list of target codes from all the MSPs in the chain.
307 List<WixBundlePatchTargetCodeRow> targetCodes = new List<WixBundlePatchTargetCodeRow>();
308
309 foreach (PackageFacade package in this.OrderedPackages)
310 {
311 writer.WriteStartElement(String.Format(CultureInfo.InvariantCulture, "{0}Package", package.Package.Type));
312
313 writer.WriteAttributeString("Id", package.Package.WixChainItemId);
314
315 switch (package.Package.Cache)
316 {
317 case YesNoAlwaysType.No:
318 writer.WriteAttributeString("Cache", "no");
319 break;
320 case YesNoAlwaysType.Yes:
321 writer.WriteAttributeString("Cache", "yes");
322 break;
323 case YesNoAlwaysType.Always:
324 writer.WriteAttributeString("Cache", "always");
325 break;
326 }
327
328 writer.WriteAttributeString("CacheId", package.Package.CacheId);
329 writer.WriteAttributeString("InstallSize", Convert.ToString(package.Package.InstallSize));
330 writer.WriteAttributeString("Size", Convert.ToString(package.Package.Size));
331 writer.WriteAttributeString("PerMachine", YesNoDefaultType.Yes == package.Package.PerMachine ? "yes" : "no");
332 writer.WriteAttributeString("Permanent", package.Package.Permanent ? "yes" : "no");
333 writer.WriteAttributeString("Vital", (YesNoType.Yes == package.Package.Vital) ? "yes" : "no");
334
335 if (null != package.Package.RollbackBoundary)
336 {
337 writer.WriteAttributeString("RollbackBoundaryForward", package.Package.RollbackBoundary);
338 }
339
340 if (!String.IsNullOrEmpty(package.Package.RollbackBoundaryBackward))
341 {
342 writer.WriteAttributeString("RollbackBoundaryBackward", package.Package.RollbackBoundaryBackward);
343 }
344
345 if (!String.IsNullOrEmpty(package.Package.LogPathVariable))
346 {
347 writer.WriteAttributeString("LogPathVariable", package.Package.LogPathVariable);
348 }
349
350 if (!String.IsNullOrEmpty(package.Package.RollbackLogPathVariable))
351 {
352 writer.WriteAttributeString("RollbackLogPathVariable", package.Package.RollbackLogPathVariable);
353 }
354
355 if (!String.IsNullOrEmpty(package.Package.InstallCondition))
356 {
357 writer.WriteAttributeString("InstallCondition", package.Package.InstallCondition);
358 }
359
360 if (WixBundlePackageType.Exe == package.Package.Type)
361 {
362 writer.WriteAttributeString("DetectCondition", package.ExePackage.DetectCondition);
363 writer.WriteAttributeString("InstallArguments", package.ExePackage.InstallCommand);
364 writer.WriteAttributeString("UninstallArguments", package.ExePackage.UninstallCommand);
365 writer.WriteAttributeString("RepairArguments", package.ExePackage.RepairCommand);
366 writer.WriteAttributeString("Repairable", package.ExePackage.Repairable ? "yes" : "no");
367 if (!String.IsNullOrEmpty(package.ExePackage.ExeProtocol))
368 {
369 writer.WriteAttributeString("Protocol", package.ExePackage.ExeProtocol);
370 }
371 }
372 else if (WixBundlePackageType.Msi == package.Package.Type)
373 {
374 writer.WriteAttributeString("ProductCode", package.MsiPackage.ProductCode);
375 writer.WriteAttributeString("Language", package.MsiPackage.ProductLanguage.ToString(CultureInfo.InvariantCulture));
376 writer.WriteAttributeString("Version", package.MsiPackage.ProductVersion);
377 writer.WriteAttributeString("DisplayInternalUI", package.MsiPackage.DisplayInternalUI ? "yes" : "no");
378 if (!String.IsNullOrEmpty(package.MsiPackage.UpgradeCode))
379 {
380 writer.WriteAttributeString("UpgradeCode", package.MsiPackage.UpgradeCode);
381 }
382 }
383 else if (WixBundlePackageType.Msp == package.Package.Type)
384 {
385 writer.WriteAttributeString("PatchCode", package.MspPackage.PatchCode);
386 writer.WriteAttributeString("PatchXml", package.MspPackage.PatchXml);
387 writer.WriteAttributeString("DisplayInternalUI", package.MspPackage.DisplayInternalUI ? "yes" : "no");
388
389 // If there is still a chance that all of our patches will target a narrow set of
390 // product codes, add the patch list to the overall list.
391 if (null != targetCodes)
392 {
393 if (!package.MspPackage.TargetUnspecified)
394 {
395 IEnumerable<WixBundlePatchTargetCodeRow> patchTargetCodes = targetCodesByPatch[package.MspPackage.ChainPackageId];
396
397 targetCodes.AddRange(patchTargetCodes);
398 }
399 else // we have a patch that targets the world, so throw the whole list away.
400 {
401 targetCodes = null;
402 }
403 }
404 }
405 else if (WixBundlePackageType.Msu == package.Package.Type)
406 {
407 writer.WriteAttributeString("DetectCondition", package.MsuPackage.DetectCondition);
408 writer.WriteAttributeString("KB", package.MsuPackage.MsuKB);
409 }
410
411 IEnumerable<WixBundleMsiFeatureRow> packageMsiFeatures = msiFeaturesByPackage[package.Package.WixChainItemId];
412
413 foreach (WixBundleMsiFeatureRow feature in packageMsiFeatures)
414 {
415 writer.WriteStartElement("MsiFeature");
416 writer.WriteAttributeString("Id", feature.Name);
417 writer.WriteEndElement();
418 }
419
420 IEnumerable<WixBundleMsiPropertyRow> packageMsiProperties = msiPropertiesByPackage[package.Package.WixChainItemId];
421
422 foreach (WixBundleMsiPropertyRow msiProperty in packageMsiProperties)
423 {
424 writer.WriteStartElement("MsiProperty");
425 writer.WriteAttributeString("Id", msiProperty.Name);
426 writer.WriteAttributeString("Value", msiProperty.Value);
427 if (!String.IsNullOrEmpty(msiProperty.Condition))
428 {
429 writer.WriteAttributeString("Condition", msiProperty.Condition);
430 }
431 writer.WriteEndElement();
432 }
433
434 IEnumerable<WixBundleSlipstreamMspRow> packageSlipstreamMsps = slipstreamMspsByPackage[package.Package.WixChainItemId];
435
436 foreach (WixBundleSlipstreamMspRow slipstreamMsp in packageSlipstreamMsps)
437 {
438 writer.WriteStartElement("SlipstreamMsp");
439 writer.WriteAttributeString("Id", slipstreamMsp.MspPackageId);
440 writer.WriteEndElement();
441 }
442
443 IEnumerable<WixBundlePackageExitCodeRow> packageExitCodes = exitCodesByPackage[package.Package.WixChainItemId];
444
445 foreach (WixBundlePackageExitCodeRow exitCode in packageExitCodes)
446 {
447 writer.WriteStartElement("ExitCode");
448
449 if (exitCode.Code.HasValue)
450 {
451 writer.WriteAttributeString("Code", unchecked((uint)exitCode.Code).ToString(CultureInfo.InvariantCulture));
452 }
453 else
454 {
455 writer.WriteAttributeString("Code", "*");
456 }
457
458 writer.WriteAttributeString("Type", ((int)exitCode.Behavior).ToString(CultureInfo.InvariantCulture));
459 writer.WriteEndElement();
460 }
461
462 IEnumerable<WixBundlePackageCommandLineRow> packageCommandLines = commandLinesByPackage[package.Package.WixChainItemId];
463
464 foreach (WixBundlePackageCommandLineRow commandLine in packageCommandLines)
465 {
466 writer.WriteStartElement("CommandLine");
467 writer.WriteAttributeString("InstallArgument", commandLine.InstallArgument);
468 writer.WriteAttributeString("UninstallArgument", commandLine.UninstallArgument);
469 writer.WriteAttributeString("RepairArgument", commandLine.RepairArgument);
470 writer.WriteAttributeString("Condition", commandLine.Condition);
471 writer.WriteEndElement();
472 }
473
474 // Output the dependency information.
475 foreach (ProvidesDependency dependency in package.Provides)
476 {
477 // TODO: Add to wixpdb as an imported table, or link package wixpdbs to bundle wixpdbs.
478 dependency.WriteXml(writer);
479 }
480
481 IEnumerable<WixBundleRelatedPackageRow> packageRelatedPackages = relatedPackagesByPackage[package.Package.WixChainItemId];
482
483 foreach (WixBundleRelatedPackageRow related in packageRelatedPackages)
484 {
485 writer.WriteStartElement("RelatedPackage");
486 writer.WriteAttributeString("Id", related.Id);
487 if (!String.IsNullOrEmpty(related.MinVersion))
488 {
489 writer.WriteAttributeString("MinVersion", related.MinVersion);
490 writer.WriteAttributeString("MinInclusive", related.MinInclusive ? "yes" : "no");
491 }
492 if (!String.IsNullOrEmpty(related.MaxVersion))
493 {
494 writer.WriteAttributeString("MaxVersion", related.MaxVersion);
495 writer.WriteAttributeString("MaxInclusive", related.MaxInclusive ? "yes" : "no");
496 }
497 writer.WriteAttributeString("OnlyDetect", related.OnlyDetect ? "yes" : "no");
498
499 string[] relatedLanguages = related.Languages.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
500
501 if (0 < relatedLanguages.Length)
502 {
503 writer.WriteAttributeString("LangInclusive", related.LangInclusive ? "yes" : "no");
504 foreach (string language in relatedLanguages)
505 {
506 writer.WriteStartElement("Language");
507 writer.WriteAttributeString("Id", language);
508 writer.WriteEndElement();
509 }
510 }
511 writer.WriteEndElement();
512 }
513
514 // Write any contained Payloads with the PackagePayload being first
515 writer.WriteStartElement("PayloadRef");
516 writer.WriteAttributeString("Id", package.Package.PackagePayload);
517 writer.WriteEndElement();
518
519 IEnumerable<WixBundlePayloadRow> packagePayloads = payloadsByPackage[package.Package.WixChainItemId];
520
521 foreach (WixBundlePayloadRow payload in packagePayloads)
522 {
523 if (payload.Id != package.Package.PackagePayload)
524 {
525 writer.WriteStartElement("PayloadRef");
526 writer.WriteAttributeString("Id", payload.Id);
527 writer.WriteEndElement();
528 }
529 }
530
531 writer.WriteEndElement(); // </XxxPackage>
532 }
533 writer.WriteEndElement(); // </Chain>
534
535 if (null != targetCodes)
536 {
537 foreach (WixBundlePatchTargetCodeRow targetCode in targetCodes)
538 {
539 writer.WriteStartElement("PatchTargetCode");
540 writer.WriteAttributeString("TargetCode", targetCode.TargetCode);
541 writer.WriteAttributeString("Product", targetCode.TargetsProductCode ? "yes" : "no");
542 writer.WriteEndElement();
543 }
544 }
545
546 // Write the ApprovedExeForElevation elements.
547 IEnumerable<WixApprovedExeForElevationRow> approvedExesForElevation = this.Output.Tables["WixApprovedExeForElevation"].RowsAs<WixApprovedExeForElevationRow>();
548
549 foreach (WixApprovedExeForElevationRow approvedExeForElevation in approvedExesForElevation)
550 {
551 writer.WriteStartElement("ApprovedExeForElevation");
552 writer.WriteAttributeString("Id", approvedExeForElevation.Id);
553 writer.WriteAttributeString("Key", approvedExeForElevation.Key);
554
555 if (!String.IsNullOrEmpty(approvedExeForElevation.ValueName))
556 {
557 writer.WriteAttributeString("ValueName", approvedExeForElevation.ValueName);
558 }
559
560 if (approvedExeForElevation.Win64)
561 {
562 writer.WriteAttributeString("Win64", "yes");
563 }
564
565 writer.WriteEndElement();
566 }
567
568 writer.WriteEndDocument(); // </BurnManifest>
569 }
570 }
571
572 private void WriteBurnManifestContainerAttributes(XmlTextWriter writer, string executableName, WixBundleContainerRow container)
573 {
574 writer.WriteAttributeString("Id", container.Id);
575 writer.WriteAttributeString("FileSize", container.Size.ToString(CultureInfo.InvariantCulture));
576 writer.WriteAttributeString("Hash", container.Hash);
577
578 if (ContainerType.Detached == container.Type)
579 {
580 string resolvedUrl = this.ResolveUrl(container.DownloadUrl, null, null, container.Id, container.Name);
581 if (!String.IsNullOrEmpty(resolvedUrl))
582 {
583 writer.WriteAttributeString("DownloadUrl", resolvedUrl);
584 }
585 else if (!String.IsNullOrEmpty(container.DownloadUrl))
586 {
587 writer.WriteAttributeString("DownloadUrl", container.DownloadUrl);
588 }
589
590 writer.WriteAttributeString("FilePath", container.Name);
591 }
592 else if (ContainerType.Attached == container.Type)
593 {
594 if (!String.IsNullOrEmpty(container.DownloadUrl))
595 {
596 Messaging.Instance.OnMessage(WixWarnings.DownloadUrlNotSupportedForAttachedContainers(container.SourceLineNumbers, container.Id));
597 }
598
599 writer.WriteAttributeString("FilePath", executableName); // attached containers use the name of the bundle since they are attached to the executable.
600 writer.WriteAttributeString("AttachedIndex", container.AttachedContainerIndex.ToString(CultureInfo.InvariantCulture));
601 writer.WriteAttributeString("Attached", "yes");
602 writer.WriteAttributeString("Primary", "yes");
603 }
604 }
605
606 private void WriteBurnManifestPayloadAttributes(XmlTextWriter writer, WixBundlePayloadRow payload, bool embeddedOnly, Dictionary<string, WixBundlePayloadRow> allPayloads)
607 {
608 Debug.Assert(!embeddedOnly || PackagingType.Embedded == payload.Packaging);
609
610 writer.WriteAttributeString("Id", payload.Id);
611 writer.WriteAttributeString("FilePath", payload.Name);
612 writer.WriteAttributeString("FileSize", payload.FileSize.ToString(CultureInfo.InvariantCulture));
613 writer.WriteAttributeString("Hash", payload.Hash);
614
615 if (payload.LayoutOnly)
616 {
617 writer.WriteAttributeString("LayoutOnly", "yes");
618 }
619
620 if (!String.IsNullOrEmpty(payload.PublicKey))
621 {
622 writer.WriteAttributeString("CertificateRootPublicKeyIdentifier", payload.PublicKey);
623 }
624
625 if (!String.IsNullOrEmpty(payload.Thumbprint))
626 {
627 writer.WriteAttributeString("CertificateRootThumbprint", payload.Thumbprint);
628 }
629
630 switch (payload.Packaging)
631 {
632 case PackagingType.Embedded: // this means it's in a container.
633 if (!String.IsNullOrEmpty(payload.DownloadUrl))
634 {
635 Messaging.Instance.OnMessage(WixWarnings.DownloadUrlNotSupportedForEmbeddedPayloads(payload.SourceLineNumbers, payload.Id));
636 }
637
638 writer.WriteAttributeString("Packaging", "embedded");
639 writer.WriteAttributeString("SourcePath", payload.EmbeddedId);
640
641 if (Compiler.BurnUXContainerId != payload.Container)
642 {
643 writer.WriteAttributeString("Container", payload.Container);
644 }
645 break;
646
647 case PackagingType.External:
648 string packageId = payload.ParentPackagePayload;
649 string parentUrl = payload.ParentPackagePayload == null ? null : allPayloads[payload.ParentPackagePayload].DownloadUrl;
650 string resolvedUrl = this.ResolveUrl(payload.DownloadUrl, parentUrl, packageId, payload.Id, payload.Name);
651 if (!String.IsNullOrEmpty(resolvedUrl))
652 {
653 writer.WriteAttributeString("DownloadUrl", resolvedUrl);
654 }
655 else if (!String.IsNullOrEmpty(payload.DownloadUrl))
656 {
657 writer.WriteAttributeString("DownloadUrl", payload.DownloadUrl);
658 }
659
660 writer.WriteAttributeString("Packaging", "external");
661 writer.WriteAttributeString("SourcePath", payload.Name);
662 break;
663 }
664
665 if (!String.IsNullOrEmpty(payload.Catalog))
666 {
667 writer.WriteAttributeString("Catalog", payload.Catalog);
668 }
669 }
670
671 private string ResolveUrl(string url, string fallbackUrl, string packageId, string payloadId, string fileName)
672 {
673 string resolved = null;
674 foreach (IBinderFileManager fileManager in this.FileManagers)
675 {
676 resolved = fileManager.ResolveUrl(url, fallbackUrl, packageId, payloadId, fileName);
677 if (!String.IsNullOrEmpty(resolved))
678 {
679 break;
680 }
681 }
682
683 return resolved;
684 }
685 }
686}
diff --git a/src/WixToolset.Core/Bind/Bundles/CreateContainerCommand.cs b/src/WixToolset.Core/Bind/Bundles/CreateContainerCommand.cs
new file mode 100644
index 00000000..1bf987e3
--- /dev/null
+++ b/src/WixToolset.Core/Bind/Bundles/CreateContainerCommand.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.Bind.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.Cab;
11 using WixToolset.Data;
12 using WixToolset.Data.Rows;
13
14 /// <summary>
15 /// Creates cabinet files.
16 /// </summary>
17 internal class CreateContainerCommand : ICommand
18 {
19 public CompressionLevel DefaultCompressionLevel { private get; set; }
20
21 public IEnumerable<WixBundlePayloadRow> Payloads { private get; set; }
22
23 public string ManifestFile { private get; set; }
24
25 public string OutputPath { private get; set; }
26
27 public string Hash { get; private set; }
28
29 public long Size { get; private set; }
30
31 public void Execute()
32 {
33 int payloadCount = this.Payloads.Count(); // The number of embedded payloads
34
35 if (!String.IsNullOrEmpty(this.ManifestFile))
36 {
37 ++payloadCount;
38 }
39
40 using (WixCreateCab cab = new WixCreateCab(Path.GetFileName(this.OutputPath), Path.GetDirectoryName(this.OutputPath), payloadCount, 0, 0, this.DefaultCompressionLevel))
41 {
42 // If a manifest was provided always add it as "payload 0" to the container.
43 if (!String.IsNullOrEmpty(this.ManifestFile))
44 {
45 cab.AddFile(this.ManifestFile, "0");
46 }
47
48 foreach (WixBundlePayloadRow payload in this.Payloads)
49 {
50 Debug.Assert(PackagingType.Embedded == payload.Packaging);
51
52 Messaging.Instance.OnMessage(WixVerboses.LoadingPayload(payload.FullFileName));
53
54 cab.AddFile(payload.FullFileName, payload.EmbeddedId);
55 }
56
57 cab.Complete();
58 }
59
60 // Now that the container is created, set the outputs of the command.
61 FileInfo fileInfo = new FileInfo(this.OutputPath);
62
63 this.Hash = Common.GetFileHash(fileInfo.FullName);
64
65 this.Size = fileInfo.Length;
66 }
67 }
68}
diff --git a/src/WixToolset.Core/Bind/Bundles/GetPackageFacadesCommand.cs b/src/WixToolset.Core/Bind/Bundles/GetPackageFacadesCommand.cs
new file mode 100644
index 00000000..dc19e380
--- /dev/null
+++ b/src/WixToolset.Core/Bind/Bundles/GetPackageFacadesCommand.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.Bind.Bundles
4{
5 using System.Collections.Generic;
6 using WixToolset.Data;
7 using WixToolset.Data.Rows;
8
9 internal class GetPackageFacadesCommand : ICommand
10 {
11 public Table PackageTable { private get; set; }
12
13 public Table ExePackageTable { private get; set; }
14
15 public Table MsiPackageTable { private get; set; }
16
17 public Table MspPackageTable { private get; set; }
18
19 public Table MsuPackageTable { private get; set; }
20
21 public IDictionary<string, PackageFacade> PackageFacades { get; private set; }
22
23 public void Execute()
24 {
25 RowDictionary<WixBundleExePackageRow> exePackages = new RowDictionary<WixBundleExePackageRow>(this.ExePackageTable);
26 RowDictionary<WixBundleMsiPackageRow> msiPackages = new RowDictionary<WixBundleMsiPackageRow>(this.MsiPackageTable);
27 RowDictionary<WixBundleMspPackageRow> mspPackages = new RowDictionary<WixBundleMspPackageRow>(this.MspPackageTable);
28 RowDictionary<WixBundleMsuPackageRow> msuPackages = new RowDictionary<WixBundleMsuPackageRow>(this.MsuPackageTable);
29
30 Dictionary<string, PackageFacade> facades = new Dictionary<string, PackageFacade>(this.PackageTable.Rows.Count);
31
32 foreach (WixBundlePackageRow package in this.PackageTable.Rows)
33 {
34 string id = package.WixChainItemId;
35 PackageFacade facade = null;
36
37 switch (package.Type)
38 {
39 case WixBundlePackageType.Exe:
40 facade = new PackageFacade(package, exePackages.Get(id));
41 break;
42
43 case WixBundlePackageType.Msi:
44 facade = new PackageFacade(package, msiPackages.Get(id));
45 break;
46
47 case WixBundlePackageType.Msp:
48 facade = new PackageFacade(package, mspPackages.Get(id));
49 break;
50
51 case WixBundlePackageType.Msu:
52 facade = new PackageFacade(package, msuPackages.Get(id));
53 break;
54 }
55
56 facades.Add(id, facade);
57 }
58
59 this.PackageFacades = facades;
60 }
61 }
62}
diff --git a/src/WixToolset.Core/Bind/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs b/src/WixToolset.Core/Bind/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs
new file mode 100644
index 00000000..ac3a301d
--- /dev/null
+++ b/src/WixToolset.Core/Bind/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs
@@ -0,0 +1,145 @@
1// Copyright (c) .NET Foundation and 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.Bind.Bundles
4{
5 using System;
6 using System.Collections.Generic;
7 using WixToolset.Data;
8 using WixToolset.Data.Rows;
9
10 internal class OrderPackagesAndRollbackBoundariesCommand : ICommand
11 {
12 public Table WixGroupTable { private get; set; }
13
14 public RowDictionary<WixBundleRollbackBoundaryRow> Boundaries { private get; set; }
15
16 public IDictionary<string, PackageFacade> PackageFacades { private get; set; }
17
18 public IEnumerable<PackageFacade> OrderedPackageFacades { get; private set; }
19
20 public IEnumerable<WixBundleRollbackBoundaryRow> UsedRollbackBoundaries { get; private set; }
21
22 public void Execute()
23 {
24 List<PackageFacade> orderedFacades = new List<PackageFacade>();
25 List<WixBundleRollbackBoundaryRow> usedBoundaries = new List<WixBundleRollbackBoundaryRow>();
26
27 // Process the chain of packages to add them in the correct order
28 // and assign the forward rollback boundaries as appropriate. Remember
29 // rollback boundaries are authored as elements in the chain which
30 // we re-interpret here to add them as attributes on the next available
31 // package in the chain. Essentially we mark some packages as being
32 // the start of a rollback boundary when installing and repairing.
33 // We handle uninstall (aka: backwards) rollback boundaries after
34 // we get these install/repair (aka: forward) rollback boundaries
35 // defined.
36 WixBundleRollbackBoundaryRow previousRollbackBoundary = null;
37 WixBundleRollbackBoundaryRow lastRollbackBoundary = null;
38 bool boundaryHadX86Package = false;
39
40 foreach (WixGroupRow row in this.WixGroupTable.Rows)
41 {
42 if (ComplexReferenceChildType.Package == row.ChildType && ComplexReferenceParentType.PackageGroup == row.ParentType && "WixChain" == row.ParentId)
43 {
44 PackageFacade facade = null;
45 if (PackageFacades.TryGetValue(row.ChildId, out facade))
46 {
47 if (null != previousRollbackBoundary)
48 {
49 usedBoundaries.Add(previousRollbackBoundary);
50 facade.Package.RollbackBoundary = previousRollbackBoundary.ChainPackageId;
51 previousRollbackBoundary = null;
52
53 boundaryHadX86Package = (facade.Package.x64 == YesNoType.Yes);
54 }
55
56 // Error if MSI transaction has x86 package preceding x64 packages
57 if ((lastRollbackBoundary != null) && (lastRollbackBoundary.Transaction == YesNoType.Yes)
58 && boundaryHadX86Package
59 && (facade.Package.x64 == YesNoType.Yes))
60 {
61 Messaging.Instance.OnMessage(WixErrors.MsiTransactionX86BeforeX64(lastRollbackBoundary.SourceLineNumbers));
62 }
63 boundaryHadX86Package = boundaryHadX86Package || (facade.Package.x64 == YesNoType.No);
64
65 orderedFacades.Add(facade);
66 }
67 else // must be a rollback boundary.
68 {
69 // Discard the next rollback boundary if we have a previously defined boundary.
70 WixBundleRollbackBoundaryRow nextRollbackBoundary = Boundaries.Get(row.ChildId);
71 if (null != previousRollbackBoundary)
72 {
73 Messaging.Instance.OnMessage(WixWarnings.DiscardedRollbackBoundary(nextRollbackBoundary.SourceLineNumbers, nextRollbackBoundary.ChainPackageId));
74 }
75 else
76 {
77 previousRollbackBoundary = nextRollbackBoundary;
78 lastRollbackBoundary = nextRollbackBoundary;
79 }
80 }
81 }
82 }
83
84 if (null != previousRollbackBoundary)
85 {
86 Messaging.Instance.OnMessage(WixWarnings.DiscardedRollbackBoundary(previousRollbackBoundary.SourceLineNumbers, previousRollbackBoundary.ChainPackageId));
87 }
88
89 // With the forward rollback boundaries assigned, we can now go
90 // through the packages with rollback boundaries and assign backward
91 // rollback boundaries. Backward rollback boundaries are used when
92 // the chain is going "backwards" which (AFAIK) only happens during
93 // uninstall.
94 //
95 // Consider the scenario with three packages: A, B and C. Packages A
96 // and C are marked as rollback boundary packages and package B is
97 // not. The naive implementation would execute the chain like this
98 // (numbers indicate where rollback boundaries would end up):
99 // install: 1 A B 2 C
100 // uninstall: 2 C B 1 A
101 //
102 // The uninstall chain is wrong, A and B should be grouped together
103 // not C and B. The fix is to label packages with a "backwards"
104 // rollback boundary used during uninstall. The backwards rollback
105 // boundaries are assigned to the package *before* the next rollback
106 // boundary. Using our example from above again, I'll mark the
107 // backwards rollback boundaries prime (aka: with ').
108 // install: 1 A B 1' 2 C 2'
109 // uninstall: 2' C 2 1' B A 1
110 //
111 // If the marked boundaries are ignored during install you get the
112 // same thing as above (good) and if the non-marked boundaries are
113 // ignored during uninstall then A and B are correctly grouped.
114 // Here's what it looks like without all the markers:
115 // install: 1 A B 2 C
116 // uninstall: 2 C 1 B A
117 // Woot!
118 string previousRollbackBoundaryId = null;
119 PackageFacade previousFacade = null;
120
121 foreach (PackageFacade package in orderedFacades)
122 {
123 if (null != package.Package.RollbackBoundary)
124 {
125 if (null != previousFacade)
126 {
127 previousFacade.Package.RollbackBoundaryBackward = previousRollbackBoundaryId;
128 }
129
130 previousRollbackBoundaryId = package.Package.RollbackBoundary;
131 }
132
133 previousFacade = package;
134 }
135
136 if (!String.IsNullOrEmpty(previousRollbackBoundaryId) && null != previousFacade)
137 {
138 previousFacade.Package.RollbackBoundaryBackward = previousRollbackBoundaryId;
139 }
140
141 this.OrderedPackageFacades = orderedFacades;
142 this.UsedRollbackBoundaries = usedBoundaries;
143 }
144 }
145}
diff --git a/src/WixToolset.Core/Bind/Bundles/PackageFacade.cs b/src/WixToolset.Core/Bind/Bundles/PackageFacade.cs
new file mode 100644
index 00000000..f7e6410f
--- /dev/null
+++ b/src/WixToolset.Core/Bind/Bundles/PackageFacade.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 WixToolset.Bind.Bundles
4{
5 using WixToolset.Data.Rows;
6
7 internal class PackageFacade
8 {
9 private PackageFacade(WixBundlePackageRow package)
10 {
11 this.Package = package;
12 this.Provides = new ProvidesDependencyCollection();
13 }
14
15 public PackageFacade(WixBundlePackageRow package, WixBundleExePackageRow exePackage)
16 : this(package)
17 {
18 this.ExePackage = exePackage;
19 }
20
21 public PackageFacade(WixBundlePackageRow package, WixBundleMsiPackageRow msiPackage)
22 : this(package)
23 {
24 this.MsiPackage = msiPackage;
25 }
26
27 public PackageFacade(WixBundlePackageRow package, WixBundleMspPackageRow mspPackage)
28 : this(package)
29 {
30 this.MspPackage = mspPackage;
31 }
32
33 public PackageFacade(WixBundlePackageRow package, WixBundleMsuPackageRow msuPackage)
34 : this(package)
35 {
36 this.MsuPackage = msuPackage;
37 }
38
39 public WixBundlePackageRow Package { get; private set; }
40
41 public WixBundleExePackageRow ExePackage { get; private set; }
42
43 public WixBundleMsiPackageRow MsiPackage { get; private set; }
44
45 public WixBundleMspPackageRow MspPackage { get; private set; }
46
47 public WixBundleMsuPackageRow MsuPackage { get; private set; }
48
49 /// <summary>
50 /// The provides dependencies authored and imported for this package.
51 /// </summary>
52 /// <remarks>
53 /// TODO: Eventually this collection should turn into Rows so they are tracked in the PDB but
54 /// the relationship with the extension makes it much trickier to pull off.
55 /// </remarks>
56 public ProvidesDependencyCollection Provides { get; private set; }
57 }
58}
diff --git a/src/WixToolset.Core/Bind/Bundles/ProcessExePackageCommand.cs b/src/WixToolset.Core/Bind/Bundles/ProcessExePackageCommand.cs
new file mode 100644
index 00000000..a1e7c271
--- /dev/null
+++ b/src/WixToolset.Core/Bind/Bundles/ProcessExePackageCommand.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.Bind.Bundles
4{
5 using System;
6 using WixToolset.Data;
7 using WixToolset.Data.Rows;
8
9 /// <summary>
10 /// Initializes package state from the Exe contents.
11 /// </summary>
12 internal class ProcessExePackageCommand : ICommand
13 {
14 public RowDictionary<WixBundlePayloadRow> AuthoredPayloads { private get; set; }
15
16 public PackageFacade Facade { private get; set; }
17
18 /// <summary>
19 /// Processes the Exe packages to add properties and payloads from the Exe packages.
20 /// </summary>
21 public void Execute()
22 {
23 WixBundlePayloadRow packagePayload = this.AuthoredPayloads.Get(this.Facade.Package.PackagePayload);
24
25 if (String.IsNullOrEmpty(this.Facade.Package.CacheId))
26 {
27 this.Facade.Package.CacheId = packagePayload.Hash;
28 }
29
30 this.Facade.Package.Version = packagePayload.Version;
31 }
32 }
33}
diff --git a/src/WixToolset.Core/Bind/Bundles/ProcessMsiPackageCommand.cs b/src/WixToolset.Core/Bind/Bundles/ProcessMsiPackageCommand.cs
new file mode 100644
index 00000000..f73776c0
--- /dev/null
+++ b/src/WixToolset.Core/Bind/Bundles/ProcessMsiPackageCommand.cs
@@ -0,0 +1,560 @@
1// Copyright (c) .NET Foundation and 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.Bind.Bundles
4{
5 using System;
6 using System.Collections;
7 using System.Collections.Generic;
8 using System.Diagnostics;
9 using System.Globalization;
10 using System.IO;
11 using System.Linq;
12 using WixToolset.Data;
13 using WixToolset.Data.Rows;
14 using WixToolset.Extensibility;
15 using WixToolset.Msi;
16 using WixToolset.Core.Native;
17 using Dtf = WixToolset.Dtf.WindowsInstaller;
18
19 /// <summary>
20 /// Initializes package state from the MSI contents.
21 /// </summary>
22 internal class ProcessMsiPackageCommand : ICommand
23 {
24 private const string PropertySqlFormat = "SELECT `Value` FROM `Property` WHERE `Property` = '{0}'";
25
26 public RowDictionary<WixBundlePayloadRow> AuthoredPayloads { private get; set; }
27
28 public PackageFacade Facade { private get; set; }
29
30 public IBinderFileManager FileManager { private get; set; }
31
32 public Table MsiFeatureTable { private get; set; }
33
34 public Table MsiPropertyTable { private get; set; }
35
36 public Table PayloadTable { private get; set; }
37
38 public Table RelatedPackageTable { private get; set; }
39
40 /// <summary>
41 /// Processes the MSI packages to add properties and payloads from the MSI packages.
42 /// </summary>
43 public void Execute()
44 {
45 WixBundlePayloadRow packagePayload = this.AuthoredPayloads.Get(this.Facade.Package.PackagePayload);
46
47 string sourcePath = packagePayload.FullFileName;
48 bool longNamesInImage = false;
49 bool compressed = false;
50 bool x64 = false;
51 try
52 {
53 // Read data out of the msi database...
54 using (Dtf.SummaryInfo sumInfo = new Dtf.SummaryInfo(sourcePath, false))
55 {
56 // 1 is the Word Count summary information stream bit that means
57 // the MSI uses short file names when set. We care about long file
58 // names so check when the bit is not set.
59 longNamesInImage = 0 == (sumInfo.WordCount & 1);
60
61 // 2 is the Word Count summary information stream bit that means
62 // files are compressed in the MSI by default when the bit is set.
63 compressed = 2 == (sumInfo.WordCount & 2);
64
65 x64 = (sumInfo.Template.Contains("x64") || sumInfo.Template.Contains("Intel64"));
66
67 // 8 is the Word Count summary information stream bit that means
68 // "Elevated privileges are not required to install this package."
69 // in MSI 4.5 and below, if this bit is 0, elevation is required.
70 this.Facade.Package.PerMachine = (0 == (sumInfo.WordCount & 8)) ? YesNoDefaultType.Yes : YesNoDefaultType.No;
71 this.Facade.Package.x64 = x64 ? YesNoType.Yes : YesNoType.No;
72 }
73
74 using (Dtf.Database db = new Dtf.Database(sourcePath))
75 {
76 this.Facade.MsiPackage.ProductCode = ProcessMsiPackageCommand.GetProperty(db, "ProductCode");
77 this.Facade.MsiPackage.UpgradeCode = ProcessMsiPackageCommand.GetProperty(db, "UpgradeCode");
78 this.Facade.MsiPackage.Manufacturer = ProcessMsiPackageCommand.GetProperty(db, "Manufacturer");
79 this.Facade.MsiPackage.ProductLanguage = Convert.ToInt32(ProcessMsiPackageCommand.GetProperty(db, "ProductLanguage"), CultureInfo.InvariantCulture);
80 this.Facade.MsiPackage.ProductVersion = ProcessMsiPackageCommand.GetProperty(db, "ProductVersion");
81
82 if (!Common.IsValidModuleOrBundleVersion(this.Facade.MsiPackage.ProductVersion))
83 {
84 // not a proper .NET version (e.g., five fields); can we get a valid four-part version number?
85 string version = null;
86 string[] versionParts = this.Facade.MsiPackage.ProductVersion.Split('.');
87 int count = versionParts.Length;
88 if (0 < count)
89 {
90 version = versionParts[0];
91 for (int i = 1; i < 4 && i < count; ++i)
92 {
93 version = String.Concat(version, ".", versionParts[i]);
94 }
95 }
96
97 if (!String.IsNullOrEmpty(version) && Common.IsValidModuleOrBundleVersion(version))
98 {
99 Messaging.Instance.OnMessage(WixWarnings.VersionTruncated(this.Facade.Package.SourceLineNumbers, this.Facade.MsiPackage.ProductVersion, sourcePath, version));
100 this.Facade.MsiPackage.ProductVersion = version;
101 }
102 else
103 {
104 Messaging.Instance.OnMessage(WixErrors.InvalidProductVersion(this.Facade.Package.SourceLineNumbers, this.Facade.MsiPackage.ProductVersion, sourcePath));
105 }
106 }
107
108 if (String.IsNullOrEmpty(this.Facade.Package.CacheId))
109 {
110 this.Facade.Package.CacheId = String.Format("{0}v{1}", this.Facade.MsiPackage.ProductCode, this.Facade.MsiPackage.ProductVersion);
111 }
112
113 if (String.IsNullOrEmpty(this.Facade.Package.DisplayName))
114 {
115 this.Facade.Package.DisplayName = ProcessMsiPackageCommand.GetProperty(db, "ProductName");
116 }
117
118 if (String.IsNullOrEmpty(this.Facade.Package.Description))
119 {
120 this.Facade.Package.Description = ProcessMsiPackageCommand.GetProperty(db, "ARPCOMMENTS");
121 }
122
123 ISet<string> payloadNames = this.GetPayloadTargetNames();
124
125 ISet<string> msiPropertyNames = this.GetMsiPropertyNames();
126
127 this.SetPerMachineAppropriately(db, sourcePath);
128
129 // Ensure the MSI package is appropriately marked visible or not.
130 this.SetPackageVisibility(db, msiPropertyNames);
131
132 // Unless the MSI or setup code overrides the default, set MSIFASTINSTALL for best performance.
133 if (!msiPropertyNames.Contains("MSIFASTINSTALL") && !ProcessMsiPackageCommand.HasProperty(db, "MSIFASTINSTALL"))
134 {
135 this.AddMsiProperty("MSIFASTINSTALL", "7");
136 }
137
138 this.CreateRelatedPackages(db);
139
140 // If feature selection is enabled, represent the Feature table in the manifest.
141 if (this.Facade.MsiPackage.EnableFeatureSelection)
142 {
143 this.CreateMsiFeatures(db);
144 }
145
146 // Add all external cabinets as package payloads.
147 this.ImportExternalCabinetAsPayloads(db, packagePayload, payloadNames);
148
149 // Add all external files as package payloads and calculate the total install size as the rollup of
150 // File table's sizes.
151 this.Facade.Package.InstallSize = this.ImportExternalFileAsPayloadsAndReturnInstallSize(db, packagePayload, longNamesInImage, compressed, payloadNames);
152
153 // Add all dependency providers from the MSI.
154 this.ImportDependencyProviders(db);
155 }
156 }
157 catch (Dtf.InstallerException e)
158 {
159 Messaging.Instance.OnMessage(WixErrors.UnableToReadPackageInformation(this.Facade.Package.SourceLineNumbers, sourcePath, e.Message));
160 }
161 }
162
163 private ISet<string> GetPayloadTargetNames()
164 {
165 IEnumerable<string> payloadNames = this.PayloadTable.RowsAs<WixBundlePayloadRow>()
166 .Where(r => r.Package == this.Facade.Package.WixChainItemId)
167 .Select(r => r.Name);
168
169 return new HashSet<string>(payloadNames, StringComparer.OrdinalIgnoreCase);
170 }
171
172 private ISet<string> GetMsiPropertyNames()
173 {
174 IEnumerable<string> properties = this.MsiPropertyTable.RowsAs<WixBundleMsiPropertyRow>()
175 .Where(r => r.ChainPackageId == this.Facade.Package.WixChainItemId)
176 .Select(r => r.Name);
177
178 return new HashSet<string>(properties, StringComparer.Ordinal);
179 }
180
181 private void SetPerMachineAppropriately(Dtf.Database db, string sourcePath)
182 {
183 if (this.Facade.MsiPackage.ForcePerMachine)
184 {
185 if (YesNoDefaultType.No == this.Facade.Package.PerMachine)
186 {
187 Messaging.Instance.OnMessage(WixWarnings.PerUserButForcingPerMachine(this.Facade.Package.SourceLineNumbers, sourcePath));
188 this.Facade.Package.PerMachine = YesNoDefaultType.Yes; // ensure that we think the package is per-machine.
189 }
190
191 // Force ALLUSERS=1 via the MSI command-line.
192 this.AddMsiProperty("ALLUSERS", "1");
193 }
194 else
195 {
196 string allusers = ProcessMsiPackageCommand.GetProperty(db, "ALLUSERS");
197
198 if (String.IsNullOrEmpty(allusers))
199 {
200 // Not forced per-machine and no ALLUSERS property, flip back to per-user.
201 if (YesNoDefaultType.Yes == this.Facade.Package.PerMachine)
202 {
203 Messaging.Instance.OnMessage(WixWarnings.ImplicitlyPerUser(this.Facade.Package.SourceLineNumbers, sourcePath));
204 this.Facade.Package.PerMachine = YesNoDefaultType.No;
205 }
206 }
207 else if (allusers.Equals("1", StringComparison.Ordinal))
208 {
209 if (YesNoDefaultType.No == this.Facade.Package.PerMachine)
210 {
211 Messaging.Instance.OnMessage(WixErrors.PerUserButAllUsersEquals1(this.Facade.Package.SourceLineNumbers, sourcePath));
212 }
213 }
214 else if (allusers.Equals("2", StringComparison.Ordinal))
215 {
216 Messaging.Instance.OnMessage(WixWarnings.DiscouragedAllUsersValue(this.Facade.Package.SourceLineNumbers, sourcePath, (YesNoDefaultType.Yes == this.Facade.Package.PerMachine) ? "machine" : "user"));
217 }
218 else
219 {
220 Messaging.Instance.OnMessage(WixErrors.UnsupportedAllUsersValue(this.Facade.Package.SourceLineNumbers, sourcePath, allusers));
221 }
222 }
223 }
224
225 private void SetPackageVisibility(Dtf.Database db, ISet<string> msiPropertyNames)
226 {
227 bool alreadyVisible = !ProcessMsiPackageCommand.HasProperty(db, "ARPSYSTEMCOMPONENT");
228
229 if (alreadyVisible != this.Facade.Package.Visible) // if not already set to the correct visibility.
230 {
231 // If the authoring specifically added "ARPSYSTEMCOMPONENT", don't do it again.
232 if (!msiPropertyNames.Contains("ARPSYSTEMCOMPONENT"))
233 {
234 this.AddMsiProperty("ARPSYSTEMCOMPONENT", this.Facade.Package.Visible ? String.Empty : "1");
235 }
236 }
237 }
238
239 private void CreateRelatedPackages(Dtf.Database db)
240 {
241 // Represent the Upgrade table as related packages.
242 if (db.Tables.Contains("Upgrade"))
243 {
244 using (Dtf.View view = db.OpenView("SELECT `UpgradeCode`, `VersionMin`, `VersionMax`, `Language`, `Attributes` FROM `Upgrade`"))
245 {
246 view.Execute();
247 while (true)
248 {
249 using (Dtf.Record record = view.Fetch())
250 {
251 if (null == record)
252 {
253 break;
254 }
255
256 WixBundleRelatedPackageRow related = (WixBundleRelatedPackageRow)this.RelatedPackageTable.CreateRow(this.Facade.Package.SourceLineNumbers);
257 related.ChainPackageId = this.Facade.Package.WixChainItemId;
258 related.Id = record.GetString(1);
259 related.MinVersion = record.GetString(2);
260 related.MaxVersion = record.GetString(3);
261 related.Languages = record.GetString(4);
262
263 int attributes = record.GetInteger(5);
264 related.OnlyDetect = (attributes & MsiInterop.MsidbUpgradeAttributesOnlyDetect) == MsiInterop.MsidbUpgradeAttributesOnlyDetect;
265 related.MinInclusive = (attributes & MsiInterop.MsidbUpgradeAttributesVersionMinInclusive) == MsiInterop.MsidbUpgradeAttributesVersionMinInclusive;
266 related.MaxInclusive = (attributes & MsiInterop.MsidbUpgradeAttributesVersionMaxInclusive) == MsiInterop.MsidbUpgradeAttributesVersionMaxInclusive;
267 related.LangInclusive = (attributes & MsiInterop.MsidbUpgradeAttributesLanguagesExclusive) == 0;
268 }
269 }
270 }
271 }
272 }
273
274 private void CreateMsiFeatures(Dtf.Database db)
275 {
276 if (db.Tables.Contains("Feature"))
277 {
278 using (Dtf.View featureView = db.OpenView("SELECT `Component_` FROM `FeatureComponents` WHERE `Feature_` = ?"))
279 using (Dtf.View componentView = db.OpenView("SELECT `FileSize` FROM `File` WHERE `Component_` = ?"))
280 {
281 using (Dtf.Record featureRecord = new Dtf.Record(1))
282 using (Dtf.Record componentRecord = new Dtf.Record(1))
283 {
284 using (Dtf.View allFeaturesView = db.OpenView("SELECT * FROM `Feature`"))
285 {
286 allFeaturesView.Execute();
287
288 while (true)
289 {
290 using (Dtf.Record allFeaturesResultRecord = allFeaturesView.Fetch())
291 {
292 if (null == allFeaturesResultRecord)
293 {
294 break;
295 }
296
297 string featureName = allFeaturesResultRecord.GetString(1);
298
299 // Calculate the Feature size.
300 featureRecord.SetString(1, featureName);
301 featureView.Execute(featureRecord);
302
303 // Loop over all the components for the feature to calculate the size of the feature.
304 long size = 0;
305 while (true)
306 {
307 using (Dtf.Record componentResultRecord = featureView.Fetch())
308 {
309 if (null == componentResultRecord)
310 {
311 break;
312 }
313 string component = componentResultRecord.GetString(1);
314 componentRecord.SetString(1, component);
315 componentView.Execute(componentRecord);
316
317 while (true)
318 {
319 using (Dtf.Record fileResultRecord = componentView.Fetch())
320 {
321 if (null == fileResultRecord)
322 {
323 break;
324 }
325
326 string fileSize = fileResultRecord.GetString(1);
327 size += Convert.ToInt32(fileSize, CultureInfo.InvariantCulture.NumberFormat);
328 }
329 }
330 }
331 }
332
333 WixBundleMsiFeatureRow feature = (WixBundleMsiFeatureRow)this.MsiFeatureTable.CreateRow(this.Facade.Package.SourceLineNumbers);
334 feature.ChainPackageId = this.Facade.Package.WixChainItemId;
335 feature.Name = featureName;
336 feature.Parent = allFeaturesResultRecord.GetString(2);
337 feature.Title = allFeaturesResultRecord.GetString(3);
338 feature.Description = allFeaturesResultRecord.GetString(4);
339 feature.Display = allFeaturesResultRecord.GetInteger(5);
340 feature.Level = allFeaturesResultRecord.GetInteger(6);
341 feature.Directory = allFeaturesResultRecord.GetString(7);
342 feature.Attributes = allFeaturesResultRecord.GetInteger(8);
343 feature.Size = size;
344 }
345 }
346 }
347 }
348 }
349 }
350 }
351
352 private void ImportExternalCabinetAsPayloads(Dtf.Database db, WixBundlePayloadRow packagePayload, ISet<string> payloadNames)
353 {
354 if (db.Tables.Contains("Media"))
355 {
356 foreach (string cabinet in db.ExecuteStringQuery("SELECT `Cabinet` FROM `Media`"))
357 {
358 if (!String.IsNullOrEmpty(cabinet) && !cabinet.StartsWith("#", StringComparison.Ordinal))
359 {
360 // If we didn't find the Payload as an existing child of the package, we need to
361 // add it. We expect the file to exist on-disk in the same relative location as
362 // the MSI expects to find it...
363 string cabinetName = Path.Combine(Path.GetDirectoryName(packagePayload.Name), cabinet);
364
365 if (!payloadNames.Contains(cabinetName))
366 {
367 string generatedId = Common.GenerateIdentifier("cab", packagePayload.Id, cabinet);
368 string payloadSourceFile = FileManager.ResolveRelatedFile(packagePayload.UnresolvedSourceFile, cabinet, "Cabinet", this.Facade.Package.SourceLineNumbers, BindStage.Normal);
369
370 WixBundlePayloadRow payload = (WixBundlePayloadRow)this.PayloadTable.CreateRow(this.Facade.Package.SourceLineNumbers);
371 payload.Id = generatedId;
372 payload.Name = cabinetName;
373 payload.SourceFile = payloadSourceFile;
374 payload.Compressed = packagePayload.Compressed;
375 payload.UnresolvedSourceFile = cabinetName;
376 payload.Package = packagePayload.Package;
377 payload.Container = packagePayload.Container;
378 payload.ContentFile = true;
379 payload.EnableSignatureValidation = packagePayload.EnableSignatureValidation;
380 payload.Packaging = packagePayload.Packaging;
381 payload.ParentPackagePayload = packagePayload.Id;
382 }
383 }
384 }
385 }
386 }
387
388 private long ImportExternalFileAsPayloadsAndReturnInstallSize(Dtf.Database db, WixBundlePayloadRow packagePayload, bool longNamesInImage, bool compressed, ISet<string> payloadNames)
389 {
390 long size = 0;
391
392 if (db.Tables.Contains("Component") && db.Tables.Contains("Directory") && db.Tables.Contains("File"))
393 {
394 Hashtable directories = new Hashtable();
395
396 // Load up the directory hash table so we will be able to resolve source paths
397 // for files in the MSI database.
398 using (Dtf.View view = db.OpenView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`"))
399 {
400 view.Execute();
401 while (true)
402 {
403 using (Dtf.Record record = view.Fetch())
404 {
405 if (null == record)
406 {
407 break;
408 }
409
410 string sourceName = Installer.GetName(record.GetString(3), true, longNamesInImage);
411 directories.Add(record.GetString(1), new ResolvedDirectory(record.GetString(2), sourceName));
412 }
413 }
414 }
415
416 // Resolve the source paths to external files and add each file size to the total
417 // install size of the package.
418 using (Dtf.View view = db.OpenView("SELECT `Directory_`, `File`, `FileName`, `File`.`Attributes`, `FileSize` FROM `Component`, `File` WHERE `Component`.`Component`=`File`.`Component_`"))
419 {
420 view.Execute();
421 while (true)
422 {
423 using (Dtf.Record record = view.Fetch())
424 {
425 if (null == record)
426 {
427 break;
428 }
429
430 // Skip adding the loose files as payloads if it was suppressed.
431 if (!this.Facade.MsiPackage.SuppressLooseFilePayloadGeneration)
432 {
433 // If the file is explicitly uncompressed or the MSI is uncompressed and the file is not
434 // explicitly marked compressed then this is an external file.
435 if (MsiInterop.MsidbFileAttributesNoncompressed == (record.GetInteger(4) & MsiInterop.MsidbFileAttributesNoncompressed) ||
436 (!compressed && 0 == (record.GetInteger(4) & MsiInterop.MsidbFileAttributesCompressed)))
437 {
438 string fileSourcePath = Binder.GetFileSourcePath(directories, record.GetString(1), record.GetString(3), compressed, longNamesInImage);
439 string name = Path.Combine(Path.GetDirectoryName(packagePayload.Name), fileSourcePath);
440
441 if (!payloadNames.Contains(name))
442 {
443 string generatedId = Common.GenerateIdentifier("f", packagePayload.Id, record.GetString(2));
444 string payloadSourceFile = FileManager.ResolveRelatedFile(packagePayload.UnresolvedSourceFile, fileSourcePath, "File", this.Facade.Package.SourceLineNumbers, BindStage.Normal);
445
446 WixBundlePayloadRow payload = (WixBundlePayloadRow)this.PayloadTable.CreateRow(this.Facade.Package.SourceLineNumbers);
447 payload.Id = generatedId;
448 payload.Name = name;
449 payload.SourceFile = payloadSourceFile;
450 payload.Compressed = packagePayload.Compressed;
451 payload.UnresolvedSourceFile = name;
452 payload.Package = packagePayload.Package;
453 payload.Container = packagePayload.Container;
454 payload.ContentFile = true;
455 payload.EnableSignatureValidation = packagePayload.EnableSignatureValidation;
456 payload.Packaging = packagePayload.Packaging;
457 payload.ParentPackagePayload = packagePayload.Id;
458 }
459 }
460 }
461
462 size += record.GetInteger(5);
463 }
464 }
465 }
466 }
467
468 return size;
469 }
470
471 private void AddMsiProperty(string name, string value)
472 {
473 WixBundleMsiPropertyRow row = (WixBundleMsiPropertyRow)this.MsiPropertyTable.CreateRow(this.Facade.MsiPackage.SourceLineNumbers);
474 row.ChainPackageId = this.Facade.Package.WixChainItemId;
475 row.Name = name;
476 row.Value = value;
477 }
478
479 private void ImportDependencyProviders(Dtf.Database db)
480 {
481 if (db.Tables.Contains("WixDependencyProvider"))
482 {
483 string query = "SELECT `ProviderKey`, `Version`, `DisplayName`, `Attributes` FROM `WixDependencyProvider`";
484
485 using (Dtf.View view = db.OpenView(query))
486 {
487 view.Execute();
488 while (true)
489 {
490 using (Dtf.Record record = view.Fetch())
491 {
492 if (null == record)
493 {
494 break;
495 }
496
497 // Import the provider key and attributes.
498 string providerKey = record.GetString(1);
499 string version = record.GetString(2) ?? this.Facade.MsiPackage.ProductVersion;
500 string displayName = record.GetString(3) ?? this.Facade.Package.DisplayName;
501 int attributes = record.GetInteger(4);
502
503 ProvidesDependency dependency = new ProvidesDependency(providerKey, version, displayName, attributes);
504 dependency.Imported = true;
505
506 this.Facade.Provides.Add(dependency);
507 }
508 }
509 }
510 }
511 }
512
513 /// <summary>
514 /// Queries a Windows Installer database for a Property value.
515 /// </summary>
516 /// <param name="db">Database to query.</param>
517 /// <param name="property">Property to examine.</param>
518 /// <returns>String value for result or null if query doesn't match a single result.</returns>
519 private static string GetProperty(Dtf.Database db, string property)
520 {
521 try
522 {
523 return db.ExecuteScalar(PropertyQuery(property)).ToString();
524 }
525 catch (Dtf.InstallerException)
526 {
527 }
528
529 return null;
530 }
531
532 /// <summary>
533 /// Queries a Windows Installer database to determine if one or more rows exist in the Property table.
534 /// </summary>
535 /// <param name="db">Database to query.</param>
536 /// <param name="property">Property to examine.</param>
537 /// <returns>True if query matches at least one result.</returns>
538 private static bool HasProperty(Dtf.Database db, string property)
539 {
540 try
541 {
542 return 0 < db.ExecuteQuery(PropertyQuery(property)).Count;
543 }
544 catch (Dtf.InstallerException)
545 {
546 }
547
548 return false;
549 }
550
551 private static string PropertyQuery(string property)
552 {
553 // quick sanity check that we'll be creating a valid query...
554 // TODO: Are there any other special characters we should be looking for?
555 Debug.Assert(!property.Contains("'"));
556
557 return String.Format(CultureInfo.InvariantCulture, ProcessMsiPackageCommand.PropertySqlFormat, property);
558 }
559 }
560}
diff --git a/src/WixToolset.Core/Bind/Bundles/ProcessMspPackageCommand.cs b/src/WixToolset.Core/Bind/Bundles/ProcessMspPackageCommand.cs
new file mode 100644
index 00000000..24063221
--- /dev/null
+++ b/src/WixToolset.Core/Bind/Bundles/ProcessMspPackageCommand.cs
@@ -0,0 +1,189 @@
1// Copyright (c) .NET Foundation and 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.Bind.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.Text;
11 using System.Xml;
12 using WixToolset.Data;
13 using WixToolset.Data.Rows;
14 using Dtf = WixToolset.Dtf.WindowsInstaller;
15
16 /// <summary>
17 /// Initializes package state from the Msp contents.
18 /// </summary>
19 internal class ProcessMspPackageCommand : ICommand
20 {
21 private const string PatchMetadataFormat = "SELECT `Value` FROM `MsiPatchMetadata` WHERE `Property` = '{0}'";
22 private static readonly Encoding XmlOutputEncoding = new UTF8Encoding(false);
23
24 public RowDictionary<WixBundlePayloadRow> AuthoredPayloads { private get; set; }
25
26 public PackageFacade Facade { private get; set; }
27
28 public Table WixBundlePatchTargetCodeTable { private get; set; }
29
30 /// <summary>
31 /// Processes the Msp packages to add properties and payloads from the Msp packages.
32 /// </summary>
33 public void Execute()
34 {
35 WixBundlePayloadRow packagePayload = this.AuthoredPayloads.Get(this.Facade.Package.PackagePayload);
36
37 string sourcePath = packagePayload.FullFileName;
38
39 try
40 {
41 // Read data out of the msp database...
42 using (Dtf.SummaryInfo sumInfo = new Dtf.SummaryInfo(sourcePath, false))
43 {
44 this.Facade.MspPackage.PatchCode = sumInfo.RevisionNumber.Substring(0, 38);
45 }
46
47 using (Dtf.Database db = new Dtf.Database(sourcePath))
48 {
49 if (String.IsNullOrEmpty(this.Facade.Package.DisplayName))
50 {
51 this.Facade.Package.DisplayName = ProcessMspPackageCommand.GetPatchMetadataProperty(db, "DisplayName");
52 }
53
54 if (String.IsNullOrEmpty(this.Facade.Package.Description))
55 {
56 this.Facade.Package.Description = ProcessMspPackageCommand.GetPatchMetadataProperty(db, "Description");
57 }
58
59 this.Facade.MspPackage.Manufacturer = ProcessMspPackageCommand.GetPatchMetadataProperty(db, "ManufacturerName");
60 }
61
62 this.ProcessPatchXml(packagePayload, sourcePath);
63 }
64 catch (Dtf.InstallerException e)
65 {
66 Messaging.Instance.OnMessage(WixErrors.UnableToReadPackageInformation(packagePayload.SourceLineNumbers, sourcePath, e.Message));
67 return;
68 }
69
70 if (String.IsNullOrEmpty(this.Facade.Package.CacheId))
71 {
72 this.Facade.Package.CacheId = this.Facade.MspPackage.PatchCode;
73 }
74 }
75
76 private void ProcessPatchXml(WixBundlePayloadRow packagePayload, string sourcePath)
77 {
78 HashSet<string> uniqueTargetCodes = new HashSet<string>();
79
80 string patchXml = Dtf.Installer.ExtractPatchXmlData(sourcePath);
81
82 XmlDocument doc = new XmlDocument();
83 doc.LoadXml(patchXml);
84
85 XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
86 nsmgr.AddNamespace("p", "http://www.microsoft.com/msi/patch_applicability.xsd");
87
88 // Determine target ProductCodes and/or UpgradeCodes.
89 foreach (XmlNode node in doc.SelectNodes("/p:MsiPatch/p:TargetProduct", nsmgr))
90 {
91 // If this patch targets a product code, this is the best case.
92 XmlNode targetCodeElement = node.SelectSingleNode("p:TargetProductCode", nsmgr);
93 WixBundlePatchTargetCodeAttributes attributes = WixBundlePatchTargetCodeAttributes.None;
94
95 if (ProcessMspPackageCommand.TargetsCode(targetCodeElement))
96 {
97 attributes = WixBundlePatchTargetCodeAttributes.TargetsProductCode;
98 }
99 else // maybe targets an upgrade code?
100 {
101 targetCodeElement = node.SelectSingleNode("p:UpgradeCode", nsmgr);
102 if (ProcessMspPackageCommand.TargetsCode(targetCodeElement))
103 {
104 attributes = WixBundlePatchTargetCodeAttributes.TargetsUpgradeCode;
105 }
106 else // this patch targets an unknown number of products
107 {
108 this.Facade.MspPackage.Attributes |= WixBundleMspPackageAttributes.TargetUnspecified;
109 }
110 }
111
112 string targetCode = targetCodeElement.InnerText;
113
114 if (uniqueTargetCodes.Add(targetCode))
115 {
116 WixBundlePatchTargetCodeRow row = (WixBundlePatchTargetCodeRow)this.WixBundlePatchTargetCodeTable.CreateRow(packagePayload.SourceLineNumbers);
117 row.MspPackageId = packagePayload.Id;
118 row.TargetCode = targetCode;
119 row.Attributes = attributes;
120 }
121 }
122
123 // Suppress patch sequence data for improved performance.
124 XmlNode root = doc.DocumentElement;
125 foreach (XmlNode node in root.SelectNodes("p:SequenceData", nsmgr))
126 {
127 root.RemoveChild(node);
128 }
129
130 // Save the XML as compact as possible.
131 using (StringWriter writer = new StringWriter())
132 {
133 XmlWriterSettings settings = new XmlWriterSettings()
134 {
135 Encoding = ProcessMspPackageCommand.XmlOutputEncoding,
136 Indent = false,
137 NewLineChars = string.Empty,
138 NewLineHandling = NewLineHandling.Replace,
139 };
140
141 using (XmlWriter xmlWriter = XmlWriter.Create(writer, settings))
142 {
143 doc.WriteTo(xmlWriter);
144 }
145
146 this.Facade.MspPackage.PatchXml = writer.ToString();
147 }
148 }
149
150 /// <summary>
151 /// Queries a Windows Installer patch database for a Property value from the MsiPatchMetadata table.
152 /// </summary>
153 /// <param name="db">Database to query.</param>
154 /// <param name="property">Property to examine.</param>
155 /// <returns>String value for result or null if query doesn't match a single result.</returns>
156 private static string GetPatchMetadataProperty(Dtf.Database db, string property)
157 {
158 try
159 {
160 return db.ExecuteScalar(PatchMetadataPropertyQuery(property)).ToString();
161 }
162 catch (Dtf.InstallerException)
163 {
164 }
165
166 return null;
167 }
168
169 private static string PatchMetadataPropertyQuery(string property)
170 {
171 // quick sanity check that we'll be creating a valid query...
172 // TODO: Are there any other special characters we should be looking for?
173 Debug.Assert(!property.Contains("'"));
174
175 return String.Format(CultureInfo.InvariantCulture, ProcessMspPackageCommand.PatchMetadataFormat, property);
176 }
177
178 private static bool TargetsCode(XmlNode node)
179 {
180 if (null != node)
181 {
182 XmlAttribute attr = node.Attributes["Validate"];
183 return null != attr && "true".Equals(attr.Value);
184 }
185
186 return false;
187 }
188 }
189}
diff --git a/src/WixToolset.Core/Bind/Bundles/ProcessMsuPackageCommand.cs b/src/WixToolset.Core/Bind/Bundles/ProcessMsuPackageCommand.cs
new file mode 100644
index 00000000..ba59f5f5
--- /dev/null
+++ b/src/WixToolset.Core/Bind/Bundles/ProcessMsuPackageCommand.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.Bind.Bundles
4{
5 using System;
6 using WixToolset.Data;
7 using WixToolset.Data.Rows;
8
9 /// <summary>
10 /// Processes the Msu packages to add properties and payloads from the Msu packages.
11 /// </summary>
12 internal class ProcessMsuPackageCommand : ICommand
13 {
14 public RowDictionary<WixBundlePayloadRow> AuthoredPayloads { private get; set; }
15
16 public PackageFacade Facade { private get; set; }
17
18 public void Execute()
19 {
20 WixBundlePayloadRow packagePayload = this.AuthoredPayloads.Get(this.Facade.Package.PackagePayload);
21
22 if (String.IsNullOrEmpty(this.Facade.Package.CacheId))
23 {
24 this.Facade.Package.CacheId = packagePayload.Hash;
25 }
26
27 this.Facade.Package.PerMachine = YesNoDefaultType.Yes; // MSUs are always per-machine.
28 }
29 }
30}
diff --git a/src/WixToolset.Core/Bind/Bundles/ProcessPayloadsCommand.cs b/src/WixToolset.Core/Bind/Bundles/ProcessPayloadsCommand.cs
new file mode 100644
index 00000000..a83a7a4a
--- /dev/null
+++ b/src/WixToolset.Core/Bind/Bundles/ProcessPayloadsCommand.cs
@@ -0,0 +1,159 @@
1// Copyright (c) .NET Foundation and 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.Bind.Bundles
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.IO;
9 using System.Security.Cryptography;
10 using System.Security.Cryptography.X509Certificates;
11 using System.Text;
12 using WixToolset.Data;
13 using WixToolset.Data.Rows;
14
15 internal class ProcessPayloadsCommand : ICommand
16 {
17 private static readonly Version EmptyVersion = new Version(0, 0, 0, 0);
18
19 public IEnumerable<WixBundlePayloadRow> Payloads { private get; set; }
20
21 public PackagingType DefaultPackaging { private get; set; }
22
23 public string LayoutDirectory { private get; set; }
24
25 public IEnumerable<FileTransfer> FileTransfers { get; private set; }
26
27 public void Execute()
28 {
29 List<FileTransfer> fileTransfers = new List<FileTransfer>();
30
31 foreach (WixBundlePayloadRow payload in this.Payloads)
32 {
33 string normalizedPath = payload.Name.Replace('\\', '/');
34 if (normalizedPath.StartsWith("../", StringComparison.Ordinal) || normalizedPath.Contains("/../"))
35 {
36 Messaging.Instance.OnMessage(WixErrors.PayloadMustBeRelativeToCache(payload.SourceLineNumbers, "Payload", "Name", payload.Name));
37 }
38
39 // Embedded files (aka: files from binary .wixlibs) are not content files (because they are hidden
40 // in the .wixlib).
41 ObjectField field = (ObjectField)payload.Fields[2];
42 payload.ContentFile = !field.EmbeddedFileIndex.HasValue;
43
44 this.UpdatePayloadPackagingType(payload);
45
46 if (String.IsNullOrEmpty(payload.SourceFile))
47 {
48 // Remote payloads obviously cannot be embedded.
49 Debug.Assert(PackagingType.Embedded != payload.Packaging);
50 }
51 else // not a remote payload so we have a lot more to update.
52 {
53 this.UpdatePayloadFileInformation(payload);
54
55 this.UpdatePayloadVersionInformation(payload);
56
57 // External payloads need to be transfered.
58 if (PackagingType.External == payload.Packaging)
59 {
60 FileTransfer transfer;
61 if (FileTransfer.TryCreate(payload.FullFileName, Path.Combine(this.LayoutDirectory, payload.Name), false, "Payload", payload.SourceLineNumbers, out transfer))
62 {
63 fileTransfers.Add(transfer);
64 }
65 }
66 }
67 }
68
69 this.FileTransfers = fileTransfers;
70 }
71
72 private void UpdatePayloadPackagingType(WixBundlePayloadRow payload)
73 {
74 if (PackagingType.Unknown == payload.Packaging)
75 {
76 if (YesNoDefaultType.Yes == payload.Compressed)
77 {
78 payload.Packaging = PackagingType.Embedded;
79 }
80 else if (YesNoDefaultType.No == payload.Compressed)
81 {
82 payload.Packaging = PackagingType.External;
83 }
84 else
85 {
86 payload.Packaging = this.DefaultPackaging;
87 }
88 }
89
90 // Embedded payloads that are not assigned a container already are placed in the default attached
91 // container.
92 if (PackagingType.Embedded == payload.Packaging && String.IsNullOrEmpty(payload.Container))
93 {
94 payload.Container = Compiler.BurnDefaultAttachedContainerId;
95 }
96 }
97
98 private void UpdatePayloadFileInformation(WixBundlePayloadRow payload)
99 {
100 FileInfo fileInfo = new FileInfo(payload.SourceFile);
101
102 if (null != fileInfo)
103 {
104 payload.FileSize = (int)fileInfo.Length;
105
106 payload.Hash = Common.GetFileHash(fileInfo.FullName);
107
108 // Try to get the certificate if the payload is a signed file and we're not suppressing signature validation.
109 if (payload.EnableSignatureValidation)
110 {
111 X509Certificate2 certificate = null;
112 try
113 {
114 certificate = new X509Certificate2(fileInfo.FullName);
115 }
116 catch (CryptographicException) // we don't care about non-signed files.
117 {
118 }
119
120 // If there is a certificate, remember its hashed public key identifier and thumbprint.
121 if (null != certificate)
122 {
123 byte[] publicKeyIdentifierHash = new byte[128];
124 uint publicKeyIdentifierHashSize = (uint)publicKeyIdentifierHash.Length;
125
126 WixToolset.Core.Native.NativeMethods.HashPublicKeyInfo(certificate.Handle, publicKeyIdentifierHash, ref publicKeyIdentifierHashSize);
127 StringBuilder sb = new StringBuilder(((int)publicKeyIdentifierHashSize + 1) * 2);
128 for (int i = 0; i < publicKeyIdentifierHashSize; ++i)
129 {
130 sb.AppendFormat("{0:X2}", publicKeyIdentifierHash[i]);
131 }
132
133 payload.PublicKey = sb.ToString();
134 payload.Thumbprint = certificate.Thumbprint;
135 }
136 }
137 }
138 }
139
140 private void UpdatePayloadVersionInformation(WixBundlePayloadRow payload)
141 {
142 FileVersionInfo versionInfo = FileVersionInfo.GetVersionInfo(payload.SourceFile);
143
144 if (null != versionInfo)
145 {
146 // Use the fixed version info block for the file since the resource text may not be a dotted quad.
147 Version version = new Version(versionInfo.ProductMajorPart, versionInfo.ProductMinorPart, versionInfo.ProductBuildPart, versionInfo.ProductPrivatePart);
148
149 if (ProcessPayloadsCommand.EmptyVersion != version)
150 {
151 payload.Version = version.ToString();
152 }
153
154 payload.Description = versionInfo.FileDescription;
155 payload.DisplayName = versionInfo.ProductName;
156 }
157 }
158 }
159}
diff --git a/src/WixToolset.Core/Bind/Bundles/VerifyPayloadsWithCatalogCommand.cs b/src/WixToolset.Core/Bind/Bundles/VerifyPayloadsWithCatalogCommand.cs
new file mode 100644
index 00000000..9c614c26
--- /dev/null
+++ b/src/WixToolset.Core/Bind/Bundles/VerifyPayloadsWithCatalogCommand.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 WixToolset.Bind.Bundles
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 using System.Runtime.InteropServices;
10 using System.Text;
11 using WixToolset.Data;
12 using WixToolset.Data.Rows;
13
14 internal class VerifyPayloadsWithCatalogCommand : ICommand
15 {
16 public IEnumerable<WixBundleCatalogRow> Catalogs { private get; set; }
17
18 public IEnumerable<WixBundlePayloadRow> Payloads { private get; set; }
19
20 public void Execute()
21 {
22 List<CatalogIdWithPath> catalogIdsWithPaths = this.Catalogs
23 .Join(this.Payloads,
24 catalog => catalog.Payload,
25 payload => payload.Id,
26 (catalog, payload) => new CatalogIdWithPath() { Id = catalog.Id, FullPath = Path.GetFullPath(payload.SourceFile) })
27 .ToList();
28
29 foreach (WixBundlePayloadRow payloadInfo in this.Payloads)
30 {
31 // Payloads that are not embedded should be verfied.
32 if (String.IsNullOrEmpty(payloadInfo.EmbeddedId))
33 {
34 bool validated = false;
35
36 foreach (CatalogIdWithPath catalog in catalogIdsWithPaths)
37 {
38 if (!validated)
39 {
40 // Get the file hash
41 uint cryptHashSize = 20;
42 byte[] cryptHashBytes = new byte[cryptHashSize];
43 int error;
44 IntPtr fileHandle = IntPtr.Zero;
45 using (FileStream payloadStream = File.OpenRead(payloadInfo.FullFileName))
46 {
47 // Get the file handle
48 fileHandle = payloadStream.SafeFileHandle.DangerousGetHandle();
49
50 // 20 bytes is usually the hash size. Future hashes may be bigger
51 if (!VerifyInterop.CryptCATAdminCalcHashFromFileHandle(fileHandle, ref cryptHashSize, cryptHashBytes, 0))
52 {
53 error = Marshal.GetLastWin32Error();
54
55 if (VerifyInterop.ErrorInsufficientBuffer == error)
56 {
57 error = 0;
58 cryptHashBytes = new byte[cryptHashSize];
59 if (!VerifyInterop.CryptCATAdminCalcHashFromFileHandle(fileHandle, ref cryptHashSize, cryptHashBytes, 0))
60 {
61 error = Marshal.GetLastWin32Error();
62 }
63 }
64
65 if (0 != error)
66 {
67 Messaging.Instance.OnMessage(WixErrors.CatalogFileHashFailed(payloadInfo.FullFileName, error));
68 }
69 }
70 }
71
72 VerifyInterop.WinTrustCatalogInfo catalogData = new VerifyInterop.WinTrustCatalogInfo();
73 VerifyInterop.WinTrustData trustData = new VerifyInterop.WinTrustData();
74 try
75 {
76 // Create WINTRUST_CATALOG_INFO structure
77 catalogData.cbStruct = (uint)Marshal.SizeOf(catalogData);
78 catalogData.cbCalculatedFileHash = cryptHashSize;
79 catalogData.pbCalculatedFileHash = Marshal.AllocCoTaskMem((int)cryptHashSize);
80 Marshal.Copy(cryptHashBytes, 0, catalogData.pbCalculatedFileHash, (int)cryptHashSize);
81
82 StringBuilder hashString = new StringBuilder();
83 foreach (byte hashByte in cryptHashBytes)
84 {
85 hashString.Append(hashByte.ToString("X2"));
86 }
87 catalogData.pcwszMemberTag = hashString.ToString();
88
89 // The file names need to be lower case for older OSes
90 catalogData.pcwszMemberFilePath = payloadInfo.FullFileName.ToLowerInvariant();
91 catalogData.pcwszCatalogFilePath = catalog.FullPath.ToLowerInvariant();
92
93 // Create WINTRUST_DATA structure
94 trustData.cbStruct = (uint)Marshal.SizeOf(trustData);
95 trustData.dwUIChoice = VerifyInterop.WTD_UI_NONE;
96 trustData.fdwRevocationChecks = VerifyInterop.WTD_REVOKE_NONE;
97 trustData.dwUnionChoice = VerifyInterop.WTD_CHOICE_CATALOG;
98 trustData.dwStateAction = VerifyInterop.WTD_STATEACTION_VERIFY;
99 trustData.dwProvFlags = VerifyInterop.WTD_REVOCATION_CHECK_NONE;
100
101 // Create the structure pointers for unmanaged
102 trustData.pCatalog = Marshal.AllocCoTaskMem(Marshal.SizeOf(catalogData));
103 Marshal.StructureToPtr(catalogData, trustData.pCatalog, false);
104
105 // Call WinTrustVerify to validate the file with the catalog
106 IntPtr noWindow = new IntPtr(-1);
107 Guid verifyGuid = new Guid(VerifyInterop.GenericVerify2);
108 long verifyResult = VerifyInterop.WinVerifyTrust(noWindow, ref verifyGuid, ref trustData);
109 if (0 == verifyResult)
110 {
111 payloadInfo.Catalog = catalog.Id;
112 validated = true;
113 break;
114 }
115 }
116 finally
117 {
118 // Free the structure memory
119 if (IntPtr.Zero != trustData.pCatalog)
120 {
121 Marshal.FreeCoTaskMem(trustData.pCatalog);
122 }
123
124 if (IntPtr.Zero != catalogData.pbCalculatedFileHash)
125 {
126 Marshal.FreeCoTaskMem(catalogData.pbCalculatedFileHash);
127 }
128 }
129 }
130 }
131
132 // Error message if the file was not validated by one of the catalogs
133 if (!validated)
134 {
135 Messaging.Instance.OnMessage(WixErrors.CatalogVerificationFailed(payloadInfo.FullFileName));
136 }
137 }
138 }
139 }
140
141 private class CatalogIdWithPath
142 {
143 public string Id { get; set; }
144
145 public string FullPath { get; set; }
146 }
147 }
148}
diff --git a/src/WixToolset.Core/Bind/Databases/AssignMediaCommand.cs b/src/WixToolset.Core/Bind/Databases/AssignMediaCommand.cs
new file mode 100644
index 00000000..5e2650e9
--- /dev/null
+++ b/src/WixToolset.Core/Bind/Databases/AssignMediaCommand.cs
@@ -0,0 +1,314 @@
1// Copyright (c) .NET Foundation and 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.Bind.Databases
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.Rows;
11
12 /// <summary>
13 /// AssignMediaCommand assigns files to cabs based on Media or MediaTemplate rows.
14 /// </summary>
15 public class AssignMediaCommand : ICommand
16 {
17 public AssignMediaCommand()
18 {
19 this.CabinetNameTemplate = "Cab{0}.cab";
20 }
21
22 public Output Output { private get; set; }
23
24 public bool FilesCompressed { private get; set; }
25
26 public string CabinetNameTemplate { private get; set; }
27
28 public IEnumerable<FileFacade> FileFacades { private get; set; }
29
30 public TableDefinitionCollection TableDefinitions { private get; set; }
31
32 /// <summary>
33 /// Gets cabinets with their file rows.
34 /// </summary>
35 public Dictionary<MediaRow, IEnumerable<FileFacade>> FileFacadesByCabinetMedia { get; private set; }
36
37 /// <summary>
38 /// Get media rows.
39 /// </summary>
40 public RowDictionary<MediaRow> MediaRows { get; private set; }
41
42 /// <summary>
43 /// Get uncompressed file rows. This will contain file rows of File elements that are marked with compression=no.
44 /// This contains all the files when Package element is marked with compression=no
45 /// </summary>
46 public IEnumerable<FileFacade> UncompressedFileFacades { get; private set; }
47
48 public void Execute()
49 {
50 Dictionary<MediaRow, List<FileFacade>> filesByCabinetMedia = new Dictionary<MediaRow, List<FileFacade>>();
51
52 RowDictionary<MediaRow> mediaRows = new RowDictionary<MediaRow>();
53
54 List<FileFacade> uncompressedFiles = new List<FileFacade>();
55
56 MediaRow mergeModuleMediaRow = null;
57 Table mediaTable = this.Output.Tables["Media"];
58 Table mediaTemplateTable = this.Output.Tables["WixMediaTemplate"];
59
60 // If both tables are authored, it is an error.
61 if ((mediaTemplateTable != null && mediaTemplateTable.Rows.Count > 0) && (mediaTable != null && mediaTable.Rows.Count > 1))
62 {
63 throw new WixException(WixErrors.MediaTableCollision(null));
64 }
65
66 // When building merge module, all the files go to "#MergeModule.CABinet".
67 if (OutputType.Module == this.Output.Type)
68 {
69 Table mergeModuleMediaTable = new Table(null, this.TableDefinitions["Media"]);
70 mergeModuleMediaRow = (MediaRow)mergeModuleMediaTable.CreateRow(null);
71 mergeModuleMediaRow.Cabinet = "#MergeModule.CABinet";
72
73 filesByCabinetMedia.Add(mergeModuleMediaRow, new List<FileFacade>());
74 }
75
76 if (OutputType.Module == this.Output.Type || null == mediaTemplateTable)
77 {
78 this.ManuallyAssignFiles(mediaTable, mergeModuleMediaRow, this.FileFacades, filesByCabinetMedia, mediaRows, uncompressedFiles);
79 }
80 else
81 {
82 this.AutoAssignFiles(mediaTable, this.FileFacades, filesByCabinetMedia, mediaRows, uncompressedFiles);
83 }
84
85 this.FileFacadesByCabinetMedia = new Dictionary<MediaRow, IEnumerable<FileFacade>>();
86
87 foreach (var mediaRowWithFiles in filesByCabinetMedia)
88 {
89 this.FileFacadesByCabinetMedia.Add(mediaRowWithFiles.Key, mediaRowWithFiles.Value);
90 }
91
92 this.MediaRows = mediaRows;
93
94 this.UncompressedFileFacades = uncompressedFiles;
95 }
96
97 /// <summary>
98 /// Assign files to cabinets based on MediaTemplate authoring.
99 /// </summary>
100 /// <param name="fileFacades">FileRowCollection</param>
101 private void AutoAssignFiles(Table mediaTable, IEnumerable<FileFacade> fileFacades, Dictionary<MediaRow, List<FileFacade>> filesByCabinetMedia, RowDictionary<MediaRow> mediaRows, List<FileFacade> uncompressedFiles)
102 {
103 const int MaxCabIndex = 999;
104
105 ulong currentPreCabSize = 0;
106 ulong maxPreCabSizeInBytes;
107 int maxPreCabSizeInMB = 0;
108 int currentCabIndex = 0;
109
110 MediaRow currentMediaRow = null;
111
112 Table mediaTemplateTable = this.Output.Tables["WixMediaTemplate"];
113
114 // Auto assign files to cabinets based on maximum uncompressed media size
115 mediaTable.Rows.Clear();
116 WixMediaTemplateRow mediaTemplateRow = (WixMediaTemplateRow)mediaTemplateTable.Rows[0];
117
118 if (!String.IsNullOrEmpty(mediaTemplateRow.CabinetTemplate))
119 {
120 this.CabinetNameTemplate = mediaTemplateRow.CabinetTemplate;
121 }
122
123 string mumsString = Environment.GetEnvironmentVariable("WIX_MUMS");
124
125 try
126 {
127 // Override authored mums value if environment variable is authored.
128 if (!String.IsNullOrEmpty(mumsString))
129 {
130 maxPreCabSizeInMB = Int32.Parse(mumsString);
131 }
132 else
133 {
134 maxPreCabSizeInMB = mediaTemplateRow.MaximumUncompressedMediaSize;
135 }
136
137 maxPreCabSizeInBytes = (ulong)maxPreCabSizeInMB * 1024 * 1024;
138 }
139 catch (FormatException)
140 {
141 throw new WixException(WixErrors.IllegalEnvironmentVariable("WIX_MUMS", mumsString));
142 }
143 catch (OverflowException)
144 {
145 throw new WixException(WixErrors.MaximumUncompressedMediaSizeTooLarge(null, maxPreCabSizeInMB));
146 }
147
148 foreach (FileFacade facade in this.FileFacades)
149 {
150 // When building a product, if the current file is not to be compressed or if
151 // the package set not to be compressed, don't cab it.
152 if (OutputType.Product == this.Output.Type &&
153 (YesNoType.No == facade.File.Compressed ||
154 (YesNoType.NotSet == facade.File.Compressed && !this.FilesCompressed)))
155 {
156 uncompressedFiles.Add(facade);
157 continue;
158 }
159
160 if (currentCabIndex == MaxCabIndex)
161 {
162 // Associate current file with last cab (irrespective of the size) and cab index is not incremented anymore.
163 List<FileFacade> cabinetFiles = filesByCabinetMedia[currentMediaRow];
164 facade.WixFile.DiskId = currentCabIndex;
165 cabinetFiles.Add(facade);
166 continue;
167 }
168
169 // Update current cab size.
170 currentPreCabSize += (ulong)facade.File.FileSize;
171
172 if (currentPreCabSize > maxPreCabSizeInBytes)
173 {
174 // Overflow due to current file
175 currentMediaRow = this.AddMediaRow(mediaTemplateRow, mediaTable, ++currentCabIndex);
176 mediaRows.Add(currentMediaRow);
177 filesByCabinetMedia.Add(currentMediaRow, new List<FileFacade>());
178
179 List<FileFacade> cabinetFileRows = filesByCabinetMedia[currentMediaRow];
180 facade.WixFile.DiskId = currentCabIndex;
181 cabinetFileRows.Add(facade);
182 // Now files larger than MaxUncompressedMediaSize will be the only file in its cabinet so as to respect MaxUncompressedMediaSize
183 currentPreCabSize = (ulong)facade.File.FileSize;
184 }
185 else
186 {
187 // File fits in the current cab.
188 if (currentMediaRow == null)
189 {
190 // Create new cab and MediaRow
191 currentMediaRow = this.AddMediaRow(mediaTemplateRow, mediaTable, ++currentCabIndex);
192 mediaRows.Add(currentMediaRow);
193 filesByCabinetMedia.Add(currentMediaRow, new List<FileFacade>());
194 }
195
196 // Associate current file with current cab.
197 List<FileFacade> cabinetFiles = filesByCabinetMedia[currentMediaRow];
198 facade.WixFile.DiskId = currentCabIndex;
199 cabinetFiles.Add(facade);
200 }
201 }
202
203 // If there are uncompressed files and no MediaRow, create a default one.
204 if (uncompressedFiles.Count > 0 && mediaTable.Rows.Count == 0)
205 {
206 MediaRow defaultMediaRow = (MediaRow)mediaTable.CreateRow(null);
207 defaultMediaRow.DiskId = 1;
208 mediaRows.Add(defaultMediaRow);
209 }
210 }
211
212 /// <summary>
213 /// Assign files to cabinets based on Media authoring.
214 /// </summary>
215 /// <param name="mediaTable"></param>
216 /// <param name="mergeModuleMediaRow"></param>
217 /// <param name="fileFacades"></param>
218 private void ManuallyAssignFiles(Table mediaTable, MediaRow mergeModuleMediaRow, IEnumerable<FileFacade> fileFacades, Dictionary<MediaRow, List<FileFacade>> filesByCabinetMedia, RowDictionary<MediaRow> mediaRows, List<FileFacade> uncompressedFiles)
219 {
220 if (OutputType.Module != this.Output.Type)
221 {
222 if (null != mediaTable)
223 {
224 Dictionary<string, MediaRow> cabinetMediaRows = new Dictionary<string, MediaRow>(StringComparer.InvariantCultureIgnoreCase);
225 foreach (MediaRow mediaRow in mediaTable.Rows)
226 {
227 // If the Media row has a cabinet, make sure it is unique across all Media rows.
228 if (!String.IsNullOrEmpty(mediaRow.Cabinet))
229 {
230 MediaRow existingRow;
231 if (cabinetMediaRows.TryGetValue(mediaRow.Cabinet, out existingRow))
232 {
233 Messaging.Instance.OnMessage(WixErrors.DuplicateCabinetName(mediaRow.SourceLineNumbers, mediaRow.Cabinet));
234 Messaging.Instance.OnMessage(WixErrors.DuplicateCabinetName2(existingRow.SourceLineNumbers, existingRow.Cabinet));
235 }
236 else
237 {
238 cabinetMediaRows.Add(mediaRow.Cabinet, mediaRow);
239 }
240 }
241
242 mediaRows.Add(mediaRow);
243 }
244 }
245
246 foreach (MediaRow mediaRow in mediaRows.Values)
247 {
248 if (null != mediaRow.Cabinet)
249 {
250 filesByCabinetMedia.Add(mediaRow, new List<FileFacade>());
251 }
252 }
253 }
254
255 foreach (FileFacade facade in fileFacades)
256 {
257 if (OutputType.Module == this.Output.Type)
258 {
259 filesByCabinetMedia[mergeModuleMediaRow].Add(facade);
260 }
261 else
262 {
263 MediaRow mediaRow;
264 if (!mediaRows.TryGetValue(facade.WixFile.DiskId.ToString(CultureInfo.InvariantCulture), out mediaRow))
265 {
266 Messaging.Instance.OnMessage(WixErrors.MissingMedia(facade.File.SourceLineNumbers, facade.WixFile.DiskId));
267 continue;
268 }
269
270 // When building a product, if the current file is not to be compressed or if
271 // the package set not to be compressed, don't cab it.
272 if (OutputType.Product == this.Output.Type &&
273 (YesNoType.No == facade.File.Compressed ||
274 (YesNoType.NotSet == facade.File.Compressed && !this.FilesCompressed)))
275 {
276 uncompressedFiles.Add(facade);
277 }
278 else // file is marked compressed.
279 {
280 List<FileFacade> cabinetFiles;
281 if (filesByCabinetMedia.TryGetValue(mediaRow, out cabinetFiles))
282 {
283 cabinetFiles.Add(facade);
284 }
285 else
286 {
287 Messaging.Instance.OnMessage(WixErrors.ExpectedMediaCabinet(facade.File.SourceLineNumbers, facade.File.File, facade.WixFile.DiskId));
288 }
289 }
290 }
291 }
292 }
293
294 /// <summary>
295 /// Adds a row to the media table with cab name template filled in.
296 /// </summary>
297 /// <param name="mediaTable"></param>
298 /// <param name="cabIndex"></param>
299 /// <returns></returns>
300 private MediaRow AddMediaRow(WixMediaTemplateRow mediaTemplateRow, Table mediaTable, int cabIndex)
301 {
302 MediaRow currentMediaRow = (MediaRow)mediaTable.CreateRow(mediaTemplateRow.SourceLineNumbers);
303 currentMediaRow.DiskId = cabIndex;
304 currentMediaRow.Cabinet = String.Format(CultureInfo.InvariantCulture, this.CabinetNameTemplate, cabIndex);
305
306 Table wixMediaTable = this.Output.EnsureTable(this.TableDefinitions["WixMedia"]);
307 WixMediaRow row = (WixMediaRow)wixMediaTable.CreateRow(mediaTemplateRow.SourceLineNumbers);
308 row.DiskId = cabIndex;
309 row.CompressionLevel = mediaTemplateRow.CompressionLevel;
310
311 return currentMediaRow;
312 }
313 }
314}
diff --git a/src/WixToolset.Core/Bind/Databases/BindSummaryInfoCommand.cs b/src/WixToolset.Core/Bind/Databases/BindSummaryInfoCommand.cs
new file mode 100644
index 00000000..95bd4cf0
--- /dev/null
+++ b/src/WixToolset.Core/Bind/Databases/BindSummaryInfoCommand.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.Bind.Databases
4{
5 using System;
6 using System.Globalization;
7 using WixToolset.Data;
8
9 /// <summary>
10 /// Binds the summary information table of a database.
11 /// </summary>
12 internal class BindSummaryInfoCommand : ICommand
13 {
14 /// <summary>
15 /// The output to bind.
16 /// </summary>
17 public Output Output { private get; set; }
18
19 /// <summary>
20 /// Returns a flag indicating if files are compressed by default.
21 /// </summary>
22 public bool Compressed { get; private set; }
23
24 /// <summary>
25 /// Returns a flag indicating if uncompressed files use long filenames.
26 /// </summary>
27 public bool LongNames { get; private set; }
28
29 public int InstallerVersion { get; private set; }
30
31 /// <summary>
32 /// Modularization guid, or null if the output is not a module.
33 /// </summary>
34 public string ModularizationGuid { get; private set; }
35
36 public void Execute()
37 {
38 this.Compressed = false;
39 this.LongNames = false;
40 this.InstallerVersion = 0;
41 this.ModularizationGuid = null;
42
43 Table summaryInformationTable = this.Output.Tables["_SummaryInformation"];
44
45 if (null != summaryInformationTable)
46 {
47 bool foundCreateDataTime = false;
48 bool foundLastSaveDataTime = false;
49 bool foundCreatingApplication = false;
50 string now = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss", CultureInfo.InvariantCulture);
51
52 foreach (Row summaryInformationRow in summaryInformationTable.Rows)
53 {
54 switch (summaryInformationRow.FieldAsInteger(0))
55 {
56 case 1: // PID_CODEPAGE
57 // make sure the code page is an int and not a web name or null
58 string codepage = summaryInformationRow.FieldAsString(1);
59
60 if (null == codepage)
61 {
62 codepage = "0";
63 }
64 else
65 {
66 summaryInformationRow[1] = Common.GetValidCodePage(codepage, false, false, summaryInformationRow.SourceLineNumbers).ToString(CultureInfo.InvariantCulture);
67 }
68 break;
69 case 9: // PID_REVNUMBER
70 string packageCode = (string)summaryInformationRow[1];
71
72 if (OutputType.Module == this.Output.Type)
73 {
74 this.ModularizationGuid = packageCode.Substring(1, 36).Replace('-', '_');
75 }
76 else if ("*" == packageCode)
77 {
78 // set the revision number (package/patch code) if it should be automatically generated
79 summaryInformationRow[1] = Common.GenerateGuid();
80 }
81 break;
82 case 12: // PID_CREATE_DTM
83 foundCreateDataTime = true;
84 break;
85 case 13: // PID_LASTSAVE_DTM
86 foundLastSaveDataTime = true;
87 break;
88 case 14:
89 this.InstallerVersion = summaryInformationRow.FieldAsInteger(1);
90 break;
91 case 15: // PID_WORDCOUNT
92 if (OutputType.Patch == this.Output.Type)
93 {
94 this.LongNames = true;
95 this.Compressed = true;
96 }
97 else
98 {
99 this.LongNames = (0 == (summaryInformationRow.FieldAsInteger(1) & 1));
100 this.Compressed = (2 == (summaryInformationRow.FieldAsInteger(1) & 2));
101 }
102 break;
103 case 18: // PID_APPNAME
104 foundCreatingApplication = true;
105 break;
106 }
107 }
108
109 // add a summary information row for the create time/date property if its not already set
110 if (!foundCreateDataTime)
111 {
112 Row createTimeDateRow = summaryInformationTable.CreateRow(null);
113 createTimeDateRow[0] = 12;
114 createTimeDateRow[1] = now;
115 }
116
117 // add a summary information row for the last save time/date property if its not already set
118 if (!foundLastSaveDataTime)
119 {
120 Row lastSaveTimeDateRow = summaryInformationTable.CreateRow(null);
121 lastSaveTimeDateRow[0] = 13;
122 lastSaveTimeDateRow[1] = now;
123 }
124
125 // add a summary information row for the creating application property if its not already set
126 if (!foundCreatingApplication)
127 {
128 Row creatingApplicationRow = summaryInformationTable.CreateRow(null);
129 creatingApplicationRow[0] = 18;
130 creatingApplicationRow[1] = String.Format(CultureInfo.InvariantCulture, AppCommon.GetCreatingApplicationString());
131 }
132 }
133 }
134 }
135}
diff --git a/src/WixToolset.Core/Bind/Databases/CabinetBuilder.cs b/src/WixToolset.Core/Bind/Databases/CabinetBuilder.cs
new file mode 100644
index 00000000..2de6ec25
--- /dev/null
+++ b/src/WixToolset.Core/Bind/Databases/CabinetBuilder.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.Bind.Databases
4{
5 using System;
6 using System.Collections;
7 using System.IO;
8 using System.Linq;
9 using System.Threading;
10 using WixToolset.Cab;
11 using WixToolset.Data;
12 using WixToolset.Data.Rows;
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 Queue cabinetWorkItems;
21 private object lockObject;
22 private int threadCount;
23
24 // Address of Binder's callback function for Cabinet Splitting
25 private IntPtr newCabNamesCallBackAddress;
26
27 public int MaximumCabinetSizeForLargeFileSplitting { get; set; }
28 public int MaximumUncompressedMediaSize { get; set; }
29
30 /// <summary>
31 /// Instantiate a new CabinetBuilder.
32 /// </summary>
33 /// <param name="threadCount">number of threads to use</param>
34 /// <param name="newCabNamesCallBackAddress">Address of Binder's callback function for Cabinet Splitting</param>
35 public CabinetBuilder(int threadCount, IntPtr newCabNamesCallBackAddress)
36 {
37 if (0 >= threadCount)
38 {
39 throw new ArgumentOutOfRangeException("threadCount");
40 }
41
42 this.cabinetWorkItems = new Queue();
43 this.lockObject = new object();
44
45 this.threadCount = threadCount;
46
47 // Set Address of Binder's callback function for Cabinet Splitting
48 this.newCabNamesCallBackAddress = newCabNamesCallBackAddress;
49 }
50
51 /// <summary>
52 /// Enqueues a CabinetWorkItem to the queue.
53 /// </summary>
54 /// <param name="cabinetWorkItem">cabinet work item</param>
55 public void Enqueue(CabinetWorkItem cabinetWorkItem)
56 {
57 this.cabinetWorkItems.Enqueue(cabinetWorkItem);
58 }
59
60 /// <summary>
61 /// Create the queued cabinets.
62 /// </summary>
63 /// <returns>error message number (zero if no error)</returns>
64 public void CreateQueuedCabinets()
65 {
66 // don't create more threads than the number of cabinets to build
67 if (this.cabinetWorkItems.Count < this.threadCount)
68 {
69 this.threadCount = this.cabinetWorkItems.Count;
70 }
71
72 if (0 < this.threadCount)
73 {
74 Thread[] threads = new Thread[this.threadCount];
75
76 for (int i = 0; i < threads.Length; i++)
77 {
78 threads[i] = new Thread(new ThreadStart(this.ProcessWorkItems));
79 threads[i].Start();
80 }
81
82 // wait for all threads to finish
83 foreach (Thread thread in threads)
84 {
85 thread.Join();
86 }
87 }
88 }
89
90 /// <summary>
91 /// This function gets called by multiple threads to do actual work.
92 /// It takes one work item at a time and calls this.CreateCabinet().
93 /// It does not return until cabinetWorkItems queue is empty
94 /// </summary>
95 private void ProcessWorkItems()
96 {
97 try
98 {
99 while (true)
100 {
101 CabinetWorkItem cabinetWorkItem;
102
103 lock (this.cabinetWorkItems)
104 {
105 // check if there are any more cabinets to create
106 if (0 == this.cabinetWorkItems.Count)
107 {
108 break;
109 }
110
111 cabinetWorkItem = (CabinetWorkItem)this.cabinetWorkItems.Dequeue();
112 }
113
114 // create a cabinet
115 this.CreateCabinet(cabinetWorkItem);
116 }
117 }
118 catch (WixException we)
119 {
120 Messaging.Instance.OnMessage(we.Error);
121 }
122 catch (Exception e)
123 {
124 Messaging.Instance.OnMessage(WixErrors.UnexpectedException(e.Message, e.GetType().ToString(), e.StackTrace));
125 }
126 }
127
128 /// <summary>
129 /// Creates a cabinet using the wixcab.dll interop layer.
130 /// </summary>
131 /// <param name="cabinetWorkItem">CabinetWorkItem containing information about the cabinet to create.</param>
132 private void CreateCabinet(CabinetWorkItem cabinetWorkItem)
133 {
134 Messaging.Instance.OnMessage(WixVerboses.CreateCabinet(cabinetWorkItem.CabinetFile));
135
136 int maxCabinetSize = 0; // The value of 0 corresponds to default of 2GB which means no cabinet splitting
137 ulong maxPreCompressedSizeInBytes = 0;
138
139 if (MaximumCabinetSizeForLargeFileSplitting != 0)
140 {
141 // User Specified Max Cab Size for File Splitting, So Check if this cabinet has a single file larger than MaximumUncompressedFileSize
142 // If a file is larger than MaximumUncompressedFileSize, then the cabinet containing it will have only this file
143 if (1 == cabinetWorkItem.FileFacades.Count())
144 {
145 // Cabinet has Single File, Check if this is Large File than needs Splitting into Multiple cabs
146 // Get the Value for Max Uncompressed Media Size
147 maxPreCompressedSizeInBytes = (ulong)MaximumUncompressedMediaSize * 1024 * 1024;
148
149 foreach (FileFacade facade in cabinetWorkItem.FileFacades) // No other easy way than looping to get the only row
150 {
151 if ((ulong)facade.File.FileSize >= maxPreCompressedSizeInBytes)
152 {
153 // If file is larger than MaximumUncompressedFileSize set Maximum Cabinet Size for Cabinet Splitting
154 maxCabinetSize = MaximumCabinetSizeForLargeFileSplitting;
155 }
156 }
157 }
158 }
159
160 // create the cabinet file
161 string cabinetFileName = Path.GetFileName(cabinetWorkItem.CabinetFile);
162 string cabinetDirectory = Path.GetDirectoryName(cabinetWorkItem.CabinetFile);
163
164 using (WixCreateCab cab = new WixCreateCab(cabinetFileName, cabinetDirectory, cabinetWorkItem.FileFacades.Count(), maxCabinetSize, cabinetWorkItem.MaxThreshold, cabinetWorkItem.CompressionLevel))
165 {
166 foreach (FileFacade facade in cabinetWorkItem.FileFacades)
167 {
168 cab.AddFile(facade);
169 }
170
171 cab.Complete(newCabNamesCallBackAddress);
172 }
173 }
174 }
175}
176
diff --git a/src/WixToolset.Core/Bind/Databases/CabinetWorkItem.cs b/src/WixToolset.Core/Bind/Databases/CabinetWorkItem.cs
new file mode 100644
index 00000000..20241bc9
--- /dev/null
+++ b/src/WixToolset.Core/Bind/Databases/CabinetWorkItem.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 WixToolset.Bind.Databases
4{
5 using System.Collections.Generic;
6 using WixToolset.Data;
7 using WixToolset.Data.Rows;
8
9 /// <summary>
10 /// A cabinet builder work item.
11 /// </summary>
12 internal sealed class CabinetWorkItem
13 {
14 private string cabinetFile;
15 private CompressionLevel compressionLevel;
16 //private BinderFileManager binderFileManager;
17 private int maxThreshold;
18
19 /// <summary>
20 /// Instantiate a new CabinetWorkItem.
21 /// </summary>
22 /// <param name="fileFacades">The collection of files in this cabinet.</param>
23 /// <param name="cabinetFile">The cabinet file.</param>
24 /// <param name="maxThreshold">Maximum threshold for each cabinet.</param>
25 /// <param name="compressionLevel">The compression level of the cabinet.</param>
26 /// <param name="binderFileManager">The binder file manager.</param>
27 public CabinetWorkItem(IEnumerable<FileFacade> fileFacades, string cabinetFile, int maxThreshold, CompressionLevel compressionLevel /*, BinderFileManager binderFileManager*/)
28 {
29 this.cabinetFile = cabinetFile;
30 this.compressionLevel = compressionLevel;
31 this.FileFacades = fileFacades;
32 //this.binderFileManager = binderFileManager;
33 this.maxThreshold = maxThreshold;
34 }
35
36 /// <summary>
37 /// Gets the cabinet file.
38 /// </summary>
39 /// <value>The cabinet file.</value>
40 public string CabinetFile
41 {
42 get { return this.cabinetFile; }
43 }
44
45 /// <summary>
46 /// Gets the compression level of the cabinet.
47 /// </summary>
48 /// <value>The compression level of the cabinet.</value>
49 public CompressionLevel CompressionLevel
50 {
51 get { return this.compressionLevel; }
52 }
53
54 /// <summary>
55 /// Gets the collection of files in this cabinet.
56 /// </summary>
57 /// <value>The collection of files in this cabinet.</value>
58 public IEnumerable<FileFacade> FileFacades { get; private set; }
59
60 /// <summary>
61 /// Gets the binder file manager.
62 /// </summary>
63 /// <value>The binder file manager.</value>
64 //public BinderFileManager BinderFileManager
65 //{
66 // get { return this.binderFileManager; }
67 //}
68
69 /// <summary>
70 /// Gets the max threshold.
71 /// </summary>
72 /// <value>The maximum threshold for a folder in a cabinet.</value>
73 public int MaxThreshold
74 {
75 get { return this.maxThreshold; }
76 }
77 }
78}
diff --git a/src/WixToolset.Core/Bind/Databases/ConfigurationCallback.cs b/src/WixToolset.Core/Bind/Databases/ConfigurationCallback.cs
new file mode 100644
index 00000000..7cb18e0f
--- /dev/null
+++ b/src/WixToolset.Core/Bind/Databases/ConfigurationCallback.cs
@@ -0,0 +1,91 @@
1// Copyright (c) .NET Foundation and 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.Bind.Databases
4{
5 using System;
6 using System.Collections;
7 using System.Globalization;
8 using WixToolset.MergeMod;
9
10 /// <summary>
11 /// Callback object for configurable merge modules.
12 /// </summary>
13 internal sealed class ConfigurationCallback : IMsmConfigureModule
14 {
15 private const int SOk = 0x0;
16 private const int SFalse = 0x1;
17 private Hashtable configurationData;
18
19 /// <summary>
20 /// Creates a ConfigurationCallback object.
21 /// </summary>
22 /// <param name="configData">String to break up into name/value pairs.</param>
23 public ConfigurationCallback(string configData)
24 {
25 if (String.IsNullOrEmpty(configData))
26 {
27 throw new ArgumentNullException("configData");
28 }
29
30 string[] pairs = configData.Split(',');
31 this.configurationData = new Hashtable(pairs.Length);
32 for (int i = 0; i < pairs.Length; ++i)
33 {
34 string[] nameVal = pairs[i].Split('=');
35 string name = nameVal[0];
36 string value = nameVal[1];
37
38 name = name.Replace("%2C", ",");
39 name = name.Replace("%3D", "=");
40 name = name.Replace("%25", "%");
41
42 value = value.Replace("%2C", ",");
43 value = value.Replace("%3D", "=");
44 value = value.Replace("%25", "%");
45
46 this.configurationData[name] = value;
47 }
48 }
49
50 /// <summary>
51 /// Returns text data based on name.
52 /// </summary>
53 /// <param name="name">Name of value to return.</param>
54 /// <param name="configData">Out param to put configuration data into.</param>
55 /// <returns>S_OK if value provided, S_FALSE if not.</returns>
56 public int ProvideTextData(string name, out string configData)
57 {
58 if (this.configurationData.Contains(name))
59 {
60 configData = (string)this.configurationData[name];
61 return SOk;
62 }
63 else
64 {
65 configData = null;
66 return SFalse;
67 }
68 }
69
70 /// <summary>
71 /// Returns integer data based on name.
72 /// </summary>
73 /// <param name="name">Name of value to return.</param>
74 /// <param name="configData">Out param to put configuration data into.</param>
75 /// <returns>S_OK if value provided, S_FALSE if not.</returns>
76 public int ProvideIntegerData(string name, out int configData)
77 {
78 if (this.configurationData.Contains(name))
79 {
80 string val = (string)this.configurationData[name];
81 configData = Convert.ToInt32(val, CultureInfo.InvariantCulture);
82 return SOk;
83 }
84 else
85 {
86 configData = 0;
87 return SFalse;
88 }
89 }
90 }
91}
diff --git a/src/WixToolset.Core/Bind/Databases/CopyTransformDataCommand.cs b/src/WixToolset.Core/Bind/Databases/CopyTransformDataCommand.cs
new file mode 100644
index 00000000..af1ab3b0
--- /dev/null
+++ b/src/WixToolset.Core/Bind/Databases/CopyTransformDataCommand.cs
@@ -0,0 +1,606 @@
1// Copyright (c) .NET Foundation and 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.Bind.Databases
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using WixToolset.Data;
9 using WixToolset.Data.Rows;
10 using WixToolset.Extensibility;
11 using WixToolset.Core.Native;
12
13 internal class CopyTransformDataCommand : ICommand
14 {
15 public bool CopyOutFileRows { private get; set; }
16
17 public BinderFileManagerCore FileManagerCore { private get; set; }
18
19 public IEnumerable<IBinderFileManager> FileManagers { private get; set; }
20
21 public Output Output { private get; set; }
22
23 public TableDefinitionCollection TableDefinitions { private get; set; }
24
25 public IEnumerable<FileFacade> FileFacades { get; private set; }
26
27 public void Execute()
28 {
29 Debug.Assert(OutputType.Patch != this.Output.Type);
30
31 List<FileFacade> allFileRows = this.CopyOutFileRows ? new List<FileFacade>() : null;
32
33#if false // TODO: Fix this patching related code to work correctly with FileFacades.
34 bool copyToPatch = (allFileRows != null);
35 bool copyFromPatch = !copyToPatch;
36
37 RowDictionary<MediaRow> patchMediaRows = new RowDictionary<MediaRow>();
38
39 Dictionary<int, RowDictionary<WixFileRow>> patchMediaFileRows = new Dictionary<int, RowDictionary<WixFileRow>>();
40
41 Table patchActualFileTable = this.Output.EnsureTable(this.TableDefinitions["File"]);
42 Table patchFileTable = this.Output.EnsureTable(this.TableDefinitions["WixFile"]);
43
44 if (copyFromPatch)
45 {
46 // index patch files by diskId+fileId
47 foreach (WixFileRow patchFileRow in patchFileTable.Rows)
48 {
49 int diskId = patchFileRow.DiskId;
50 RowDictionary<WixFileRow> mediaFileRows;
51 if (!patchMediaFileRows.TryGetValue(diskId, out mediaFileRows))
52 {
53 mediaFileRows = new RowDictionary<WixFileRow>();
54 patchMediaFileRows.Add(diskId, mediaFileRows);
55 }
56
57 mediaFileRows.Add(patchFileRow);
58 }
59
60 Table patchMediaTable = this.Output.EnsureTable(this.TableDefinitions["Media"]);
61 patchMediaRows = new RowDictionary<MediaRow>(patchMediaTable);
62 }
63
64 // index paired transforms
65 Dictionary<string, Output> pairedTransforms = new Dictionary<string, Output>();
66 foreach (SubStorage substorage in this.Output.SubStorages)
67 {
68 if (substorage.Name.StartsWith("#"))
69 {
70 pairedTransforms.Add(substorage.Name.Substring(1), substorage.Data);
71 }
72 }
73
74 try
75 {
76 // copy File bind data into substorages
77 foreach (SubStorage substorage in this.Output.SubStorages)
78 {
79 if (substorage.Name.StartsWith("#"))
80 {
81 // no changes necessary for paired transforms
82 continue;
83 }
84
85 Output mainTransform = substorage.Data;
86 Table mainWixFileTable = mainTransform.Tables["WixFile"];
87 Table mainMsiFileHashTable = mainTransform.Tables["MsiFileHash"];
88
89 this.FileManagerCore.ActiveSubStorage = substorage;
90
91 RowDictionary<WixFileRow> mainWixFiles = new RowDictionary<WixFileRow>(mainWixFileTable);
92 RowDictionary<Row> mainMsiFileHashIndex = new RowDictionary<Row>();
93
94 Table mainFileTable = mainTransform.Tables["File"];
95 Output pairedTransform = (Output)pairedTransforms[substorage.Name];
96
97 // copy Media.LastSequence and index the MsiFileHash table if it exists.
98 if (copyFromPatch)
99 {
100 Table pairedMediaTable = pairedTransform.Tables["Media"];
101 foreach (MediaRow pairedMediaRow in pairedMediaTable.Rows)
102 {
103 MediaRow patchMediaRow = patchMediaRows.Get(pairedMediaRow.DiskId);
104 pairedMediaRow.Fields[1] = patchMediaRow.Fields[1];
105 }
106
107 if (null != mainMsiFileHashTable)
108 {
109 mainMsiFileHashIndex = new RowDictionary<Row>(mainMsiFileHashTable);
110 }
111
112 // Validate file row changes for keypath-related issues
113 this.ValidateFileRowChanges(mainTransform);
114 }
115
116 // Index File table of pairedTransform
117 Table pairedFileTable = pairedTransform.Tables["File"];
118 RowDictionary<FileRow> pairedFileRows = new RowDictionary<FileRow>(pairedFileTable);
119
120 if (null != mainFileTable)
121 {
122 if (copyFromPatch)
123 {
124 // Remove the MsiFileHash table because it will be updated later with the final file hash for each file
125 mainTransform.Tables.Remove("MsiFileHash");
126 }
127
128 foreach (FileRow mainFileRow in mainFileTable.Rows)
129 {
130 if (RowOperation.Delete == mainFileRow.Operation)
131 {
132 continue;
133 }
134 else if (RowOperation.None == mainFileRow.Operation && !copyToPatch)
135 {
136 continue;
137 }
138
139 WixFileRow mainWixFileRow = mainWixFiles.Get(mainFileRow.File);
140
141 if (copyToPatch) // when copying to the patch, we need compare the underlying files and include all file changes.
142 {
143 ObjectField objectField = (ObjectField)mainWixFileRow.Fields[6];
144 FileRow pairedFileRow = pairedFileRows.Get(mainFileRow.File);
145
146 // If the file is new, we always need to add it to the patch.
147 if (mainFileRow.Operation != RowOperation.Add)
148 {
149 // If PreviousData doesn't exist, target and upgrade layout point to the same location. No need to compare.
150 if (null == objectField.PreviousData)
151 {
152 if (mainFileRow.Operation == RowOperation.None)
153 {
154 continue;
155 }
156 }
157 else
158 {
159 // TODO: should this entire condition be placed in the binder file manager?
160 if ((0 == (PatchAttributeType.Ignore & mainWixFileRow.PatchAttributes)) &&
161 !this.CompareFiles(objectField.PreviousData.ToString(), objectField.Data.ToString()))
162 {
163 // If the file is different, we need to mark the mainFileRow and pairedFileRow as modified.
164 mainFileRow.Operation = RowOperation.Modify;
165 if (null != pairedFileRow)
166 {
167 // Always patch-added, but never non-compressed.
168 pairedFileRow.Attributes |= MsiInterop.MsidbFileAttributesPatchAdded;
169 pairedFileRow.Attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed;
170 pairedFileRow.Fields[6].Modified = true;
171 pairedFileRow.Operation = RowOperation.Modify;
172 }
173 }
174 else
175 {
176 // The File is same. We need mark all the attributes as unchanged.
177 mainFileRow.Operation = RowOperation.None;
178 foreach (Field field in mainFileRow.Fields)
179 {
180 field.Modified = false;
181 }
182
183 if (null != pairedFileRow)
184 {
185 pairedFileRow.Attributes &= ~MsiInterop.MsidbFileAttributesPatchAdded;
186 pairedFileRow.Fields[6].Modified = false;
187 pairedFileRow.Operation = RowOperation.None;
188 }
189 continue;
190 }
191 }
192 }
193 else if (null != pairedFileRow) // RowOperation.Add
194 {
195 // Always patch-added, but never non-compressed.
196 pairedFileRow.Attributes |= MsiInterop.MsidbFileAttributesPatchAdded;
197 pairedFileRow.Attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed;
198 pairedFileRow.Fields[6].Modified = true;
199 pairedFileRow.Operation = RowOperation.Add;
200 }
201 }
202
203 // index patch files by diskId+fileId
204 int diskId = mainWixFileRow.DiskId;
205
206 RowDictionary<WixFileRow> mediaFileRows;
207 if (!patchMediaFileRows.TryGetValue(diskId, out mediaFileRows))
208 {
209 mediaFileRows = new RowDictionary<WixFileRow>();
210 patchMediaFileRows.Add(diskId, mediaFileRows);
211 }
212
213 string fileId = mainFileRow.File;
214 WixFileRow patchFileRow = mediaFileRows.Get(fileId);
215 if (copyToPatch)
216 {
217 if (null == patchFileRow)
218 {
219 FileRow patchActualFileRow = (FileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers);
220 patchActualFileRow.CopyFrom(mainFileRow);
221
222 patchFileRow = (WixFileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers);
223 patchFileRow.CopyFrom(mainWixFileRow);
224
225 mediaFileRows.Add(patchFileRow);
226
227 allFileRows.Add(new FileFacade(patchActualFileRow, patchFileRow, null)); // TODO: should we be passing along delta information? Probably, right?
228 }
229 else
230 {
231 // TODO: confirm the rest of data is identical?
232
233 // make sure Source is same. Otherwise we are silently ignoring a file.
234 if (0 != String.Compare(patchFileRow.Source, mainWixFileRow.Source, StringComparison.OrdinalIgnoreCase))
235 {
236 Messaging.Instance.OnMessage(WixErrors.SameFileIdDifferentSource(mainFileRow.SourceLineNumbers, fileId, patchFileRow.Source, mainWixFileRow.Source));
237 }
238
239 // capture the previous file versions (and associated data) from this targeted instance of the baseline into the current filerow.
240 patchFileRow.AppendPreviousDataFrom(mainWixFileRow);
241 }
242 }
243 else
244 {
245 // copy data from the patch back to the transform
246 if (null != patchFileRow)
247 {
248 FileRow pairedFileRow = (FileRow)pairedFileRows.Get(fileId);
249 for (int i = 0; i < patchFileRow.Fields.Length; i++)
250 {
251 string patchValue = patchFileRow[i] == null ? "" : patchFileRow[i].ToString();
252 string mainValue = mainFileRow[i] == null ? "" : mainFileRow[i].ToString();
253
254 if (1 == i)
255 {
256 // File.Component_ changes should not come from the shared file rows
257 // that contain the file information as each individual transform might
258 // have different changes (or no changes at all).
259 }
260 // File.Attributes should not changed for binary deltas
261 else if (6 == i)
262 {
263 if (null != patchFileRow.Patch)
264 {
265 // File.Attribute should not change for binary deltas
266 pairedFileRow.Attributes = mainFileRow.Attributes;
267 mainFileRow.Fields[i].Modified = false;
268 }
269 }
270 // File.Sequence is updated in pairedTransform, not mainTransform
271 else if (7 == i)
272 {
273 // file sequence is updated in Patch table instead of File table for delta patches
274 if (null != patchFileRow.Patch)
275 {
276 pairedFileRow.Fields[i].Modified = false;
277 }
278 else
279 {
280 pairedFileRow[i] = patchFileRow[i];
281 pairedFileRow.Fields[i].Modified = true;
282 }
283 mainFileRow.Fields[i].Modified = false;
284 }
285 else if (patchValue != mainValue)
286 {
287 mainFileRow[i] = patchFileRow[i];
288 mainFileRow.Fields[i].Modified = true;
289 if (mainFileRow.Operation == RowOperation.None)
290 {
291 mainFileRow.Operation = RowOperation.Modify;
292 }
293 }
294 }
295
296 // copy MsiFileHash row for this File
297 Row patchHashRow;
298 if (!mainMsiFileHashIndex.TryGetValue(patchFileRow.File, out patchHashRow))
299 {
300 patchHashRow = patchFileRow.Hash;
301 }
302
303 if (null != patchHashRow)
304 {
305 Table mainHashTable = mainTransform.EnsureTable(this.TableDefinitions["MsiFileHash"]);
306 Row mainHashRow = mainHashTable.CreateRow(mainFileRow.SourceLineNumbers);
307 for (int i = 0; i < patchHashRow.Fields.Length; i++)
308 {
309 mainHashRow[i] = patchHashRow[i];
310 if (i > 1)
311 {
312 // assume all hash fields have been modified
313 mainHashRow.Fields[i].Modified = true;
314 }
315 }
316
317 // assume the MsiFileHash operation follows the File one
318 mainHashRow.Operation = mainFileRow.Operation;
319 }
320
321 // copy MsiAssemblyName rows for this File
322 List<Row> patchAssemblyNameRows = patchFileRow.AssemblyNames;
323 if (null != patchAssemblyNameRows)
324 {
325 Table mainAssemblyNameTable = mainTransform.EnsureTable(this.TableDefinitions["MsiAssemblyName"]);
326 foreach (Row patchAssemblyNameRow in patchAssemblyNameRows)
327 {
328 // Copy if there isn't an identical modified/added row already in the transform.
329 bool foundMatchingModifiedRow = false;
330 foreach (Row mainAssemblyNameRow in mainAssemblyNameTable.Rows)
331 {
332 if (RowOperation.None != mainAssemblyNameRow.Operation && mainAssemblyNameRow.GetPrimaryKey('/').Equals(patchAssemblyNameRow.GetPrimaryKey('/')))
333 {
334 foundMatchingModifiedRow = true;
335 break;
336 }
337 }
338
339 if (!foundMatchingModifiedRow)
340 {
341 Row mainAssemblyNameRow = mainAssemblyNameTable.CreateRow(mainFileRow.SourceLineNumbers);
342 for (int i = 0; i < patchAssemblyNameRow.Fields.Length; i++)
343 {
344 mainAssemblyNameRow[i] = patchAssemblyNameRow[i];
345 }
346
347 // assume value field has been modified
348 mainAssemblyNameRow.Fields[2].Modified = true;
349 mainAssemblyNameRow.Operation = mainFileRow.Operation;
350 }
351 }
352 }
353
354 // Add patch header for this file
355 if (null != patchFileRow.Patch)
356 {
357 // Add the PatchFiles action automatically to the AdminExecuteSequence and InstallExecuteSequence tables.
358 AddPatchFilesActionToSequenceTable(SequenceTable.AdminExecuteSequence, mainTransform, pairedTransform, mainFileRow);
359 AddPatchFilesActionToSequenceTable(SequenceTable.InstallExecuteSequence, mainTransform, pairedTransform, mainFileRow);
360
361 // Add to Patch table
362 Table patchTable = pairedTransform.EnsureTable(this.TableDefinitions["Patch"]);
363 if (0 == patchTable.Rows.Count)
364 {
365 patchTable.Operation = TableOperation.Add;
366 }
367
368 Row patchRow = patchTable.CreateRow(mainFileRow.SourceLineNumbers);
369 patchRow[0] = patchFileRow.File;
370 patchRow[1] = patchFileRow.Sequence;
371
372 FileInfo patchFile = new FileInfo(patchFileRow.Source);
373 patchRow[2] = (int)patchFile.Length;
374 patchRow[3] = 0 == (PatchAttributeType.AllowIgnoreOnError & patchFileRow.PatchAttributes) ? 0 : 1;
375
376 string streamName = patchTable.Name + "." + patchRow[0] + "." + patchRow[1];
377 if (MsiInterop.MsiMaxStreamNameLength < streamName.Length)
378 {
379 streamName = "_" + Guid.NewGuid().ToString("D").ToUpperInvariant().Replace('-', '_');
380 Table patchHeadersTable = pairedTransform.EnsureTable(this.TableDefinitions["MsiPatchHeaders"]);
381 if (0 == patchHeadersTable.Rows.Count)
382 {
383 patchHeadersTable.Operation = TableOperation.Add;
384 }
385 Row patchHeadersRow = patchHeadersTable.CreateRow(mainFileRow.SourceLineNumbers);
386 patchHeadersRow[0] = streamName;
387 patchHeadersRow[1] = patchFileRow.Patch;
388 patchRow[5] = streamName;
389 patchHeadersRow.Operation = RowOperation.Add;
390 }
391 else
392 {
393 patchRow[4] = patchFileRow.Patch;
394 }
395 patchRow.Operation = RowOperation.Add;
396 }
397 }
398 else
399 {
400 // TODO: throw because all transform rows should have made it into the patch
401 }
402 }
403 }
404 }
405
406 if (copyFromPatch)
407 {
408 this.Output.Tables.Remove("Media");
409 this.Output.Tables.Remove("File");
410 this.Output.Tables.Remove("MsiFileHash");
411 this.Output.Tables.Remove("MsiAssemblyName");
412 }
413 }
414 }
415 finally
416 {
417 this.FileManagerCore.ActiveSubStorage = null;
418 }
419#endif
420 this.FileFacades = allFileRows;
421 }
422
423 /// <summary>
424 /// Adds the PatchFiles action to the sequence table if it does not already exist.
425 /// </summary>
426 /// <param name="table">The sequence table to check or modify.</param>
427 /// <param name="mainTransform">The primary authoring transform.</param>
428 /// <param name="pairedTransform">The secondary patch transform.</param>
429 /// <param name="mainFileRow">The file row that contains information about the patched file.</param>
430 private void AddPatchFilesActionToSequenceTable(SequenceTable table, Output mainTransform, Output pairedTransform, Row mainFileRow)
431 {
432 // Find/add PatchFiles action (also determine sequence for it).
433 // Search mainTransform first, then pairedTransform (pairedTransform overrides).
434 bool hasPatchFilesAction = false;
435 int seqInstallFiles = 0;
436 int seqDuplicateFiles = 0;
437 string tableName = table.ToString();
438
439 TestSequenceTableForPatchFilesAction(
440 mainTransform.Tables[tableName],
441 ref hasPatchFilesAction,
442 ref seqInstallFiles,
443 ref seqDuplicateFiles);
444 TestSequenceTableForPatchFilesAction(
445 pairedTransform.Tables[tableName],
446 ref hasPatchFilesAction,
447 ref seqInstallFiles,
448 ref seqDuplicateFiles);
449 if (!hasPatchFilesAction)
450 {
451 Table iesTable = pairedTransform.EnsureTable(this.TableDefinitions[tableName]);
452 if (0 == iesTable.Rows.Count)
453 {
454 iesTable.Operation = TableOperation.Add;
455 }
456
457 Row patchAction = iesTable.CreateRow(null);
458 WixActionRow wixPatchAction = WindowsInstallerStandard.GetStandardActions()[table, "PatchFiles"];
459 int sequence = wixPatchAction.Sequence;
460 // Test for default sequence value's appropriateness
461 if (seqInstallFiles >= sequence || (0 != seqDuplicateFiles && seqDuplicateFiles <= sequence))
462 {
463 if (0 != seqDuplicateFiles)
464 {
465 if (seqDuplicateFiles < seqInstallFiles)
466 {
467 throw new WixException(WixErrors.InsertInvalidSequenceActionOrder(mainFileRow.SourceLineNumbers, iesTable.Name, "InstallFiles", "DuplicateFiles", wixPatchAction.Action));
468 }
469 else
470 {
471 sequence = (seqDuplicateFiles + seqInstallFiles) / 2;
472 if (seqInstallFiles == sequence || seqDuplicateFiles == sequence)
473 {
474 throw new WixException(WixErrors.InsertSequenceNoSpace(mainFileRow.SourceLineNumbers, iesTable.Name, "InstallFiles", "DuplicateFiles", wixPatchAction.Action));
475 }
476 }
477 }
478 else
479 {
480 sequence = seqInstallFiles + 1;
481 }
482 }
483 patchAction[0] = wixPatchAction.Action;
484 patchAction[1] = wixPatchAction.Condition;
485 patchAction[2] = sequence;
486 patchAction.Operation = RowOperation.Add;
487 }
488 }
489
490 /// <summary>
491 /// Tests sequence table for PatchFiles and associated actions
492 /// </summary>
493 /// <param name="iesTable">The table to test.</param>
494 /// <param name="hasPatchFilesAction">Set to true if PatchFiles action is found. Left unchanged otherwise.</param>
495 /// <param name="seqInstallFiles">Set to sequence value of InstallFiles action if found. Left unchanged otherwise.</param>
496 /// <param name="seqDuplicateFiles">Set to sequence value of DuplicateFiles action if found. Left unchanged otherwise.</param>
497 private static void TestSequenceTableForPatchFilesAction(Table iesTable, ref bool hasPatchFilesAction, ref int seqInstallFiles, ref int seqDuplicateFiles)
498 {
499 if (null != iesTable)
500 {
501 foreach (Row iesRow in iesTable.Rows)
502 {
503 if (String.Equals("PatchFiles", (string)iesRow[0], StringComparison.Ordinal))
504 {
505 hasPatchFilesAction = true;
506 }
507 if (String.Equals("InstallFiles", (string)iesRow[0], StringComparison.Ordinal))
508 {
509 seqInstallFiles = (int)iesRow.Fields[2].Data;
510 }
511 if (String.Equals("DuplicateFiles", (string)iesRow[0], StringComparison.Ordinal))
512 {
513 seqDuplicateFiles = (int)iesRow.Fields[2].Data;
514 }
515 }
516 }
517 }
518
519 /// <summary>
520 /// Signal a warning if a non-keypath file was changed in a patch without also changing the keypath file of the component.
521 /// </summary>
522 /// <param name="output">The output to validate.</param>
523 private void ValidateFileRowChanges(Output transform)
524 {
525 Table componentTable = transform.Tables["Component"];
526 Table fileTable = transform.Tables["File"];
527
528 // There's no sense validating keypaths if the transform has no component or file table
529 if (componentTable == null || fileTable == null)
530 {
531 return;
532 }
533
534 Dictionary<string, string> componentKeyPath = new Dictionary<string, string>(componentTable.Rows.Count);
535
536 // Index the Component table for non-directory & non-registry key paths.
537 foreach (Row row in componentTable.Rows)
538 {
539 if (null != row.Fields[5].Data &&
540 0 != ((int)row.Fields[3].Data & MsiInterop.MsidbComponentAttributesRegistryKeyPath))
541 {
542 componentKeyPath.Add(row.Fields[0].Data.ToString(), row.Fields[5].Data.ToString());
543 }
544 }
545
546 Dictionary<string, string> componentWithChangedKeyPath = new Dictionary<string, string>();
547 Dictionary<string, string> componentWithNonKeyPathChanged = new Dictionary<string, string>();
548 // Verify changes in the file table, now that file diffing has occurred
549 foreach (FileRow row in fileTable.Rows)
550 {
551 string fileId = row.Fields[0].Data.ToString();
552 string componentId = row.Fields[1].Data.ToString();
553
554 if (RowOperation.Modify != row.Operation)
555 {
556 continue;
557 }
558
559 // If this file is the keypath of a component
560 if (componentKeyPath.ContainsValue(fileId))
561 {
562 if (!componentWithChangedKeyPath.ContainsKey(componentId))
563 {
564 componentWithChangedKeyPath.Add(componentId, fileId);
565 }
566 }
567 else
568 {
569 if (!componentWithNonKeyPathChanged.ContainsKey(componentId))
570 {
571 componentWithNonKeyPathChanged.Add(componentId, fileId);
572 }
573 }
574 }
575
576 foreach (KeyValuePair<string, string> componentFile in componentWithNonKeyPathChanged)
577 {
578 // Make sure all changes to non keypath files also had a change in the keypath.
579 if (!componentWithChangedKeyPath.ContainsKey(componentFile.Key) && componentKeyPath.ContainsKey(componentFile.Key))
580 {
581 Messaging.Instance.OnMessage(WixWarnings.UpdateOfNonKeyPathFile((string)componentFile.Value, (string)componentFile.Key, (string)componentKeyPath[componentFile.Key]));
582 }
583 }
584 }
585
586 private bool CompareFiles(string targetFile, string updatedFile)
587 {
588 bool? compared = null;
589 foreach (IBinderFileManager fileManager in this.FileManagers)
590 {
591 compared = fileManager.CompareFiles(targetFile, updatedFile);
592 if (compared.HasValue)
593 {
594 break;
595 }
596 }
597
598 if (!compared.HasValue)
599 {
600 throw new InvalidOperationException(); // TODO: something needs to be said here that none of the binder file managers returned a result.
601 }
602
603 return compared.Value;
604 }
605 }
606}
diff --git a/src/WixToolset.Core/Bind/Databases/CreateCabinetsCommand.cs b/src/WixToolset.Core/Bind/Databases/CreateCabinetsCommand.cs
new file mode 100644
index 00000000..35c8abb4
--- /dev/null
+++ b/src/WixToolset.Core/Bind/Databases/CreateCabinetsCommand.cs
@@ -0,0 +1,489 @@
1// Copyright (c) .NET Foundation and 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.Bind.Databases
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.Threading;
12 using WixToolset.Data;
13 using WixToolset.Data.Rows;
14 using WixToolset.Extensibility;
15
16 /// <summary>
17 /// Creates cabinet files.
18 /// </summary>
19 internal class CreateCabinetsCommand : ICommand
20 {
21 private List<FileTransfer> fileTransfers;
22
23 private FileSplitCabNamesCallback newCabNamesCallBack;
24
25 private Dictionary<string, string> lastCabinetAddedToMediaTable; // Key is First Cabinet Name, Value is Last Cabinet Added in the Split Sequence
26
27 public CreateCabinetsCommand()
28 {
29 this.fileTransfers = new List<FileTransfer>();
30
31 this.newCabNamesCallBack = NewCabNamesCallBack;
32 }
33
34 /// <summary>
35 /// Sets the number of threads to use for cabinet creation.
36 /// </summary>
37 public int CabbingThreadCount { private get; set; }
38
39 public string TempFilesLocation { private get; set; }
40
41 /// <summary>
42 /// Sets the default compression level to use for cabinets
43 /// that don't have their compression level explicitly set.
44 /// </summary>
45 public CompressionLevel DefaultCompressionLevel { private get; set; }
46
47 public Output Output { private get; set; }
48
49 public IEnumerable<IBinderFileManager> FileManagers { private get; set; }
50
51 public string LayoutDirectory { private get; set; }
52
53 public bool Compressed { private get; set; }
54
55 public Dictionary<MediaRow, IEnumerable<FileFacade>> FileRowsByCabinet { private get; set; }
56
57 public Func<MediaRow, string, string, string> ResolveMedia { private get; set; }
58
59 public TableDefinitionCollection TableDefinitions { private get; set; }
60
61 public Table WixMediaTable { private get; set; }
62
63 public IEnumerable<FileTransfer> FileTransfers { get { return this.fileTransfers; } }
64
65 /// <param name="output">Output to generate image for.</param>
66 /// <param name="fileTransfers">Array of files to be transfered.</param>
67 /// <param name="layoutDirectory">The directory in which the image should be layed out.</param>
68 /// <param name="compressed">Flag if source image should be compressed.</param>
69 /// <returns>The uncompressed file rows.</returns>
70 public void Execute()
71 {
72 RowDictionary<WixMediaRow> wixMediaRows = new RowDictionary<WixMediaRow>(this.WixMediaTable);
73
74 this.lastCabinetAddedToMediaTable = new Dictionary<string, string>();
75
76 this.SetCabbingThreadCount();
77
78 // Send Binder object to Facilitate NewCabNamesCallBack Callback
79 CabinetBuilder cabinetBuilder = new CabinetBuilder(this.CabbingThreadCount, Marshal.GetFunctionPointerForDelegate(this.newCabNamesCallBack));
80
81 // Supply Compile MediaTemplate Attributes to Cabinet Builder
82 int MaximumCabinetSizeForLargeFileSplitting;
83 int MaximumUncompressedMediaSize;
84 this.GetMediaTemplateAttributes(out MaximumCabinetSizeForLargeFileSplitting, out MaximumUncompressedMediaSize);
85 cabinetBuilder.MaximumCabinetSizeForLargeFileSplitting = MaximumCabinetSizeForLargeFileSplitting;
86 cabinetBuilder.MaximumUncompressedMediaSize = MaximumUncompressedMediaSize;
87
88 foreach (var entry in this.FileRowsByCabinet)
89 {
90 MediaRow mediaRow = entry.Key;
91 IEnumerable<FileFacade> files = entry.Value;
92 CompressionLevel compressionLevel = this.DefaultCompressionLevel;
93
94 WixMediaRow wixMediaRow = null;
95 string mediaLayoutFolder = null;
96
97 if (wixMediaRows.TryGetValue(mediaRow.GetKey(), out wixMediaRow))
98 {
99 mediaLayoutFolder = wixMediaRow.Layout;
100
101 if (wixMediaRow.CompressionLevel.HasValue)
102 {
103 compressionLevel = wixMediaRow.CompressionLevel.Value;
104 }
105 }
106
107 string cabinetDir = this.ResolveMedia(mediaRow, mediaLayoutFolder, this.LayoutDirectory);
108
109 CabinetWorkItem cabinetWorkItem = this.CreateCabinetWorkItem(this.Output, cabinetDir, mediaRow, compressionLevel, files, this.fileTransfers);
110 if (null != cabinetWorkItem)
111 {
112 cabinetBuilder.Enqueue(cabinetWorkItem);
113 }
114 }
115
116 // stop processing if an error previously occurred
117 if (Messaging.Instance.EncounteredError)
118 {
119 return;
120 }
121
122 // create queued cabinets with multiple threads
123 cabinetBuilder.CreateQueuedCabinets();
124 if (Messaging.Instance.EncounteredError)
125 {
126 return;
127 }
128 }
129
130 /// <summary>
131 /// Sets the thead count to the number of processors if the current thread count is set to 0.
132 /// </summary>
133 /// <remarks>The thread count value must be greater than 0 otherwise and exception will be thrown.</remarks>
134 private void SetCabbingThreadCount()
135 {
136 // default the number of cabbing threads to the number of processors if it wasn't specified
137 if (0 == this.CabbingThreadCount)
138 {
139 string numberOfProcessors = System.Environment.GetEnvironmentVariable("NUMBER_OF_PROCESSORS");
140
141 try
142 {
143 if (null != numberOfProcessors)
144 {
145 this.CabbingThreadCount = Convert.ToInt32(numberOfProcessors, CultureInfo.InvariantCulture.NumberFormat);
146
147 if (0 >= this.CabbingThreadCount)
148 {
149 throw new WixException(WixErrors.IllegalEnvironmentVariable("NUMBER_OF_PROCESSORS", numberOfProcessors));
150 }
151 }
152 else // default to 1 if the environment variable is not set
153 {
154 this.CabbingThreadCount = 1;
155 }
156
157 Messaging.Instance.OnMessage(WixVerboses.SetCabbingThreadCount(this.CabbingThreadCount.ToString()));
158 }
159 catch (ArgumentException)
160 {
161 throw new WixException(WixErrors.IllegalEnvironmentVariable("NUMBER_OF_PROCESSORS", numberOfProcessors));
162 }
163 catch (FormatException)
164 {
165 throw new WixException(WixErrors.IllegalEnvironmentVariable("NUMBER_OF_PROCESSORS", numberOfProcessors));
166 }
167 }
168 }
169
170
171 /// <summary>
172 /// Creates a work item to create a cabinet.
173 /// </summary>
174 /// <param name="output">Output for the current database.</param>
175 /// <param name="cabinetDir">Directory to create cabinet in.</param>
176 /// <param name="mediaRow">MediaRow containing information about the cabinet.</param>
177 /// <param name="fileFacades">Collection of files in this cabinet.</param>
178 /// <param name="fileTransfers">Array of files to be transfered.</param>
179 /// <returns>created CabinetWorkItem object</returns>
180 private CabinetWorkItem CreateCabinetWorkItem(Output output, string cabinetDir, MediaRow mediaRow, CompressionLevel compressionLevel, IEnumerable<FileFacade> fileFacades, List<FileTransfer> fileTransfers)
181 {
182 CabinetWorkItem cabinetWorkItem = null;
183 string tempCabinetFileX = Path.Combine(this.TempFilesLocation, mediaRow.Cabinet);
184
185 // check for an empty cabinet
186 if (!fileFacades.Any())
187 {
188 string cabinetName = mediaRow.Cabinet;
189
190 // remove the leading '#' from the embedded cabinet name to make the warning easier to understand
191 if (cabinetName.StartsWith("#", StringComparison.Ordinal))
192 {
193 cabinetName = cabinetName.Substring(1);
194 }
195
196 // If building a patch, remind them to run -p for torch.
197 if (OutputType.Patch == output.Type)
198 {
199 Messaging.Instance.OnMessage(WixWarnings.EmptyCabinet(mediaRow.SourceLineNumbers, cabinetName, true));
200 }
201 else
202 {
203 Messaging.Instance.OnMessage(WixWarnings.EmptyCabinet(mediaRow.SourceLineNumbers, cabinetName));
204 }
205 }
206
207 ResolvedCabinet resolvedCabinet = this.ResolveCabinet(tempCabinetFileX, fileFacades);
208
209 // create a cabinet work item if it's not being skipped
210 if (CabinetBuildOption.BuildAndCopy == resolvedCabinet.BuildOption || CabinetBuildOption.BuildAndMove == resolvedCabinet.BuildOption)
211 {
212 int maxThreshold = 0; // default to the threshold for best smartcabbing (makes smallest cabinet).
213
214 cabinetWorkItem = new CabinetWorkItem(fileFacades, resolvedCabinet.Path, maxThreshold, compressionLevel/*, this.FileManager*/);
215 }
216 else // reuse the cabinet from the cabinet cache.
217 {
218 Messaging.Instance.OnMessage(WixVerboses.ReusingCabCache(mediaRow.SourceLineNumbers, mediaRow.Cabinet, resolvedCabinet.Path));
219
220 try
221 {
222 // Ensure the cached cabinet timestamp is current to prevent perpetual incremental builds. The
223 // problematic scenario goes like this. Imagine two cabinets in the cache. Update a file that
224 // goes into one of the cabinets. One cabinet will get rebuilt, the other will be copied from
225 // the cache. Now the file (an input) has a newer timestamp than the reused cabient (an output)
226 // causing the project to look like it perpetually needs a rebuild until all of the reused
227 // cabinets get newer timestamps.
228 File.SetLastWriteTime(resolvedCabinet.Path, DateTime.Now);
229 }
230 catch (Exception e)
231 {
232 Messaging.Instance.OnMessage(WixWarnings.CannotUpdateCabCache(mediaRow.SourceLineNumbers, resolvedCabinet.Path, e.Message));
233 }
234 }
235
236 if (mediaRow.Cabinet.StartsWith("#", StringComparison.Ordinal))
237 {
238 Table streamsTable = output.EnsureTable(this.TableDefinitions["_Streams"]);
239
240 Row streamRow = streamsTable.CreateRow(mediaRow.SourceLineNumbers);
241 streamRow[0] = mediaRow.Cabinet.Substring(1);
242 streamRow[1] = resolvedCabinet.Path;
243 }
244 else
245 {
246 string destinationPath = Path.Combine(cabinetDir, mediaRow.Cabinet);
247 FileTransfer transfer;
248 if (FileTransfer.TryCreate(resolvedCabinet.Path, destinationPath, CabinetBuildOption.BuildAndMove == resolvedCabinet.BuildOption, "Cabinet", mediaRow.SourceLineNumbers, out transfer))
249 {
250 transfer.Built = true;
251 fileTransfers.Add(transfer);
252 }
253 }
254
255 return cabinetWorkItem;
256 }
257
258 private ResolvedCabinet ResolveCabinet(string cabinetPath, IEnumerable<FileFacade> fileFacades)
259 {
260 ResolvedCabinet resolved = null;
261
262 List<BindFileWithPath> filesWithPath = fileFacades.Select(f => new BindFileWithPath() { Id = f.File.File, Path = f.WixFile.Source }).ToList();
263
264 foreach (IBinderFileManager fileManager in this.FileManagers)
265 {
266 resolved = fileManager.ResolveCabinet(cabinetPath, filesWithPath);
267 if (null != resolved)
268 {
269 break;
270 }
271 }
272
273 return resolved;
274 }
275
276 /// <summary>
277 /// Delegate for Cabinet Split Callback
278 /// </summary>
279 [UnmanagedFunctionPointer(CallingConvention.StdCall)]
280 internal delegate void FileSplitCabNamesCallback([MarshalAs(UnmanagedType.LPWStr)]string firstCabName, [MarshalAs(UnmanagedType.LPWStr)]string newCabName, [MarshalAs(UnmanagedType.LPWStr)]string fileToken);
281
282 /// <summary>
283 /// Call back to Add File Transfer for new Cab and add new Cab to Media table
284 /// This callback can come from Multiple Cabinet Builder Threads and so should be thread safe
285 /// This callback will not be called in case there is no File splitting. i.e. MaximumCabinetSizeForLargeFileSplitting was not authored
286 /// </summary>
287 /// <param name="firstCabName">The name of splitting cabinet without extention e.g. "cab1".</param>
288 /// <param name="newCabName">The name of the new cabinet that would be formed by splitting e.g. "cab1b.cab"</param>
289 /// <param name="fileToken">The file token of the first file present in the splitting cabinet</param>
290 internal void NewCabNamesCallBack([MarshalAs(UnmanagedType.LPWStr)]string firstCabName, [MarshalAs(UnmanagedType.LPWStr)]string newCabName, [MarshalAs(UnmanagedType.LPWStr)]string fileToken)
291 {
292 // Locking Mutex here as this callback can come from Multiple Cabinet Builder Threads
293 Mutex mutex = new Mutex(false, "WixCabinetSplitBinderCallback");
294 try
295 {
296 if (!mutex.WaitOne(0, false)) // Check if you can get the lock
297 {
298 // Cound not get the Lock
299 Messaging.Instance.OnMessage(WixVerboses.CabinetsSplitInParallel());
300 mutex.WaitOne(); // Wait on other thread
301 }
302
303 string firstCabinetName = firstCabName + ".cab";
304 string newCabinetName = newCabName;
305 bool transferAdded = false; // Used for Error Handling
306
307 // Create File Transfer for new Cabinet using transfer of Base Cabinet
308 foreach (FileTransfer transfer in this.FileTransfers)
309 {
310 if (firstCabinetName.Equals(Path.GetFileName(transfer.Source), StringComparison.InvariantCultureIgnoreCase))
311 {
312 string newCabSourcePath = Path.Combine(Path.GetDirectoryName(transfer.Source), newCabinetName);
313 string newCabTargetPath = Path.Combine(Path.GetDirectoryName(transfer.Destination), newCabinetName);
314
315 FileTransfer newTransfer;
316 if (FileTransfer.TryCreate(newCabSourcePath, newCabTargetPath, transfer.Move, "Cabinet", transfer.SourceLineNumbers, out newTransfer))
317 {
318 newTransfer.Built = true;
319 this.fileTransfers.Add(newTransfer);
320 transferAdded = true;
321 break;
322 }
323 }
324 }
325
326 // Check if File Transfer was added
327 if (!transferAdded)
328 {
329 throw new WixException(WixErrors.SplitCabinetCopyRegistrationFailed(newCabinetName, firstCabinetName));
330 }
331
332 // Add the new Cabinets to media table using LastSequence of Base Cabinet
333 Table mediaTable = this.Output.Tables["Media"];
334 Table wixFileTable = this.Output.Tables["WixFile"];
335 int diskIDForLastSplitCabAdded = 0; // The DiskID value for the first cab in this cabinet split chain
336 int lastSequenceForLastSplitCabAdded = 0; // The LastSequence value for the first cab in this cabinet split chain
337 bool lastSplitCabinetFound = false; // Used for Error Handling
338
339 string lastCabinetOfThisSequence = String.Empty;
340 // Get the Value of Last Cabinet Added in this split Sequence from Dictionary
341 if (!this.lastCabinetAddedToMediaTable.TryGetValue(firstCabinetName, out lastCabinetOfThisSequence))
342 {
343 // If there is no value for this sequence, then use first Cabinet is the last one of this split sequence
344 lastCabinetOfThisSequence = firstCabinetName;
345 }
346
347 foreach (MediaRow mediaRow in mediaTable.Rows)
348 {
349 // Get details for the Last Cabinet Added in this Split Sequence
350 if ((lastSequenceForLastSplitCabAdded == 0) && lastCabinetOfThisSequence.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase))
351 {
352 lastSequenceForLastSplitCabAdded = mediaRow.LastSequence;
353 diskIDForLastSplitCabAdded = mediaRow.DiskId;
354 lastSplitCabinetFound = true;
355 }
356
357 // Check for Name Collision for the new Cabinet added
358 if (newCabinetName.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase))
359 {
360 // Name Collision of generated Split Cabinet Name and user Specified Cab name for current row
361 throw new WixException(WixErrors.SplitCabinetNameCollision(newCabinetName, firstCabinetName));
362 }
363 }
364
365 // Check if the last Split Cabinet was found in the Media Table
366 if (!lastSplitCabinetFound)
367 {
368 throw new WixException(WixErrors.SplitCabinetInsertionFailed(newCabinetName, firstCabinetName, lastCabinetOfThisSequence));
369 }
370
371 // The new Row has to be inserted just after the last cab in this cabinet split chain according to DiskID Sort
372 // This is because the FDI Extract requires DiskID of Split Cabinets to be continuous. It Fails otherwise with
373 // Error 2350 (FDI Server Error) as next DiskID did not have the right split cabinet during extraction
374 MediaRow newMediaRow = (MediaRow)mediaTable.CreateRow(null);
375 newMediaRow.Cabinet = newCabinetName;
376 newMediaRow.DiskId = diskIDForLastSplitCabAdded + 1; // When Sorted with DiskID, this new Cabinet Row is an Insertion
377 newMediaRow.LastSequence = lastSequenceForLastSplitCabAdded;
378
379 // Now increment the DiskID for all rows that come after the newly inserted row to Ensure that DiskId is unique
380 foreach (MediaRow mediaRow in mediaTable.Rows)
381 {
382 // Check if this row comes after inserted row and it is not the new cabinet inserted row
383 if (mediaRow.DiskId >= newMediaRow.DiskId && !newCabinetName.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase))
384 {
385 mediaRow.DiskId++; // Increment DiskID
386 }
387 }
388
389 // Now Increment DiskID for All files Rows so that they refer to the right Media Row
390 foreach (WixFileRow wixFileRow in wixFileTable.Rows)
391 {
392 // Check if this row comes after inserted row and if this row is not the file that has to go into the current cabinet
393 // This check will work as we have only one large file in every splitting cabinet
394 // If we want to support splitting cabinet with more large files we need to update this code
395 if (wixFileRow.DiskId >= newMediaRow.DiskId && !wixFileRow.File.Equals(fileToken, StringComparison.InvariantCultureIgnoreCase))
396 {
397 wixFileRow.DiskId++; // Increment DiskID
398 }
399 }
400
401 // Update the Last Cabinet Added in the Split Sequence in Dictionary for future callback
402 this.lastCabinetAddedToMediaTable[firstCabinetName] = newCabinetName;
403
404 mediaTable.ValidateRows(); // Valdiates DiskDIs, throws Exception as Wix Error if validation fails
405 }
406 finally
407 {
408 // Releasing the Mutex here
409 mutex.ReleaseMutex();
410 }
411 }
412
413
414 /// <summary>
415 /// Gets Compiler Values of MediaTemplate Attributes governing Maximum Cabinet Size after applying Environment Variable Overrides
416 /// </summary>
417 /// <param name="output">Output to generate image for.</param>
418 /// <param name="fileRows">The indexed file rows.</param>
419 private void GetMediaTemplateAttributes(out int maxCabSizeForLargeFileSplitting, out int maxUncompressedMediaSize)
420 {
421 // Get Environment Variable Overrides for MediaTemplate Attributes governing Maximum Cabinet Size
422 string mcslfsString = Environment.GetEnvironmentVariable("WIX_MCSLFS");
423 string mumsString = Environment.GetEnvironmentVariable("WIX_MUMS");
424 int maxCabSizeForLargeFileInMB = 0;
425 int maxPreCompressedSizeInMB = 0;
426 ulong testOverFlow = 0;
427
428 // Supply Compile MediaTemplate Attributes to Cabinet Builder
429 Table mediaTemplateTable = this.Output.Tables["WixMediaTemplate"];
430 if (mediaTemplateTable != null)
431 {
432 WixMediaTemplateRow mediaTemplateRow = (WixMediaTemplateRow)mediaTemplateTable.Rows[0];
433
434 // Get the Value for Max Cab Size for File Splitting
435 try
436 {
437 // Override authored mcslfs value if environment variable is authored.
438 if (!String.IsNullOrEmpty(mcslfsString))
439 {
440 maxCabSizeForLargeFileInMB = Int32.Parse(mcslfsString);
441 }
442 else
443 {
444 maxCabSizeForLargeFileInMB = mediaTemplateRow.MaximumCabinetSizeForLargeFileSplitting;
445 }
446 testOverFlow = (ulong)maxCabSizeForLargeFileInMB * 1024 * 1024;
447 }
448 catch (FormatException)
449 {
450 throw new WixException(WixErrors.IllegalEnvironmentVariable("WIX_MCSLFS", mcslfsString));
451 }
452 catch (OverflowException)
453 {
454 throw new WixException(WixErrors.MaximumCabinetSizeForLargeFileSplittingTooLarge(null, maxCabSizeForLargeFileInMB, CompilerCore.MaxValueOfMaxCabSizeForLargeFileSplitting));
455 }
456
457 try
458 {
459 // Override authored mums value if environment variable is authored.
460 if (!String.IsNullOrEmpty(mumsString))
461 {
462 maxPreCompressedSizeInMB = Int32.Parse(mumsString);
463 }
464 else
465 {
466 maxPreCompressedSizeInMB = mediaTemplateRow.MaximumUncompressedMediaSize;
467 }
468 testOverFlow = (ulong)maxPreCompressedSizeInMB * 1024 * 1024;
469 }
470 catch (FormatException)
471 {
472 throw new WixException(WixErrors.IllegalEnvironmentVariable("WIX_MUMS", mumsString));
473 }
474 catch (OverflowException)
475 {
476 throw new WixException(WixErrors.MaximumUncompressedMediaSizeTooLarge(null, maxPreCompressedSizeInMB));
477 }
478
479 maxCabSizeForLargeFileSplitting = maxCabSizeForLargeFileInMB;
480 maxUncompressedMediaSize = maxPreCompressedSizeInMB;
481 }
482 else
483 {
484 maxCabSizeForLargeFileSplitting = 0;
485 maxUncompressedMediaSize = CompilerCore.DefaultMaximumUncompressedMediaSize;
486 }
487 }
488 }
489}
diff --git a/src/WixToolset.Core/Bind/Databases/CreateDeltaPatchesCommand.cs b/src/WixToolset.Core/Bind/Databases/CreateDeltaPatchesCommand.cs
new file mode 100644
index 00000000..933a1ea8
--- /dev/null
+++ b/src/WixToolset.Core/Bind/Databases/CreateDeltaPatchesCommand.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 WixToolset.Bind.Databases
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.Rows;
11
12 /// <summary>
13 /// Creates delta patches and updates the appropriate rows to point to the newly generated patches.
14 /// </summary>
15 internal class CreateDeltaPatchesCommand : ICommand
16 {
17 public IEnumerable<FileFacade> FileFacades { private get; set; }
18
19 public Table WixPatchIdTable { private get; set; }
20
21 public string TempFilesLocation { private get; set; }
22
23 public void Execute()
24 {
25 bool optimizePatchSizeForLargeFiles = false;
26 PatchAPI.PatchInterop.PatchSymbolFlagsType apiPatchingSymbolFlags = 0;
27
28 if (null != this.WixPatchIdTable)
29 {
30 Row row = this.WixPatchIdTable.Rows[0];
31 if (null != row)
32 {
33 if (null != row[2])
34 {
35 optimizePatchSizeForLargeFiles = (1 == Convert.ToUInt32(row[2], CultureInfo.InvariantCulture));
36 }
37
38 if (null != row[3])
39 {
40 apiPatchingSymbolFlags = (PatchAPI.PatchInterop.PatchSymbolFlagsType)Convert.ToUInt32(row[3], CultureInfo.InvariantCulture);
41 }
42 }
43 }
44
45 foreach (FileFacade facade in this.FileFacades)
46 {
47 if (RowOperation.Modify == facade.File.Operation &&
48 0 != (facade.WixFile.PatchAttributes & PatchAttributeType.IncludeWholeFile))
49 {
50 string deltaBase = String.Concat("delta_", facade.File.File);
51 string deltaFile = Path.Combine(this.TempFilesLocation, String.Concat(deltaBase, ".dpf"));
52 string headerFile = Path.Combine(this.TempFilesLocation, String.Concat(deltaBase, ".phd"));
53
54 bool retainRangeWarning = false;
55
56 if (PatchAPI.PatchInterop.CreateDelta(
57 deltaFile,
58 facade.WixFile.Source,
59 facade.DeltaPatchFile.Symbols,
60 facade.DeltaPatchFile.RetainOffsets,
61 new[] { facade.WixFile.PreviousSource },
62 facade.DeltaPatchFile.PreviousSymbols.Split(new[] { ';' }),
63 facade.DeltaPatchFile.PreviousIgnoreLengths.Split(new[] { ';' }),
64 facade.DeltaPatchFile.PreviousIgnoreOffsets.Split(new[] { ';' }),
65 facade.DeltaPatchFile.PreviousRetainLengths.Split(new[] { ';' }),
66 facade.DeltaPatchFile.PreviousRetainOffsets.Split(new[] { ';' }),
67 apiPatchingSymbolFlags,
68 optimizePatchSizeForLargeFiles,
69 out retainRangeWarning))
70 {
71 PatchAPI.PatchInterop.ExtractDeltaHeader(deltaFile, headerFile);
72
73 facade.WixFile.Source = deltaFile;
74 facade.WixFile.DeltaPatchHeaderSource = headerFile;
75 }
76
77 if (retainRangeWarning)
78 {
79 // TODO: get patch family to add to warning message for PatchWiz parity.
80 Messaging.Instance.OnMessage(WixWarnings.RetainRangeMismatch(facade.File.SourceLineNumbers, facade.File.File));
81 }
82 }
83 }
84 }
85 }
86}
diff --git a/src/WixToolset.Core/Bind/Databases/CreateSpecialPropertiesCommand.cs b/src/WixToolset.Core/Bind/Databases/CreateSpecialPropertiesCommand.cs
new file mode 100644
index 00000000..5db2768b
--- /dev/null
+++ b/src/WixToolset.Core/Bind/Databases/CreateSpecialPropertiesCommand.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.Bind.Databases
4{
5 using System;
6 using System.Collections.Generic;
7 using WixToolset.Data;
8 using WixToolset.Data.Rows;
9
10 internal class CreateSpecialPropertiesCommand : ICommand
11 {
12 public Table PropertyTable { private get; set; }
13
14 public Table WixPropertyTable { private get; set; }
15
16 public void Execute()
17 {
18 // Create the special properties.
19 if (null != this.WixPropertyTable)
20 {
21 // Create lists of the properties that contribute to the special lists of properties.
22 SortedSet<string> adminProperties = new SortedSet<string>();
23 SortedSet<string> secureProperties = new SortedSet<string>();
24 SortedSet<string> hiddenProperties = new SortedSet<string>();
25
26 foreach (WixPropertyRow wixPropertyRow in this.WixPropertyTable.Rows)
27 {
28 if (wixPropertyRow.Admin)
29 {
30 adminProperties.Add(wixPropertyRow.Id);
31 }
32
33 if (wixPropertyRow.Hidden)
34 {
35 hiddenProperties.Add(wixPropertyRow.Id);
36 }
37
38 if (wixPropertyRow.Secure)
39 {
40 secureProperties.Add(wixPropertyRow.Id);
41 }
42 }
43
44 Table propertyTable = this.PropertyTable;
45 if (0 < adminProperties.Count)
46 {
47 PropertyRow row = (PropertyRow)propertyTable.CreateRow(null);
48 row.Property = "AdminProperties";
49 row.Value = String.Join(";", adminProperties);
50 }
51
52 if (0 < secureProperties.Count)
53 {
54 PropertyRow row = (PropertyRow)propertyTable.CreateRow(null);
55 row.Property = "SecureCustomProperties";
56 row.Value = String.Join(";", secureProperties);
57 }
58
59 if (0 < hiddenProperties.Count)
60 {
61 PropertyRow row = (PropertyRow)propertyTable.CreateRow(null);
62 row.Property = "MsiHiddenProperties";
63 row.Value = String.Join(";", hiddenProperties);
64 }
65 }
66 }
67 }
68}
diff --git a/src/WixToolset.Core/Bind/Databases/ExtractMergeModuleFilesCommand.cs b/src/WixToolset.Core/Bind/Databases/ExtractMergeModuleFilesCommand.cs
new file mode 100644
index 00000000..bee1488b
--- /dev/null
+++ b/src/WixToolset.Core/Bind/Databases/ExtractMergeModuleFilesCommand.cs
@@ -0,0 +1,225 @@
1// Copyright (c) .NET Foundation and 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.Bind.Databases
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.Cab;
13 using WixToolset.Data;
14 using WixToolset.Data.Rows;
15 using WixToolset.MergeMod;
16 using WixToolset.Msi;
17 using WixToolset.Core.Native;
18
19 /// <summary>
20 /// Retrieve files information and extract them from merge modules.
21 /// </summary>
22 internal class ExtractMergeModuleFilesCommand : ICommand
23 {
24 public IEnumerable<FileFacade> FileFacades { private get; set; }
25
26 public Table FileTable { private get; set; }
27
28 public Table WixFileTable { private get; set; }
29
30 public Table WixMergeTable { private get; set; }
31
32 public int OutputInstallerVersion { private get; set; }
33
34 public bool SuppressLayout { private get; set; }
35
36 public string TempFilesLocation { private get; set; }
37
38 public IEnumerable<FileFacade> MergeModulesFileFacades { get; private set; }
39
40 public void Execute()
41 {
42 List<FileFacade> mergeModulesFileFacades = new List<FileFacade>();
43
44 IMsmMerge2 merge = MsmInterop.GetMsmMerge();
45
46 // Index all of the file rows to be able to detect collisions with files in the Merge Modules.
47 // It may seem a bit expensive to build up this index solely for the purpose of checking collisions
48 // and you may be thinking, "Surely, we must need the file rows indexed elsewhere." It turns out
49 // there are other cases where we need all the file rows indexed, however they are not common cases.
50 // Now since Merge Modules are already slow and generally less desirable than .wixlibs we'll let
51 // this case be slightly more expensive because the cost of maintaining an indexed file row collection
52 // is a lot more costly for the common cases.
53 Dictionary<string, FileFacade> indexedFileFacades = this.FileFacades.ToDictionary(f => f.File.File, StringComparer.Ordinal);
54
55 foreach (WixMergeRow wixMergeRow in this.WixMergeTable.Rows)
56 {
57 bool containsFiles = this.CreateFacadesForMergeModuleFiles(wixMergeRow, mergeModulesFileFacades, indexedFileFacades);
58
59 // If the module has files and creating layout
60 if (containsFiles && !this.SuppressLayout)
61 {
62 this.ExtractFilesFromMergeModule(merge, wixMergeRow);
63 }
64 }
65
66 this.MergeModulesFileFacades = mergeModulesFileFacades;
67 }
68
69 private bool CreateFacadesForMergeModuleFiles(WixMergeRow wixMergeRow, List<FileFacade> mergeModulesFileFacades, Dictionary<string, FileFacade> indexedFileFacades)
70 {
71 bool containsFiles = false;
72
73 try
74 {
75 // read the module's File table to get its FileMediaInformation entries and gather any other information needed from the module.
76 using (Database db = new Database(wixMergeRow.SourceFile, OpenDatabase.ReadOnly))
77 {
78 if (db.TableExists("File") && db.TableExists("Component"))
79 {
80 Dictionary<string, FileFacade> uniqueModuleFileIdentifiers = new Dictionary<string, FileFacade>(StringComparer.OrdinalIgnoreCase);
81
82 using (View view = db.OpenExecuteView("SELECT `File`, `Directory_` FROM `File`, `Component` WHERE `Component_`=`Component`"))
83 {
84 // add each file row from the merge module into the file row collection (check for errors along the way)
85 while (true)
86 {
87 using (Record record = view.Fetch())
88 {
89 if (null == record)
90 {
91 break;
92 }
93
94 // NOTE: this is very tricky - the merge module file rows are not added to the
95 // file table because they should not be created via idt import. Instead, these
96 // rows are created by merging in the actual modules.
97 FileRow fileRow = (FileRow)this.FileTable.CreateRow(wixMergeRow.SourceLineNumbers, false);
98 fileRow.File = record[1];
99 fileRow.Compressed = wixMergeRow.FileCompression;
100
101 WixFileRow wixFileRow = (WixFileRow)this.WixFileTable.CreateRow(wixMergeRow.SourceLineNumbers, false);
102 wixFileRow.Directory = record[2];
103 wixFileRow.DiskId = wixMergeRow.DiskId;
104 wixFileRow.PatchGroup = -1;
105 wixFileRow.Source = String.Concat(this.TempFilesLocation, Path.DirectorySeparatorChar, "MergeId.", wixMergeRow.Number.ToString(CultureInfo.InvariantCulture), Path.DirectorySeparatorChar, record[1]);
106
107 FileFacade mergeModuleFileFacade = new FileFacade(true, fileRow, wixFileRow);
108
109 FileFacade collidingFacade;
110
111 // If case-sensitive collision with another merge module or a user-authored file identifier.
112 if (indexedFileFacades.TryGetValue(mergeModuleFileFacade.File.File, out collidingFacade))
113 {
114 Messaging.Instance.OnMessage(WixErrors.DuplicateModuleFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, collidingFacade.File.File));
115 }
116 else if (uniqueModuleFileIdentifiers.TryGetValue(mergeModuleFileFacade.File.File, out collidingFacade)) // case-insensitive collision with another file identifier in the same merge module
117 {
118 Messaging.Instance.OnMessage(WixErrors.DuplicateModuleCaseInsensitiveFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, mergeModuleFileFacade.File.File, collidingFacade.File.File));
119 }
120 else // no collision
121 {
122 mergeModulesFileFacades.Add(mergeModuleFileFacade);
123
124 // Keep updating the indexes as new rows are added.
125 indexedFileFacades.Add(mergeModuleFileFacade.File.File, mergeModuleFileFacade);
126 uniqueModuleFileIdentifiers.Add(mergeModuleFileFacade.File.File, mergeModuleFileFacade);
127 }
128
129 containsFiles = true;
130 }
131 }
132 }
133 }
134
135 // Get the summary information to detect the Schema
136 using (SummaryInformation summaryInformation = new SummaryInformation(db))
137 {
138 string moduleInstallerVersionString = summaryInformation.GetProperty(14);
139
140 try
141 {
142 int moduleInstallerVersion = Convert.ToInt32(moduleInstallerVersionString, CultureInfo.InvariantCulture);
143 if (moduleInstallerVersion > this.OutputInstallerVersion)
144 {
145 Messaging.Instance.OnMessage(WixWarnings.InvalidHigherInstallerVersionInModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, moduleInstallerVersion, this.OutputInstallerVersion));
146 }
147 }
148 catch (FormatException)
149 {
150 throw new WixException(WixErrors.MissingOrInvalidModuleInstallerVersion(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.SourceFile, moduleInstallerVersionString));
151 }
152 }
153 }
154 }
155 catch (FileNotFoundException)
156 {
157 throw new WixException(WixErrors.FileNotFound(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile));
158 }
159 catch (Win32Exception)
160 {
161 throw new WixException(WixErrors.CannotOpenMergeModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.SourceFile));
162 }
163
164 return containsFiles;
165 }
166
167 private void ExtractFilesFromMergeModule(IMsmMerge2 merge, WixMergeRow wixMergeRow)
168 {
169 bool moduleOpen = false;
170 short mergeLanguage;
171
172 try
173 {
174 mergeLanguage = Convert.ToInt16(wixMergeRow.Language, CultureInfo.InvariantCulture);
175 }
176 catch (System.FormatException)
177 {
178 Messaging.Instance.OnMessage(WixErrors.InvalidMergeLanguage(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.Language));
179 return;
180 }
181
182 try
183 {
184 merge.OpenModule(wixMergeRow.SourceFile, mergeLanguage);
185 moduleOpen = true;
186
187 string safeMergeId = wixMergeRow.Number.ToString(CultureInfo.InvariantCulture.NumberFormat);
188
189 // extract the module cabinet, then explode all of the files to a temp directory
190 string moduleCabPath = String.Concat(this.TempFilesLocation, Path.DirectorySeparatorChar, safeMergeId, ".module.cab");
191 merge.ExtractCAB(moduleCabPath);
192
193 string mergeIdPath = String.Concat(this.TempFilesLocation, Path.DirectorySeparatorChar, "MergeId.", safeMergeId);
194 Directory.CreateDirectory(mergeIdPath);
195
196 using (WixExtractCab extractCab = new WixExtractCab())
197 {
198 try
199 {
200 extractCab.Extract(moduleCabPath, mergeIdPath);
201 }
202 catch (FileNotFoundException)
203 {
204 throw new WixException(WixErrors.CabFileDoesNotExist(moduleCabPath, wixMergeRow.SourceFile, mergeIdPath));
205 }
206 catch
207 {
208 throw new WixException(WixErrors.CabExtractionFailed(moduleCabPath, wixMergeRow.SourceFile, mergeIdPath));
209 }
210 }
211 }
212 catch (COMException ce)
213 {
214 throw new WixException(WixErrors.UnableToOpenModule(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile, ce.Message));
215 }
216 finally
217 {
218 if (moduleOpen)
219 {
220 merge.CloseModule();
221 }
222 }
223 }
224 }
225}
diff --git a/src/WixToolset.Core/Bind/Databases/FileFacade.cs b/src/WixToolset.Core/Bind/Databases/FileFacade.cs
new file mode 100644
index 00000000..37115c97
--- /dev/null
+++ b/src/WixToolset.Core/Bind/Databases/FileFacade.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.Bind.Databases
4{
5 using System.Collections.Generic;
6 using WixToolset.Data;
7 using WixToolset.Data.Rows;
8
9 public class FileFacade
10 {
11 public FileFacade(FileRow file, WixFileRow wixFile, WixDeltaPatchFileRow deltaPatchFile)
12 {
13 this.File = file;
14 this.WixFile = wixFile;
15 this.DeltaPatchFile = deltaPatchFile;
16 }
17
18 public FileFacade(bool fromModule, FileRow file, WixFileRow wixFile)
19 {
20 this.FromModule = fromModule;
21 this.File = file;
22 this.WixFile = wixFile;
23 }
24
25 public bool FromModule { get; private set; }
26
27 public FileRow File { get; private set; }
28
29 public WixFileRow WixFile { get; private set; }
30
31 public WixDeltaPatchFileRow DeltaPatchFile { get; private set; }
32
33 /// <summary>
34 /// Gets the set of MsiAssemblyName rows created for this file.
35 /// </summary>
36 /// <value>RowCollection of MsiAssemblyName table.</value>
37 public List<Row> AssemblyNames { get; set; }
38
39 /// <summary>
40 /// Gets or sets the MsiFileHash row for this file.
41 /// </summary>
42 public Row Hash { get; set; }
43 }
44}
diff --git a/src/WixToolset.Core/Bind/Databases/GetFileFacadesCommand.cs b/src/WixToolset.Core/Bind/Databases/GetFileFacadesCommand.cs
new file mode 100644
index 00000000..b6bcd3af
--- /dev/null
+++ b/src/WixToolset.Core/Bind/Databases/GetFileFacadesCommand.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 WixToolset.Bind.Databases
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.Rows;
11
12 internal class GetFileFacadesCommand : ICommand
13 {
14 public Table FileTable { private get; set; }
15
16 public Table WixFileTable { private get; set; }
17
18 public Table WixDeltaPatchFileTable { private get; set; }
19
20 public Table WixDeltaPatchSymbolPathsTable { private get; set; }
21
22 public List<FileFacade> FileFacades { get; private set; }
23
24 public void Execute()
25 {
26 List<FileFacade> facades = new List<FileFacade>(this.FileTable.Rows.Count);
27
28 RowDictionary<WixFileRow> wixFiles = new RowDictionary<WixFileRow>(this.WixFileTable);
29 RowDictionary<WixDeltaPatchFileRow> deltaPatchFiles = new RowDictionary<WixDeltaPatchFileRow>(this.WixDeltaPatchFileTable);
30
31 foreach (FileRow file in this.FileTable.Rows)
32 {
33 WixDeltaPatchFileRow deltaPatchFile = null;
34
35 deltaPatchFiles.TryGetValue(file.File, out deltaPatchFile);
36
37 facades.Add(new FileFacade(file, wixFiles[file.File], deltaPatchFile));
38 }
39
40 if (null != this.WixDeltaPatchSymbolPathsTable)
41 {
42 this.ResolveDeltaPatchSymbolPaths(deltaPatchFiles, facades);
43 }
44
45 this.FileFacades = facades;
46 }
47
48 /// <summary>
49 /// Merge data from the WixPatchSymbolPaths rows into the WixDeltaPatchFile rows.
50 /// </summary>
51 public RowDictionary<WixDeltaPatchFileRow> ResolveDeltaPatchSymbolPaths(RowDictionary<WixDeltaPatchFileRow> deltaPatchFiles, IEnumerable<FileFacade> facades)
52 {
53 ILookup<string, FileFacade> filesByComponent = null;
54 ILookup<string, FileFacade> filesByDirectory = null;
55 ILookup<string, FileFacade> filesByDiskId = null;
56
57 foreach (WixDeltaPatchSymbolPathsRow row in this.WixDeltaPatchSymbolPathsTable.RowsAs<WixDeltaPatchSymbolPathsRow>().OrderBy(r => r.Type))
58 {
59 switch (row.Type)
60 {
61 case SymbolPathType.File:
62 this.MergeSymbolPaths(row, deltaPatchFiles[row.Id]);
63 break;
64
65 case SymbolPathType.Component:
66 if (null == filesByComponent)
67 {
68 filesByComponent = facades.ToLookup(f => f.File.Component);
69 }
70
71 foreach (FileFacade facade in filesByComponent[row.Id])
72 {
73 this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.File]);
74 }
75 break;
76
77 case SymbolPathType.Directory:
78 if (null == filesByDirectory)
79 {
80 filesByDirectory = facades.ToLookup(f => f.WixFile.Directory);
81 }
82
83 foreach (FileFacade facade in filesByDirectory[row.Id])
84 {
85 this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.File]);
86 }
87 break;
88
89 case SymbolPathType.Media:
90 if (null == filesByDiskId)
91 {
92 filesByDiskId = facades.ToLookup(f => f.WixFile.DiskId.ToString(CultureInfo.InvariantCulture));
93 }
94
95 foreach (FileFacade facade in filesByDiskId[row.Id])
96 {
97 this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.File]);
98 }
99 break;
100
101 case SymbolPathType.Product:
102 foreach (WixDeltaPatchFileRow fileRow in deltaPatchFiles.Values)
103 {
104 this.MergeSymbolPaths(row, fileRow);
105 }
106 break;
107
108 default:
109 // error
110 break;
111 }
112 }
113
114 return deltaPatchFiles;
115 }
116
117 /// <summary>
118 /// Merge data from a row in the WixPatchSymbolsPaths table into an associated WixDeltaPatchFile row.
119 /// </summary>
120 /// <param name="row">Row from the WixPatchSymbolsPaths table.</param>
121 /// <param name="file">FileRow into which to set symbol information.</param>
122 /// <comment>This includes PreviousData as well.</comment>
123 private void MergeSymbolPaths(WixDeltaPatchSymbolPathsRow row, WixDeltaPatchFileRow file)
124 {
125 if (null == file.Symbols)
126 {
127 file.Symbols = row.SymbolPaths;
128 }
129 else
130 {
131 file.Symbols = String.Concat(file.Symbols, ";", row.SymbolPaths);
132 }
133
134 Field field = row.Fields[2];
135 if (null != field.PreviousData)
136 {
137 if (null == file.PreviousSymbols)
138 {
139 file.PreviousSymbols = field.PreviousData;
140 }
141 else
142 {
143 file.PreviousSymbols = String.Concat(file.PreviousSymbols, ";", field.PreviousData);
144 }
145 }
146 }
147 }
148}
diff --git a/src/WixToolset.Core/Bind/Databases/MergeModulesCommand.cs b/src/WixToolset.Core/Bind/Databases/MergeModulesCommand.cs
new file mode 100644
index 00000000..035ef059
--- /dev/null
+++ b/src/WixToolset.Core/Bind/Databases/MergeModulesCommand.cs
@@ -0,0 +1,350 @@
1// Copyright (c) .NET Foundation and 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.Bind.Databases
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Collections.Specialized;
8 using System.ComponentModel;
9 using System.Diagnostics;
10 using System.Globalization;
11 using System.IO;
12 using System.Linq;
13 using System.Runtime.InteropServices;
14 using System.Text;
15 using System.Xml;
16 using System.Xml.XPath;
17 using WixToolset.Clr.Interop;
18 using WixToolset.Data;
19 using WixToolset.Data.Rows;
20 using WixToolset.MergeMod;
21 using WixToolset.Msi;
22 using WixToolset.Core.Native;
23
24 /// <summary>
25 /// Update file information.
26 /// </summary>
27 internal class MergeModulesCommand : ICommand
28 {
29 public IEnumerable<FileFacade> FileFacades { private get; set; }
30
31 public Output Output { private get; set; }
32
33 public string OutputPath { private get; set; }
34
35 public IEnumerable<string> SuppressedTableNames { private get; set; }
36
37 public string TempFilesLocation { private get; set; }
38
39 public void Execute()
40 {
41 Debug.Assert(OutputType.Product == this.Output.Type);
42
43 Table wixMergeTable = this.Output.Tables["WixMerge"];
44 Table wixFeatureModulesTable = this.Output.Tables["WixFeatureModules"];
45
46 // check for merge rows to see if there is any work to do
47 if (null == wixMergeTable || 0 == wixMergeTable.Rows.Count)
48 {
49 return;
50 }
51
52 IMsmMerge2 merge = null;
53 bool commit = true;
54 bool logOpen = false;
55 bool databaseOpen = false;
56 string logPath = null;
57 try
58 {
59 merge = MsmInterop.GetMsmMerge();
60
61 logPath = Path.Combine(this.TempFilesLocation, "merge.log");
62 merge.OpenLog(logPath);
63 logOpen = true;
64
65 merge.OpenDatabase(this.OutputPath);
66 databaseOpen = true;
67
68 // process all the merge rows
69 foreach (WixMergeRow wixMergeRow in wixMergeTable.Rows)
70 {
71 bool moduleOpen = false;
72
73 try
74 {
75 short mergeLanguage;
76
77 try
78 {
79 mergeLanguage = Convert.ToInt16(wixMergeRow.Language, CultureInfo.InvariantCulture);
80 }
81 catch (System.FormatException)
82 {
83 Messaging.Instance.OnMessage(WixErrors.InvalidMergeLanguage(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.Language));
84 continue;
85 }
86
87 Messaging.Instance.OnMessage(WixVerboses.OpeningMergeModule(wixMergeRow.SourceFile, mergeLanguage));
88 merge.OpenModule(wixMergeRow.SourceFile, mergeLanguage);
89 moduleOpen = true;
90
91 // If there is merge configuration data, create a callback object to contain it all.
92 ConfigurationCallback callback = null;
93 if (!String.IsNullOrEmpty(wixMergeRow.ConfigurationData))
94 {
95 callback = new ConfigurationCallback(wixMergeRow.ConfigurationData);
96 }
97
98 // merge the module into the database that's being built
99 Messaging.Instance.OnMessage(WixVerboses.MergingMergeModule(wixMergeRow.SourceFile));
100 merge.MergeEx(wixMergeRow.Feature, wixMergeRow.Directory, callback);
101
102 // connect any non-primary features
103 if (null != wixFeatureModulesTable)
104 {
105 foreach (Row row in wixFeatureModulesTable.Rows)
106 {
107 if (wixMergeRow.Id == (string)row[1])
108 {
109 Messaging.Instance.OnMessage(WixVerboses.ConnectingMergeModule(wixMergeRow.SourceFile, (string)row[0]));
110 merge.Connect((string)row[0]);
111 }
112 }
113 }
114 }
115 catch (COMException)
116 {
117 commit = false;
118 }
119 finally
120 {
121 IMsmErrors mergeErrors = merge.Errors;
122
123 // display all the errors encountered during the merge operations for this module
124 for (int i = 1; i <= mergeErrors.Count; i++)
125 {
126 IMsmError mergeError = mergeErrors[i];
127 StringBuilder databaseKeys = new StringBuilder();
128 StringBuilder moduleKeys = new StringBuilder();
129
130 // build a string of the database keys
131 for (int j = 1; j <= mergeError.DatabaseKeys.Count; j++)
132 {
133 if (1 != j)
134 {
135 databaseKeys.Append(';');
136 }
137 databaseKeys.Append(mergeError.DatabaseKeys[j]);
138 }
139
140 // build a string of the module keys
141 for (int j = 1; j <= mergeError.ModuleKeys.Count; j++)
142 {
143 if (1 != j)
144 {
145 moduleKeys.Append(';');
146 }
147 moduleKeys.Append(mergeError.ModuleKeys[j]);
148 }
149
150 // display the merge error based on the msm error type
151 switch (mergeError.Type)
152 {
153 case MsmErrorType.msmErrorExclusion:
154 Messaging.Instance.OnMessage(WixErrors.MergeExcludedModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, moduleKeys.ToString()));
155 break;
156 case MsmErrorType.msmErrorFeatureRequired:
157 Messaging.Instance.OnMessage(WixErrors.MergeFeatureRequired(wixMergeRow.SourceLineNumbers, mergeError.ModuleTable, moduleKeys.ToString(), wixMergeRow.SourceFile, wixMergeRow.Id));
158 break;
159 case MsmErrorType.msmErrorLanguageFailed:
160 Messaging.Instance.OnMessage(WixErrors.MergeLanguageFailed(wixMergeRow.SourceLineNumbers, mergeError.Language, wixMergeRow.SourceFile));
161 break;
162 case MsmErrorType.msmErrorLanguageUnsupported:
163 Messaging.Instance.OnMessage(WixErrors.MergeLanguageUnsupported(wixMergeRow.SourceLineNumbers, mergeError.Language, wixMergeRow.SourceFile));
164 break;
165 case MsmErrorType.msmErrorResequenceMerge:
166 Messaging.Instance.OnMessage(WixWarnings.MergeRescheduledAction(wixMergeRow.SourceLineNumbers, mergeError.DatabaseTable, databaseKeys.ToString(), wixMergeRow.SourceFile));
167 break;
168 case MsmErrorType.msmErrorTableMerge:
169 if ("_Validation" != mergeError.DatabaseTable) // ignore merge errors in the _Validation table
170 {
171 Messaging.Instance.OnMessage(WixWarnings.MergeTableFailed(wixMergeRow.SourceLineNumbers, mergeError.DatabaseTable, databaseKeys.ToString(), wixMergeRow.SourceFile));
172 }
173 break;
174 case MsmErrorType.msmErrorPlatformMismatch:
175 Messaging.Instance.OnMessage(WixErrors.MergePlatformMismatch(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile));
176 break;
177 default:
178 Messaging.Instance.OnMessage(WixErrors.UnexpectedException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnexpectedMergerErrorWithType, Enum.GetName(typeof(MsmErrorType), mergeError.Type), logPath), "InvalidOperationException", Environment.StackTrace));
179 break;
180 }
181 }
182
183 if (0 >= mergeErrors.Count && !commit)
184 {
185 Messaging.Instance.OnMessage(WixErrors.UnexpectedException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnexpectedMergerErrorInSourceFile, wixMergeRow.SourceFile, logPath), "InvalidOperationException", Environment.StackTrace));
186 }
187
188 if (moduleOpen)
189 {
190 merge.CloseModule();
191 }
192 }
193 }
194 }
195 finally
196 {
197 if (databaseOpen)
198 {
199 merge.CloseDatabase(commit);
200 }
201
202 if (logOpen)
203 {
204 merge.CloseLog();
205 }
206 }
207
208 // stop processing if an error previously occurred
209 if (Messaging.Instance.EncounteredError)
210 {
211 return;
212 }
213
214 using (Database db = new Database(this.OutputPath, OpenDatabase.Direct))
215 {
216 Table suppressActionTable = this.Output.Tables["WixSuppressAction"];
217
218 // suppress individual actions
219 if (null != suppressActionTable)
220 {
221 foreach (Row row in suppressActionTable.Rows)
222 {
223 if (db.TableExists((string)row[0]))
224 {
225 string query = String.Format(CultureInfo.InvariantCulture, "SELECT * FROM {0} WHERE `Action` = '{1}'", row[0].ToString(), (string)row[1]);
226
227 using (View view = db.OpenExecuteView(query))
228 {
229 using (Record record = view.Fetch())
230 {
231 if (null != record)
232 {
233 Messaging.Instance.OnMessage(WixWarnings.SuppressMergedAction((string)row[1], row[0].ToString()));
234 view.Modify(ModifyView.Delete, record);
235 }
236 }
237 }
238 }
239 }
240 }
241
242 // query for merge module actions in suppressed sequences and drop them
243 foreach (string tableName in this.SuppressedTableNames)
244 {
245 if (!db.TableExists(tableName))
246 {
247 continue;
248 }
249
250 using (View view = db.OpenExecuteView(String.Concat("SELECT `Action` FROM ", tableName)))
251 {
252 while (true)
253 {
254 using (Record resultRecord = view.Fetch())
255 {
256 if (null == resultRecord)
257 {
258 break;
259 }
260
261 Messaging.Instance.OnMessage(WixWarnings.SuppressMergedAction(resultRecord.GetString(1), tableName));
262 }
263 }
264 }
265
266 // drop suppressed sequences
267 using (View view = db.OpenExecuteView(String.Concat("DROP TABLE ", tableName)))
268 {
269 }
270
271 // delete the validation rows
272 using (View view = db.OpenView(String.Concat("DELETE FROM _Validation WHERE `Table` = ?")))
273 {
274 using (Record record = new Record(1))
275 {
276 record.SetString(1, tableName);
277 view.Execute(record);
278 }
279 }
280 }
281
282 // now update the Attributes column for the files from the Merge Modules
283 Messaging.Instance.OnMessage(WixVerboses.ResequencingMergeModuleFiles());
284 using (View view = db.OpenView("SELECT `Sequence`, `Attributes` FROM `File` WHERE `File`=?"))
285 {
286 foreach (FileFacade file in this.FileFacades)
287 {
288 if (!file.FromModule)
289 {
290 continue;
291 }
292
293 using (Record record = new Record(1))
294 {
295 record.SetString(1, file.File.File);
296 view.Execute(record);
297 }
298
299 using (Record recordUpdate = view.Fetch())
300 {
301 if (null == recordUpdate)
302 {
303 throw new InvalidOperationException("Failed to fetch a File row from the database that was merged in from a module.");
304 }
305
306 recordUpdate.SetInteger(1, file.File.Sequence);
307
308 // update the file attributes to match the compression specified
309 // on the Merge element or on the Package element
310 int attributes = 0;
311
312 // get the current value if its not null
313 if (!recordUpdate.IsNull(2))
314 {
315 attributes = recordUpdate.GetInteger(2);
316 }
317
318 if (YesNoType.Yes == file.File.Compressed)
319 {
320 // these are mutually exclusive
321 attributes |= MsiInterop.MsidbFileAttributesCompressed;
322 attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed;
323 }
324 else if (YesNoType.No == file.File.Compressed)
325 {
326 // these are mutually exclusive
327 attributes |= MsiInterop.MsidbFileAttributesNoncompressed;
328 attributes &= ~MsiInterop.MsidbFileAttributesCompressed;
329 }
330 else // not specified
331 {
332 Debug.Assert(YesNoType.NotSet == file.File.Compressed);
333
334 // clear any compression bits
335 attributes &= ~MsiInterop.MsidbFileAttributesCompressed;
336 attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed;
337 }
338
339 recordUpdate.SetInteger(2, attributes);
340
341 view.Modify(ModifyView.Update, recordUpdate);
342 }
343 }
344 }
345
346 db.Commit();
347 }
348 }
349 }
350}
diff --git a/src/WixToolset.Core/Bind/Databases/ProcessUncompressedFilesCommand.cs b/src/WixToolset.Core/Bind/Databases/ProcessUncompressedFilesCommand.cs
new file mode 100644
index 00000000..dd7b85b7
--- /dev/null
+++ b/src/WixToolset.Core/Bind/Databases/ProcessUncompressedFilesCommand.cs
@@ -0,0 +1,115 @@
1// Copyright (c) .NET Foundation and 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.Bind.Databases
4{
5 using System;
6 using System.Collections;
7 using System.Collections.Generic;
8 using System.IO;
9 using WixToolset.Data;
10 using WixToolset.Data.Rows;
11 using WixToolset.Msi;
12 using WixToolset.Core.Native;
13
14 /// <summary>
15 /// Defines the file transfers necessary to layout the uncompressed files.
16 /// </summary>
17 internal class ProcessUncompressedFilesCommand : ICommand
18 {
19 public string DatabasePath { private get; set; }
20
21 public IEnumerable<FileFacade> FileFacades { private get; set; }
22
23 public RowDictionary<MediaRow> MediaRows { private get; set; }
24
25 public string LayoutDirectory { private get; set; }
26
27 public bool Compressed { private get; set; }
28
29 public bool LongNamesInImage { private get; set; }
30
31 public Func<MediaRow, string, string, string> ResolveMedia { private get; set; }
32
33 public Table WixMediaTable { private get; set; }
34
35 public IEnumerable<FileTransfer> FileTransfers { get; private set; }
36
37 public void Execute()
38 {
39 List<FileTransfer> fileTransfers = new List<FileTransfer>();
40
41 Hashtable directories = new Hashtable();
42
43 RowDictionary<WixMediaRow> wixMediaRows = new RowDictionary<WixMediaRow>(this.WixMediaTable);
44
45 using (Database db = new Database(this.DatabasePath, OpenDatabase.ReadOnly))
46 {
47 using (View directoryView = db.OpenExecuteView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`"))
48 {
49 while (true)
50 {
51 using (Record directoryRecord = directoryView.Fetch())
52 {
53 if (null == directoryRecord)
54 {
55 break;
56 }
57
58 string sourceName = Installer.GetName(directoryRecord.GetString(3), true, this.LongNamesInImage);
59
60 directories.Add(directoryRecord.GetString(1), new ResolvedDirectory(directoryRecord.GetString(2), sourceName));
61 }
62 }
63 }
64
65 using (View fileView = db.OpenView("SELECT `Directory_`, `FileName` FROM `Component`, `File` WHERE `Component`.`Component`=`File`.`Component_` AND `File`.`File`=?"))
66 {
67 using (Record fileQueryRecord = new Record(1))
68 {
69 // for each file in the array of uncompressed files
70 foreach (FileFacade facade in this.FileFacades)
71 {
72 MediaRow mediaRow = this.MediaRows.Get(facade.WixFile.DiskId);
73 string relativeFileLayoutPath = null;
74
75 WixMediaRow wixMediaRow = null;
76 string mediaLayoutFolder = null;
77
78 if (wixMediaRows.TryGetValue(mediaRow.GetKey(), out wixMediaRow))
79 {
80 mediaLayoutFolder = wixMediaRow.Layout;
81 }
82
83 string mediaLayoutDirectory = this.ResolveMedia(mediaRow, mediaLayoutFolder, this.LayoutDirectory);
84
85 // setup up the query record and find the appropriate file in the
86 // previously executed file view
87 fileQueryRecord[1] = facade.File.File;
88 fileView.Execute(fileQueryRecord);
89
90 using (Record fileRecord = fileView.Fetch())
91 {
92 if (null == fileRecord)
93 {
94 throw new WixException(WixErrors.FileIdentifierNotFound(facade.File.SourceLineNumbers, facade.File.File));
95 }
96
97 relativeFileLayoutPath = Binder.GetFileSourcePath(directories, fileRecord[1], fileRecord[2], this.Compressed, this.LongNamesInImage);
98 }
99
100 // finally put together the base media layout path and the relative file layout path
101 string fileLayoutPath = Path.Combine(mediaLayoutDirectory, relativeFileLayoutPath);
102 FileTransfer transfer;
103 if (FileTransfer.TryCreate(facade.WixFile.Source, fileLayoutPath, false, "File", facade.File.SourceLineNumbers, out transfer))
104 {
105 fileTransfers.Add(transfer);
106 }
107 }
108 }
109 }
110 }
111
112 this.FileTransfers = fileTransfers;
113 }
114 }
115}
diff --git a/src/WixToolset.Core/Bind/Databases/UpdateControlTextCommand.cs b/src/WixToolset.Core/Bind/Databases/UpdateControlTextCommand.cs
new file mode 100644
index 00000000..9e17ee02
--- /dev/null
+++ b/src/WixToolset.Core/Bind/Databases/UpdateControlTextCommand.cs
@@ -0,0 +1,80 @@
1// Copyright (c) .NET Foundation and 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.Bind.Databases
4{
5 using System;
6 using System.IO;
7 using WixToolset.Data;
8 using WixToolset.Data.Rows;
9
10 internal class UpdateControlTextCommand : ICommand
11 {
12 public Table BBControlTable { private get; set; }
13
14 public Table WixBBControlTable { private get; set; }
15
16 public Table ControlTable { private get; set; }
17
18 public Table WixControlTable { private get; set; }
19
20 public void Execute()
21 {
22 if (null != this.WixBBControlTable)
23 {
24 RowDictionary<BBControlRow> bbControlRows = new RowDictionary<BBControlRow>(this.BBControlTable);
25 foreach (Row wixRow in this.WixBBControlTable.Rows)
26 {
27 BBControlRow bbControlRow = bbControlRows.Get(wixRow.GetPrimaryKey());
28 bbControlRow.Text = this.ReadTextFile(bbControlRow.SourceLineNumbers, wixRow.FieldAsString(2));
29 }
30 }
31
32 if (null != this.WixControlTable)
33 {
34 RowDictionary<ControlRow> controlRows = new RowDictionary<ControlRow>(this.ControlTable);
35 foreach (Row wixRow in this.WixControlTable.Rows)
36 {
37 ControlRow controlRow = controlRows.Get(wixRow.GetPrimaryKey());
38 controlRow.Text = this.ReadTextFile(controlRow.SourceLineNumbers, wixRow.FieldAsString(2));
39 }
40 }
41 }
42
43 /// <summary>
44 /// Reads a text file and returns the contents.
45 /// </summary>
46 /// <param name="sourceLineNumbers">Source line numbers for row from source.</param>
47 /// <param name="source">Source path to file to read.</param>
48 /// <returns>Text string read from file.</returns>
49 private string ReadTextFile(SourceLineNumber sourceLineNumbers, string source)
50 {
51 string text = null;
52
53 try
54 {
55 using (StreamReader reader = new StreamReader(source))
56 {
57 text = reader.ReadToEnd();
58 }
59 }
60 catch (DirectoryNotFoundException e)
61 {
62 Messaging.Instance.OnMessage(WixErrors.BinderFileManagerMissingFile(sourceLineNumbers, e.Message));
63 }
64 catch (FileNotFoundException e)
65 {
66 Messaging.Instance.OnMessage(WixErrors.BinderFileManagerMissingFile(sourceLineNumbers, e.Message));
67 }
68 catch (IOException e)
69 {
70 Messaging.Instance.OnMessage(WixErrors.BinderFileManagerMissingFile(sourceLineNumbers, e.Message));
71 }
72 catch (NotSupportedException)
73 {
74 Messaging.Instance.OnMessage(WixErrors.FileNotFound(sourceLineNumbers, source));
75 }
76
77 return text;
78 }
79 }
80}
diff --git a/src/WixToolset.Core/Bind/Databases/UpdateFileFacadesCommand.cs b/src/WixToolset.Core/Bind/Databases/UpdateFileFacadesCommand.cs
new file mode 100644
index 00000000..36818afa
--- /dev/null
+++ b/src/WixToolset.Core/Bind/Databases/UpdateFileFacadesCommand.cs
@@ -0,0 +1,532 @@
1// Copyright (c) .NET Foundation and 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.Bind.Databases
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Collections.Specialized;
8 using System.ComponentModel;
9 using System.Globalization;
10 using System.IO;
11 using System.Linq;
12 using System.Xml;
13 using System.Xml.XPath;
14 using WixToolset.Clr.Interop;
15 using WixToolset.Data;
16 using WixToolset.Data.Rows;
17 using WixToolset.Msi;
18
19 /// <summary>
20 /// Update file information.
21 /// </summary>
22 internal class UpdateFileFacadesCommand : ICommand
23 {
24 public IEnumerable<FileFacade> FileFacades { private get; set; }
25
26 public IEnumerable<FileFacade> UpdateFileFacades { private get; set; }
27
28 public string ModularizationGuid { private get; set; }
29
30 public Output Output { private get; set; }
31
32 public bool OverwriteHash { private get; set; }
33
34 public TableDefinitionCollection TableDefinitions { private get; set; }
35
36 public IDictionary<string, string> VariableCache { private get; set; }
37
38 public void Execute()
39 {
40 foreach (FileFacade file in this.UpdateFileFacades)
41 {
42 this.UpdateFileFacade(file);
43 }
44 }
45
46 private void UpdateFileFacade(FileFacade file)
47 {
48 FileInfo fileInfo = null;
49 try
50 {
51 fileInfo = new FileInfo(file.WixFile.Source);
52 }
53 catch (ArgumentException)
54 {
55 Messaging.Instance.OnMessage(WixErrors.InvalidFileName(file.File.SourceLineNumbers, file.WixFile.Source));
56 return;
57 }
58 catch (PathTooLongException)
59 {
60 Messaging.Instance.OnMessage(WixErrors.InvalidFileName(file.File.SourceLineNumbers, file.WixFile.Source));
61 return;
62 }
63 catch (NotSupportedException)
64 {
65 Messaging.Instance.OnMessage(WixErrors.InvalidFileName(file.File.SourceLineNumbers, file.WixFile.Source));
66 return;
67 }
68
69 if (!fileInfo.Exists)
70 {
71 Messaging.Instance.OnMessage(WixErrors.CannotFindFile(file.File.SourceLineNumbers, file.File.File, file.File.FileName, file.WixFile.Source));
72 return;
73 }
74
75 using (FileStream fileStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read))
76 {
77 if (Int32.MaxValue < fileStream.Length)
78 {
79 throw new WixException(WixErrors.FileTooLarge(file.File.SourceLineNumbers, file.WixFile.Source));
80 }
81
82 file.File.FileSize = Convert.ToInt32(fileStream.Length, CultureInfo.InvariantCulture);
83 }
84
85 string version = null;
86 string language = null;
87 try
88 {
89 Installer.GetFileVersion(fileInfo.FullName, out version, out language);
90 }
91 catch (Win32Exception e)
92 {
93 if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND
94 {
95 throw new WixException(WixErrors.FileNotFound(file.File.SourceLineNumbers, fileInfo.FullName));
96 }
97 else
98 {
99 throw new WixException(WixErrors.Win32Exception(e.NativeErrorCode, e.Message));
100 }
101 }
102
103 // If there is no version, it is assumed there is no language because it won't matter in the versioning of the install.
104 if (String.IsNullOrEmpty(version)) // unversioned files have their hashes added to the MsiFileHash table
105 {
106 if (!this.OverwriteHash)
107 {
108 // not overwriting hash, so don't do the rest of these options.
109 }
110 else if (null != file.File.Version)
111 {
112 // Search all of the file rows available to see if the specified version is actually a companion file. Yes, this looks
113 // 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.
114 // That's a reasonable thought but companion file usage is usually pretty rare so we'd be doing something expensive (indexing
115 // all the file rows) for a relatively uncommon situation. Let's not do that.
116 //
117 // Also, if we do not find a matching file identifier then the user provided a default version and is providing a version
118 // for unversioned file. That's allowed but generally a dangerous thing to do so let's point that out to the user.
119 if (!this.FileFacades.Any(r => file.File.Version.Equals(r.File.File, StringComparison.Ordinal)))
120 {
121 Messaging.Instance.OnMessage(WixWarnings.DefaultVersionUsedForUnversionedFile(file.File.SourceLineNumbers, file.File.Version, file.File.File));
122 }
123 }
124 else
125 {
126 if (null != file.File.Language)
127 {
128 Messaging.Instance.OnMessage(WixWarnings.DefaultLanguageUsedForUnversionedFile(file.File.SourceLineNumbers, file.File.Language, file.File.File));
129 }
130
131 int[] hash;
132 try
133 {
134 Installer.GetFileHash(fileInfo.FullName, 0, out hash);
135 }
136 catch (Win32Exception e)
137 {
138 if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND
139 {
140 throw new WixException(WixErrors.FileNotFound(file.File.SourceLineNumbers, fileInfo.FullName));
141 }
142 else
143 {
144 throw new WixException(WixErrors.Win32Exception(e.NativeErrorCode, fileInfo.FullName, e.Message));
145 }
146 }
147
148 if (null == file.Hash)
149 {
150 Table msiFileHashTable = this.Output.EnsureTable(this.TableDefinitions["MsiFileHash"]);
151 file.Hash = msiFileHashTable.CreateRow(file.File.SourceLineNumbers);
152 }
153
154 file.Hash[0] = file.File.File;
155 file.Hash[1] = 0;
156 file.Hash[2] = hash[0];
157 file.Hash[3] = hash[1];
158 file.Hash[4] = hash[2];
159 file.Hash[5] = hash[3];
160 }
161 }
162 else // update the file row with the version and language information.
163 {
164 // If no version was provided by the user, use the version from the file itself.
165 // This is the most common case.
166 if (String.IsNullOrEmpty(file.File.Version))
167 {
168 file.File.Version = version;
169 }
170 else if (!this.FileFacades.Any(r => file.File.Version.Equals(r.File.File, StringComparison.Ordinal))) // this looks expensive, but see explanation below.
171 {
172 // The user provided a default version for the file row so we looked for a companion file (a file row with Id matching
173 // the version value). We didn't find it so, we will override the default version they provided with the actual
174 // version from the file itself. Now, I know it looks expensive to search through all the file rows trying to match
175 // on the Id. However, the alternative is to build a big index of all file rows to do look ups. Since this case
176 // where the file version is already present is rare (companion files are pretty uncommon), we'll do the more
177 // CPU intensive search to save on the memory intensive index that wouldn't be used much.
178 //
179 // Also note this case can occur when the file is being updated using the WixBindUpdatedFiles extension mechanism.
180 // That's typically even more rare than companion files so again, no index, just search.
181 file.File.Version = version;
182 }
183
184 if (!String.IsNullOrEmpty(file.File.Language) && String.IsNullOrEmpty(language))
185 {
186 Messaging.Instance.OnMessage(WixWarnings.DefaultLanguageUsedForVersionedFile(file.File.SourceLineNumbers, file.File.Language, file.File.File));
187 }
188 else // override the default provided by the user (usually nothing) with the actual language from the file itself.
189 {
190 file.File.Language = language;
191 }
192
193 // Populate the binder variables for this file information if requested.
194 if (null != this.VariableCache)
195 {
196 if (!String.IsNullOrEmpty(file.File.Version))
197 {
198 string key = String.Format(CultureInfo.InvariantCulture, "fileversion.{0}", BindDatabaseCommand.Demodularize(this.Output.Type, this.ModularizationGuid, file.File.File));
199 this.VariableCache[key] = file.File.Version;
200 }
201
202 if (!String.IsNullOrEmpty(file.File.Language))
203 {
204 string key = String.Format(CultureInfo.InvariantCulture, "filelanguage.{0}", BindDatabaseCommand.Demodularize(this.Output.Type, ModularizationGuid, file.File.File));
205 this.VariableCache[key] = file.File.Language;
206 }
207 }
208 }
209
210 // If this is a CLR assembly, load the assembly and get the assembly name information
211 if (FileAssemblyType.DotNetAssembly == file.WixFile.AssemblyType)
212 {
213 bool targetNetfx1 = false;
214 StringDictionary assemblyNameValues = new StringDictionary();
215
216 ClrInterop.IReferenceIdentity referenceIdentity = null;
217 Guid referenceIdentityGuid = ClrInterop.ReferenceIdentityGuid;
218 uint result = ClrInterop.GetAssemblyIdentityFromFile(fileInfo.FullName, ref referenceIdentityGuid, out referenceIdentity);
219 if (0 == result && null != referenceIdentity)
220 {
221 string imageRuntimeVersion = referenceIdentity.GetAttribute(null, "ImageRuntimeVersion");
222 if (null != imageRuntimeVersion)
223 {
224 targetNetfx1 = imageRuntimeVersion.StartsWith("v1", StringComparison.OrdinalIgnoreCase);
225 }
226
227 string culture = referenceIdentity.GetAttribute(null, "Culture") ?? "neutral";
228 assemblyNameValues.Add("Culture", culture);
229
230 string name = referenceIdentity.GetAttribute(null, "Name");
231 if (null != name)
232 {
233 assemblyNameValues.Add("Name", name);
234 }
235
236 string processorArchitecture = referenceIdentity.GetAttribute(null, "ProcessorArchitecture");
237 if (null != processorArchitecture)
238 {
239 assemblyNameValues.Add("ProcessorArchitecture", processorArchitecture);
240 }
241
242 string publicKeyToken = referenceIdentity.GetAttribute(null, "PublicKeyToken");
243 if (null != publicKeyToken)
244 {
245 bool publicKeyIsNeutral = (String.Equals(publicKeyToken, "neutral", StringComparison.OrdinalIgnoreCase));
246
247 // Managed code expects "null" instead of "neutral", and
248 // this won't be installed to the GAC since it's not signed anyway.
249 assemblyNameValues.Add("publicKeyToken", publicKeyIsNeutral ? "null" : publicKeyToken.ToUpperInvariant());
250 assemblyNameValues.Add("publicKeyTokenPreservedCase", publicKeyIsNeutral ? "null" : publicKeyToken);
251 }
252 else if (file.WixFile.AssemblyApplication == null)
253 {
254 throw new WixException(WixErrors.GacAssemblyNoStrongName(file.File.SourceLineNumbers, fileInfo.FullName, file.File.Component));
255 }
256
257 string assemblyVersion = referenceIdentity.GetAttribute(null, "Version");
258 if (null != version)
259 {
260 assemblyNameValues.Add("Version", assemblyVersion);
261 }
262 }
263 else
264 {
265 Messaging.Instance.OnMessage(WixErrors.InvalidAssemblyFile(file.File.SourceLineNumbers, fileInfo.FullName, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", result)));
266 return;
267 }
268
269 Table assemblyNameTable = this.Output.EnsureTable(this.TableDefinitions["MsiAssemblyName"]);
270 if (assemblyNameValues.ContainsKey("name"))
271 {
272 this.SetMsiAssemblyName(assemblyNameTable, file, "name", assemblyNameValues["name"]);
273 }
274
275 if (!String.IsNullOrEmpty(version))
276 {
277 this.SetMsiAssemblyName(assemblyNameTable, file, "fileVersion", version);
278 }
279
280 if (assemblyNameValues.ContainsKey("version"))
281 {
282 string assemblyVersion = assemblyNameValues["version"];
283
284 if (!targetNetfx1)
285 {
286 // There is a bug in v1 fusion that requires the assembly's "version" attribute
287 // to be equal to or longer than the "fileVersion" in length when its present;
288 // the workaround is to prepend zeroes to the last version number in the assembly
289 // version.
290 if (null != version && version.Length > assemblyVersion.Length)
291 {
292 string padding = new string('0', version.Length - assemblyVersion.Length);
293 string[] assemblyVersionNumbers = assemblyVersion.Split('.');
294
295 if (assemblyVersionNumbers.Length > 0)
296 {
297 assemblyVersionNumbers[assemblyVersionNumbers.Length - 1] = String.Concat(padding, assemblyVersionNumbers[assemblyVersionNumbers.Length - 1]);
298 assemblyVersion = String.Join(".", assemblyVersionNumbers);
299 }
300 }
301 }
302
303 this.SetMsiAssemblyName(assemblyNameTable, file, "version", assemblyVersion);
304 }
305
306 if (assemblyNameValues.ContainsKey("culture"))
307 {
308 this.SetMsiAssemblyName(assemblyNameTable, file, "culture", assemblyNameValues["culture"]);
309 }
310
311 if (assemblyNameValues.ContainsKey("publicKeyToken"))
312 {
313 this.SetMsiAssemblyName(assemblyNameTable, file, "publicKeyToken", assemblyNameValues["publicKeyToken"]);
314 }
315
316 if (!String.IsNullOrEmpty(file.WixFile.ProcessorArchitecture))
317 {
318 this.SetMsiAssemblyName(assemblyNameTable, file, "processorArchitecture", file.WixFile.ProcessorArchitecture);
319 }
320
321 if (assemblyNameValues.ContainsKey("processorArchitecture"))
322 {
323 this.SetMsiAssemblyName(assemblyNameTable, file, "processorArchitecture", assemblyNameValues["processorArchitecture"]);
324 }
325
326 // add the assembly name to the information cache
327 if (null != this.VariableCache)
328 {
329 string fileId = BindDatabaseCommand.Demodularize(this.Output.Type, this.ModularizationGuid, file.File.File);
330 string key = String.Concat("assemblyfullname.", fileId);
331 string assemblyName = String.Concat(assemblyNameValues["name"], ", version=", assemblyNameValues["version"], ", culture=", assemblyNameValues["culture"], ", publicKeyToken=", String.IsNullOrEmpty(assemblyNameValues["publicKeyToken"]) ? "null" : assemblyNameValues["publicKeyToken"]);
332 if (assemblyNameValues.ContainsKey("processorArchitecture"))
333 {
334 assemblyName = String.Concat(assemblyName, ", processorArchitecture=", assemblyNameValues["processorArchitecture"]);
335 }
336
337 this.VariableCache[key] = assemblyName;
338
339 // Add entries with the preserved case publicKeyToken
340 string pcAssemblyNameKey = String.Concat("assemblyfullnamepreservedcase.", fileId);
341 this.VariableCache[pcAssemblyNameKey] = (assemblyNameValues["publicKeyToken"] == assemblyNameValues["publicKeyTokenPreservedCase"]) ? assemblyName : assemblyName.Replace(assemblyNameValues["publicKeyToken"], assemblyNameValues["publicKeyTokenPreservedCase"]);
342
343 string pcPublicKeyTokenKey = String.Concat("assemblypublickeytokenpreservedcase.", fileId);
344 this.VariableCache[pcPublicKeyTokenKey] = assemblyNameValues["publicKeyTokenPreservedCase"];
345 }
346 }
347 else if (FileAssemblyType.Win32Assembly == file.WixFile.AssemblyType)
348 {
349 // TODO: Consider passing in the this.FileFacades as an indexed collection instead of searching through
350 // all files like this. Even though this is a rare case it looks like we might be able to index the
351 // file earlier.
352 FileFacade fileManifest = this.FileFacades.SingleOrDefault(r => r.File.File.Equals(file.WixFile.AssemblyManifest, StringComparison.Ordinal));
353 if (null == fileManifest)
354 {
355 Messaging.Instance.OnMessage(WixErrors.MissingManifestForWin32Assembly(file.File.SourceLineNumbers, file.File.File, file.WixFile.AssemblyManifest));
356 }
357
358 string win32Type = null;
359 string win32Name = null;
360 string win32Version = null;
361 string win32ProcessorArchitecture = null;
362 string win32PublicKeyToken = null;
363
364 // loading the dom is expensive we want more performant APIs than the DOM
365 // Navigator is cheaper than dom. Perhaps there is a cheaper API still.
366 try
367 {
368 XPathDocument doc = new XPathDocument(fileManifest.WixFile.Source);
369 XPathNavigator nav = doc.CreateNavigator();
370 nav.MoveToRoot();
371
372 // this assumes a particular schema for a win32 manifest and does not
373 // provide error checking if the file does not conform to schema.
374 // The fallback case here is that nothing is added to the MsiAssemblyName
375 // table for an out of tolerance Win32 manifest. Perhaps warnings needed.
376 if (nav.MoveToFirstChild())
377 {
378 while (nav.NodeType != XPathNodeType.Element || nav.Name != "assembly")
379 {
380 nav.MoveToNext();
381 }
382
383 if (nav.MoveToFirstChild())
384 {
385 bool hasNextSibling = true;
386 while (nav.NodeType != XPathNodeType.Element || nav.Name != "assemblyIdentity" && hasNextSibling)
387 {
388 hasNextSibling = nav.MoveToNext();
389 }
390 if (!hasNextSibling)
391 {
392 Messaging.Instance.OnMessage(WixErrors.InvalidManifestContent(file.File.SourceLineNumbers, fileManifest.WixFile.Source));
393 return;
394 }
395
396 if (nav.MoveToAttribute("type", String.Empty))
397 {
398 win32Type = nav.Value;
399 nav.MoveToParent();
400 }
401
402 if (nav.MoveToAttribute("name", String.Empty))
403 {
404 win32Name = nav.Value;
405 nav.MoveToParent();
406 }
407
408 if (nav.MoveToAttribute("version", String.Empty))
409 {
410 win32Version = nav.Value;
411 nav.MoveToParent();
412 }
413
414 if (nav.MoveToAttribute("processorArchitecture", String.Empty))
415 {
416 win32ProcessorArchitecture = nav.Value;
417 nav.MoveToParent();
418 }
419
420 if (nav.MoveToAttribute("publicKeyToken", String.Empty))
421 {
422 win32PublicKeyToken = nav.Value;
423 nav.MoveToParent();
424 }
425 }
426 }
427 }
428 catch (FileNotFoundException fe)
429 {
430 Messaging.Instance.OnMessage(WixErrors.FileNotFound(new SourceLineNumber(fileManifest.WixFile.Source), fe.FileName, "AssemblyManifest"));
431 }
432 catch (XmlException xe)
433 {
434 Messaging.Instance.OnMessage(WixErrors.InvalidXml(new SourceLineNumber(fileManifest.WixFile.Source), "manifest", xe.Message));
435 }
436
437 Table assemblyNameTable = this.Output.EnsureTable(this.TableDefinitions["MsiAssemblyName"]);
438 if (!String.IsNullOrEmpty(win32Name))
439 {
440 this.SetMsiAssemblyName(assemblyNameTable, file, "name", win32Name);
441 }
442
443 if (!String.IsNullOrEmpty(win32Version))
444 {
445 this.SetMsiAssemblyName(assemblyNameTable, file, "version", win32Version);
446 }
447
448 if (!String.IsNullOrEmpty(win32Type))
449 {
450 this.SetMsiAssemblyName(assemblyNameTable, file, "type", win32Type);
451 }
452
453 if (!String.IsNullOrEmpty(win32ProcessorArchitecture))
454 {
455 this.SetMsiAssemblyName(assemblyNameTable, file, "processorArchitecture", win32ProcessorArchitecture);
456 }
457
458 if (!String.IsNullOrEmpty(win32PublicKeyToken))
459 {
460 this.SetMsiAssemblyName(assemblyNameTable, file, "publicKeyToken", win32PublicKeyToken);
461 }
462 }
463 }
464
465 /// <summary>
466 /// Set an MsiAssemblyName row. If it was directly authored, override the value, otherwise
467 /// create a new row.
468 /// </summary>
469 /// <param name="assemblyNameTable">MsiAssemblyName table.</param>
470 /// <param name="file">FileFacade containing the assembly read for the MsiAssemblyName row.</param>
471 /// <param name="name">MsiAssemblyName name.</param>
472 /// <param name="value">MsiAssemblyName value.</param>
473 private void SetMsiAssemblyName(Table assemblyNameTable, FileFacade file, string name, string value)
474 {
475 // check for null value (this can occur when grabbing the file version from an assembly without one)
476 if (String.IsNullOrEmpty(value))
477 {
478 Messaging.Instance.OnMessage(WixWarnings.NullMsiAssemblyNameValue(file.File.SourceLineNumbers, file.File.Component, name));
479 }
480 else
481 {
482 Row assemblyNameRow = null;
483
484 // override directly authored value
485 foreach (Row row in assemblyNameTable.Rows)
486 {
487 if ((string)row[0] == file.File.Component && (string)row[1] == name)
488 {
489 assemblyNameRow = row;
490 break;
491 }
492 }
493
494 // 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.
495 if ("name" == name && FileAssemblyType.DotNetAssembly == file.WixFile.AssemblyType &&
496 String.IsNullOrEmpty(file.WixFile.AssemblyApplication) &&
497 !String.Equals(Path.GetFileNameWithoutExtension(file.File.LongFileName), value, StringComparison.OrdinalIgnoreCase))
498 {
499 Messaging.Instance.OnMessage(WixErrors.GACAssemblyIdentityWarning(file.File.SourceLineNumbers, Path.GetFileNameWithoutExtension(file.File.LongFileName), value));
500 }
501
502 if (null == assemblyNameRow)
503 {
504 assemblyNameRow = assemblyNameTable.CreateRow(file.File.SourceLineNumbers);
505 assemblyNameRow[0] = file.File.Component;
506 assemblyNameRow[1] = name;
507 assemblyNameRow[2] = value;
508
509 // put the MsiAssemblyName row in the same section as the related File row
510 assemblyNameRow.SectionId = file.File.SectionId;
511
512 if (null == file.AssemblyNames)
513 {
514 file.AssemblyNames = new List<Row>();
515 }
516
517 file.AssemblyNames.Add(assemblyNameRow);
518 }
519 else
520 {
521 assemblyNameRow[2] = value;
522 }
523
524 if (this.VariableCache != null)
525 {
526 string key = String.Format(CultureInfo.InvariantCulture, "assembly{0}.{1}", name, BindDatabaseCommand.Demodularize(this.Output.Type, this.ModularizationGuid, file.File.File)).ToLowerInvariant();
527 this.VariableCache[key] = (string)assemblyNameRow[2];
528 }
529 }
530 }
531 }
532}
diff --git a/src/WixToolset.Core/Bind/DelayedField.cs b/src/WixToolset.Core/Bind/DelayedField.cs
new file mode 100644
index 00000000..181ac3e3
--- /dev/null
+++ b/src/WixToolset.Core/Bind/DelayedField.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.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using System.Text;
9 using WixToolset.Data;
10
11 /// <summary>
12 /// Structure used to hold a row and field that contain binder variables, which need to be resolved
13 /// later, once the files have been resolved.
14 /// </summary>
15 internal class DelayedField
16 {
17 /// <summary>
18 /// Basic constructor for struct
19 /// </summary>
20 /// <param name="row">Row for the field.</param>
21 /// <param name="field">Field needing further resolution.</param>
22 public DelayedField(Row row, Field field)
23 {
24 this.Row = row;
25 this.Field = field;
26 }
27
28 /// <summary>
29 /// The row containing the field.
30 /// </summary>
31 public Row Row { get; private set; }
32
33 /// <summary>
34 /// The field needing further resolving.
35 /// </summary>
36 public Field Field { get; private set; }
37 }
38}
diff --git a/src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs b/src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs
new file mode 100644
index 00000000..0ecd0096
--- /dev/null
+++ b/src/WixToolset.Core/Bind/ExtractEmbeddedFiles.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.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 sealed class ExtractEmbeddedFiles
17 {
18 private Dictionary<Uri, SortedList<int, string>> filesWithEmbeddedFiles = new Dictionary<Uri, SortedList<int, string>>();
19
20 public IEnumerable<Uri> Uris { get { return 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="embeddedFileIndex">Index of the embedded file to extract.</param>
27 /// <param name="tempPath">Path where temporary files should be placed.</param>
28 /// <returns>The extract path for the embedded file.</returns>
29 public string AddEmbeddedFileIndex(Uri uri, int embeddedFileIndex, string tempPath)
30 {
31 string extractPath;
32 SortedList<int, string> extracts;
33
34 // If the uri to the file that contains the embedded file does not already have embedded files
35 // being extracted, create the dictionary to track that.
36 if (!filesWithEmbeddedFiles.TryGetValue(uri, out extracts))
37 {
38 extracts = new SortedList<int, string>();
39 filesWithEmbeddedFiles.Add(uri, extracts);
40 }
41
42 // If the embedded file is not already tracked in the dictionary of extracts, add it.
43 if (!extracts.TryGetValue(embeddedFileIndex, out extractPath))
44 {
45 string localFileNameWithoutExtension = Path.GetFileNameWithoutExtension(uri.LocalPath);
46 string unique = this.HashUri(uri.AbsoluteUri);
47 string extractedName = String.Format(CultureInfo.InvariantCulture, @"{0}_{1}\{2}", localFileNameWithoutExtension, unique, embeddedFileIndex);
48
49 extractPath = Path.Combine(tempPath, extractedName);
50 extracts.Add(embeddedFileIndex, extractPath);
51 }
52
53 return extractPath;
54 }
55
56 public IEnumerable<ExtractFile> GetExtractFilesForUri(Uri uri)
57 {
58 SortedList<int, string> extracts;
59 if (!filesWithEmbeddedFiles.TryGetValue(uri, out extracts))
60 {
61 extracts = new SortedList<int, string>();
62 }
63
64 return extracts.Select(e => new ExtractFile() { EmbeddedFileIndex = e.Key, OutputPath = e.Value });
65 }
66
67 private string HashUri(string uri)
68 {
69 using (SHA1 sha1 = new SHA1CryptoServiceProvider())
70 {
71 byte[] hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(uri));
72 return Convert.ToBase64String(hash).TrimEnd('=').Replace('+', '-').Replace('/', '_');
73 }
74 }
75
76 internal struct ExtractFile
77 {
78 public int EmbeddedFileIndex { get; set; }
79
80 public string OutputPath { get; set; }
81 }
82 }
83}
diff --git a/src/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs b/src/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs
new file mode 100644
index 00000000..68bfd8d7
--- /dev/null
+++ b/src/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.Bind
4{
5 using System.IO;
6 using System.Reflection;
7 using WixToolset.Data;
8
9 internal class ExtractEmbeddedFilesCommand : ICommand
10 {
11 public ExtractEmbeddedFiles FilesWithEmbeddedFiles { private get; set; }
12
13 public void Execute()
14 {
15 foreach (var baseUri in this.FilesWithEmbeddedFiles.Uris)
16 {
17 Stream stream = null;
18 try
19 {
20 // If the embedded files are stored in an assembly resource stream (usually
21 // a .wixlib embedded in a WixExtension).
22 if ("embeddedresource" == baseUri.Scheme)
23 {
24 string assemblyPath = Path.GetFullPath(baseUri.LocalPath);
25 string resourceName = baseUri.Fragment.TrimStart('#');
26
27 Assembly assembly = Assembly.LoadFile(assemblyPath);
28 stream = assembly.GetManifestResourceStream(resourceName);
29 }
30 else // normal file (usually a binary .wixlib on disk).
31 {
32 stream = File.OpenRead(baseUri.LocalPath);
33 }
34
35 using (FileStructure fs = FileStructure.Read(stream))
36 {
37 foreach (var embeddedFile in this.FilesWithEmbeddedFiles.GetExtractFilesForUri(baseUri))
38 {
39 fs.ExtractEmbeddedFile(embeddedFile.EmbeddedFileIndex, embeddedFile.OutputPath);
40 }
41 }
42 }
43 finally
44 {
45 if (null != stream)
46 {
47 stream.Close();
48 }
49 }
50 }
51 }
52 }
53}
diff --git a/src/WixToolset.Core/Bind/FileTransfer.cs b/src/WixToolset.Core/Bind/FileTransfer.cs
new file mode 100644
index 00000000..64bbc5f1
--- /dev/null
+++ b/src/WixToolset.Core/Bind/FileTransfer.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 WixToolset.Bind
4{
5 using System;
6 using System.IO;
7 using WixToolset;
8 using WixToolset.Data;
9
10 /// <summary>
11 /// Structure used for all file transfer information.
12 /// </summary>
13 internal class FileTransfer
14 {
15 /// <summary>Source path to file.</summary>
16 public string Source { get; set; }
17
18 /// <summary>Destination path for file.</summary>
19 public string Destination { get; set; }
20
21 /// <summary>Flag if file should be moved (optimal).</summary>
22 public bool Move { get; set; }
23
24 /// <summary>Optional source line numbers where this file transfer orginated.</summary>
25 public SourceLineNumber SourceLineNumbers { get; set; }
26
27 /// <summary>Optional type of file this transfer is moving or copying.</summary>
28 public string Type { get; set; }
29
30 /// <summary>Indicates whether the file transer was a built by this build or copied from other some build.</summary>
31 internal bool Built { get; set; }
32
33 /// <summary>Set during layout of media when the file transfer when the source and target resolve to the same path.</summary>
34 internal bool Redundant { get; set; }
35
36 /// <summary>
37 /// Prefer the TryCreate() method to create FileTransfer objects.
38 /// </summary>
39 /// <param name="source">Source path to file.</param>
40 /// <param name="destination">Destination path for file.</param>
41 /// <param name="move">File if file should be moved (optimal).</param>
42 /// <param name="type">Optional type of file this transfer is transferring.</param>
43 /// <param name="sourceLineNumbers">Optional source line numbers wher this transfer originated.</param>
44 public FileTransfer(string source, string destination, bool move, string type = null, SourceLineNumber sourceLineNumbers = null)
45 {
46 this.Source = source;
47 this.Destination = destination;
48 this.Move = move;
49
50 this.Type = type;
51 this.SourceLineNumbers = sourceLineNumbers;
52 }
53
54 /// <summary>
55 /// Creates a file transfer if the source and destination are different.
56 /// </summary>
57 /// <param name="source">Source path to file.</param>
58 /// <param name="destination">Destination path for file.</param>
59 /// <param name="move">File if file should be moved (optimal).</param>
60 /// <param name="type">Optional type of file this transfer is transferring.</param>
61 /// <param name="sourceLineNumbers">Optional source line numbers wher this transfer originated.</param>
62 /// <returns>true if the source and destination are the different, false if no file transfer is created.</returns>
63 public static bool TryCreate(string source, string destination, bool move, string type, SourceLineNumber sourceLineNumbers, out FileTransfer transfer)
64 {
65 string sourceFullPath = GetValidatedFullPath(sourceLineNumbers, source);
66
67 string fileLayoutFullPath = GetValidatedFullPath(sourceLineNumbers, destination);
68
69 // if the current source path (where we know that the file already exists) and the resolved
70 // path as dictated by the Directory table are not the same, then propagate the file. The
71 // image that we create may have already been done by some other process other than the linker, so
72 // there is no reason to copy the files to the resolved source if they are already there.
73 if (String.Equals(sourceFullPath, fileLayoutFullPath, StringComparison.OrdinalIgnoreCase))
74 {
75 transfer = null;
76 return false;
77 }
78
79 transfer = new FileTransfer(source, destination, move, type, sourceLineNumbers);
80 return true;
81 }
82
83 private static string GetValidatedFullPath(SourceLineNumber sourceLineNumbers, string path)
84 {
85 string result;
86
87 try
88 {
89 result = Path.GetFullPath(path);
90
91 string filename = Path.GetFileName(result);
92
93 foreach (string reservedName in Common.ReservedFileNames)
94 {
95 if (reservedName.Equals(filename, StringComparison.OrdinalIgnoreCase))
96 {
97 throw new WixException(WixErrors.InvalidFileName(sourceLineNumbers, path));
98 }
99 }
100 }
101 catch (System.ArgumentException)
102 {
103 throw new WixException(WixErrors.InvalidFileName(sourceLineNumbers, path));
104 }
105 catch (System.IO.PathTooLongException)
106 {
107 throw new WixException(WixErrors.PathTooLong(sourceLineNumbers, path));
108 }
109
110 return result;
111 }
112 }
113}
diff --git a/src/WixToolset.Core/Bind/GenerateDatabaseCommand.cs b/src/WixToolset.Core/Bind/GenerateDatabaseCommand.cs
new file mode 100644
index 00000000..fdf1ab32
--- /dev/null
+++ b/src/WixToolset.Core/Bind/GenerateDatabaseCommand.cs
@@ -0,0 +1,335 @@
1// Copyright (c) .NET Foundation and 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.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.Text;
11 using WixToolset.Data;
12 using WixToolset.Extensibility;
13 using WixToolset.Msi;
14 using WixToolset.Core.Native;
15
16 internal class GenerateDatabaseCommand : ICommand
17 {
18 public int Codepage { private get; set; }
19
20 public IEnumerable<IBinderExtension> Extensions { private get; set; }
21
22 public IEnumerable<IBinderFileManager> FileManagers { private get; set; }
23
24 /// <summary>
25 /// Whether to keep columns added in a transform.
26 /// </summary>
27 public bool KeepAddedColumns { private get; set; }
28
29 public Output Output { private get; set; }
30
31 public string OutputPath { private get; set; }
32
33 public TableDefinitionCollection TableDefinitions { private get; set; }
34
35 public string TempFilesLocation { private get; set; }
36
37 /// <summary>
38 /// Whether to use a subdirectory based on the <paramref name="databaseFile"/> file name for intermediate files.
39 /// </summary>
40 public bool SuppressAddingValidationRows { private get; set; }
41
42 public bool UseSubDirectory { private get; set; }
43
44 public void Execute()
45 {
46 // Add the _Validation rows.
47 if (!this.SuppressAddingValidationRows)
48 {
49 Table validationTable = this.Output.EnsureTable(this.TableDefinitions["_Validation"]);
50
51 foreach (Table table in this.Output.Tables)
52 {
53 if (!table.Definition.Unreal)
54 {
55 // Add the validation rows for this table.
56 table.Definition.AddValidationRows(validationTable);
57 }
58 }
59 }
60
61 // Set the base directory.
62 string baseDirectory = this.TempFilesLocation;
63
64 if (this.UseSubDirectory)
65 {
66 string filename = Path.GetFileNameWithoutExtension(this.OutputPath);
67 baseDirectory = Path.Combine(baseDirectory, filename);
68
69 // make sure the directory exists
70 Directory.CreateDirectory(baseDirectory);
71 }
72
73 try
74 {
75 OpenDatabase type = OpenDatabase.CreateDirect;
76
77 // set special flag for patch files
78 if (OutputType.Patch == this.Output.Type)
79 {
80 type |= OpenDatabase.OpenPatchFile;
81 }
82
83#if DEBUG
84 Console.WriteLine("Opening database at: {0}", this.OutputPath);
85#endif
86
87 using (Database db = new Database(this.OutputPath, type))
88 {
89 // Localize the codepage if a value was specified directly.
90 if (-1 != this.Codepage)
91 {
92 this.Output.Codepage = this.Codepage;
93 }
94
95 // if we're not using the default codepage, import a new one into our
96 // database before we add any tables (or the tables would be added
97 // with the wrong codepage).
98 if (0 != this.Output.Codepage)
99 {
100 this.SetDatabaseCodepage(db, this.Output.Codepage);
101 }
102
103 foreach (Table table in this.Output.Tables)
104 {
105 Table importTable = table;
106 bool hasBinaryColumn = false;
107
108 // Skip all unreal tables other than _Streams.
109 if (table.Definition.Unreal && "_Streams" != table.Name)
110 {
111 continue;
112 }
113
114 // Do not put the _Validation table in patches, it is not needed.
115 if (OutputType.Patch == this.Output.Type && "_Validation" == table.Name)
116 {
117 continue;
118 }
119
120 // The only way to import binary data is to copy it to a local subdirectory first.
121 // To avoid this extra copying and perf hit, import an empty table with the same
122 // definition and later import the binary data from source using records.
123 foreach (ColumnDefinition columnDefinition in table.Definition.Columns)
124 {
125 if (ColumnType.Object == columnDefinition.Type)
126 {
127 importTable = new Table(table.Section, table.Definition);
128 hasBinaryColumn = true;
129 break;
130 }
131 }
132
133 // Create the table via IDT import.
134 if ("_Streams" != importTable.Name)
135 {
136 try
137 {
138 db.ImportTable(this.Output.Codepage, importTable, baseDirectory, this.KeepAddedColumns);
139 }
140 catch (WixInvalidIdtException)
141 {
142 // If ValidateRows finds anything it doesn't like, it throws
143 importTable.ValidateRows();
144
145 // Otherwise we rethrow the InvalidIdt
146 throw;
147 }
148 }
149
150 // insert the rows via SQL query if this table contains object fields
151 if (hasBinaryColumn)
152 {
153 StringBuilder query = new StringBuilder("SELECT ");
154
155 // Build the query for the view.
156 bool firstColumn = true;
157 foreach (ColumnDefinition columnDefinition in table.Definition.Columns)
158 {
159 if (!firstColumn)
160 {
161 query.Append(",");
162 }
163
164 query.AppendFormat(" `{0}`", columnDefinition.Name);
165 firstColumn = false;
166 }
167 query.AppendFormat(" FROM `{0}`", table.Name);
168
169 using (View tableView = db.OpenExecuteView(query.ToString()))
170 {
171 // Import each row containing a stream
172 foreach (Row row in table.Rows)
173 {
174 using (Record record = new Record(table.Definition.Columns.Count))
175 {
176 StringBuilder streamName = new StringBuilder();
177 bool needStream = false;
178
179 // the _Streams table doesn't prepend the table name (or a period)
180 if ("_Streams" != table.Name)
181 {
182 streamName.Append(table.Name);
183 }
184
185 for (int i = 0; i < table.Definition.Columns.Count; i++)
186 {
187 ColumnDefinition columnDefinition = table.Definition.Columns[i];
188
189 switch (columnDefinition.Type)
190 {
191 case ColumnType.Localized:
192 case ColumnType.Preserved:
193 case ColumnType.String:
194 if (columnDefinition.PrimaryKey)
195 {
196 if (0 < streamName.Length)
197 {
198 streamName.Append(".");
199 }
200 streamName.Append((string)row[i]);
201 }
202
203 record.SetString(i + 1, (string)row[i]);
204 break;
205 case ColumnType.Number:
206 record.SetInteger(i + 1, Convert.ToInt32(row[i], CultureInfo.InvariantCulture));
207 break;
208 case ColumnType.Object:
209 if (null != row[i])
210 {
211 needStream = true;
212 try
213 {
214 record.SetStream(i + 1, (string)row[i]);
215 }
216 catch (Win32Exception e)
217 {
218 if (0xA1 == e.NativeErrorCode) // ERROR_BAD_PATHNAME
219 {
220 throw new WixException(WixErrors.FileNotFound(row.SourceLineNumbers, (string)row[i]));
221 }
222 else
223 {
224 throw new WixException(WixErrors.Win32Exception(e.NativeErrorCode, e.Message));
225 }
226 }
227 }
228 break;
229 }
230 }
231
232 // stream names are created by concatenating the name of the table with the values
233 // of the primary key (delimited by periods)
234 // check for a stream name that is more than 62 characters long (the maximum allowed length)
235 if (needStream && MsiInterop.MsiMaxStreamNameLength < streamName.Length)
236 {
237 Messaging.Instance.OnMessage(WixErrors.StreamNameTooLong(row.SourceLineNumbers, table.Name, streamName.ToString(), streamName.Length));
238 }
239 else // add the row to the database
240 {
241 tableView.Modify(ModifyView.Assign, record);
242 }
243 }
244 }
245 }
246
247 // Remove rows from the _Streams table for wixpdbs.
248 if ("_Streams" == table.Name)
249 {
250 table.Rows.Clear();
251 }
252 }
253 }
254
255 // Insert substorages (usually transforms inside a patch or instance transforms in a package).
256 if (0 < this.Output.SubStorages.Count)
257 {
258 using (View storagesView = new View(db, "SELECT `Name`, `Data` FROM `_Storages`"))
259 {
260 foreach (SubStorage subStorage in this.Output.SubStorages)
261 {
262 string transformFile = Path.Combine(this.TempFilesLocation, String.Concat(subStorage.Name, ".mst"));
263
264 // Bind the transform.
265 this.BindTransform(subStorage.Data, transformFile);
266
267 if (Messaging.Instance.EncounteredError)
268 {
269 continue;
270 }
271
272 // add the storage
273 using (Record record = new Record(2))
274 {
275 record.SetString(1, subStorage.Name);
276 record.SetStream(2, transformFile);
277 storagesView.Modify(ModifyView.Assign, record);
278 }
279 }
280 }
281 }
282
283 // We're good, commit the changes to the new database.
284 db.Commit();
285 }
286 }
287 catch (IOException)
288 {
289 // TODO: this error message doesn't seem specific enough
290 throw new WixFileNotFoundException(new SourceLineNumber(this.OutputPath), this.OutputPath);
291 }
292 }
293
294 private void BindTransform(Output transform, string outputPath)
295 {
296 BindTransformCommand command = new BindTransformCommand();
297 command.Extensions = this.Extensions;
298 command.FileManagers = this.FileManagers;
299 command.TempFilesLocation = this.TempFilesLocation;
300 command.Transform = transform;
301 command.OutputPath = outputPath;
302 command.TableDefinitions = this.TableDefinitions;
303 command.Execute();
304 }
305
306 /// <summary>
307 /// Sets the codepage of a database.
308 /// </summary>
309 /// <param name="db">Database to set codepage into.</param>
310 /// <param name="output">Output with the codepage for the database.</param>
311 private void SetDatabaseCodepage(Database db, int codepage)
312 {
313 // write out the _ForceCodepage IDT file
314 string idtPath = Path.Combine(this.TempFilesLocation, "_ForceCodepage.idt");
315 using (StreamWriter idtFile = new StreamWriter(idtPath, false, Encoding.ASCII))
316 {
317 idtFile.WriteLine(); // dummy column name record
318 idtFile.WriteLine(); // dummy column definition record
319 idtFile.Write(codepage);
320 idtFile.WriteLine("\t_ForceCodepage");
321 }
322
323 // try to import the table into the MSI
324 try
325 {
326 db.Import(idtPath);
327 }
328 catch (WixInvalidIdtException)
329 {
330 // the IDT should be valid, so an invalid code page was given
331 throw new WixException(WixErrors.IllegalCodepage(codepage));
332 }
333 }
334 }
335}
diff --git a/src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs b/src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs
new file mode 100644
index 00000000..4ffe9e82
--- /dev/null
+++ b/src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.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.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.Linq;
9 using System.Text;
10 using WixToolset.Data;
11
12 /// <summary>
13 /// Resolves the fields which had variables that needed to be resolved after the file information
14 /// was loaded.
15 /// </summary>
16 internal class ResolveDelayedFieldsCommand : ICommand
17 {
18 public OutputType OutputType { private get; set;}
19
20 public IEnumerable<DelayedField> DelayedFields { private get; set;}
21
22 public IDictionary<string, string> VariableCache { private get; set; }
23
24 public string ModularizationGuid { private get; set; }
25
26 /// <param name="output">Internal representation of the msi database to operate upon.</param>
27 /// <param name="delayedFields">The fields which had resolution delayed.</param>
28 /// <param name="variableCache">The file information to use when resolving variables.</param>
29 /// <param name="modularizationGuid">The modularization guid (used in case of a merge module).</param>
30 public void Execute()
31 {
32 List<DelayedField> deferredFields = new List<DelayedField>();
33
34 foreach (DelayedField delayedField in this.DelayedFields)
35 {
36 try
37 {
38 Row propertyRow = delayedField.Row;
39
40 // process properties first in case they refer to other binder variables
41 if ("Property" == propertyRow.Table.Name)
42 {
43 string value = WixVariableResolver.ResolveDelayedVariables(propertyRow.SourceLineNumbers, (string)delayedField.Field.Data, this.VariableCache);
44
45 // update the variable cache with the new value
46 string key = String.Concat("property.", BindDatabaseCommand.Demodularize(this.OutputType, this.ModularizationGuid, (string)propertyRow[0]));
47 this.VariableCache[key] = value;
48
49 // update the field data
50 delayedField.Field.Data = value;
51 }
52 else
53 {
54 deferredFields.Add(delayedField);
55 }
56 }
57 catch (WixException we)
58 {
59 Messaging.Instance.OnMessage(we.Error);
60 continue;
61 }
62 }
63
64 // add specialization for ProductVersion fields
65 string keyProductVersion = "property.ProductVersion";
66 if (this.VariableCache.ContainsKey(keyProductVersion))
67 {
68 string value = this.VariableCache[keyProductVersion];
69 Version productVersion = null;
70
71 try
72 {
73 productVersion = new Version(value);
74
75 // Don't add the variable if it already exists (developer defined a property with the same name).
76 string fieldKey = String.Concat(keyProductVersion, ".Major");
77 if (!this.VariableCache.ContainsKey(fieldKey))
78 {
79 this.VariableCache[fieldKey] = productVersion.Major.ToString(CultureInfo.InvariantCulture);
80 }
81
82 fieldKey = String.Concat(keyProductVersion, ".Minor");
83 if (!this.VariableCache.ContainsKey(fieldKey))
84 {
85 this.VariableCache[fieldKey] = productVersion.Minor.ToString(CultureInfo.InvariantCulture);
86 }
87
88 fieldKey = String.Concat(keyProductVersion, ".Build");
89 if (!this.VariableCache.ContainsKey(fieldKey))
90 {
91 this.VariableCache[fieldKey] = productVersion.Build.ToString(CultureInfo.InvariantCulture);
92 }
93
94 fieldKey = String.Concat(keyProductVersion, ".Revision");
95 if (!this.VariableCache.ContainsKey(fieldKey))
96 {
97 this.VariableCache[fieldKey] = productVersion.Revision.ToString(CultureInfo.InvariantCulture);
98 }
99 }
100 catch
101 {
102 // Ignore the error introduced by new behavior.
103 }
104 }
105
106 // process the remaining fields in case they refer to property binder variables
107 foreach (DelayedField delayedField in deferredFields)
108 {
109 try
110 {
111 delayedField.Field.Data = WixVariableResolver.ResolveDelayedVariables(delayedField.Row.SourceLineNumbers, (string)delayedField.Field.Data, this.VariableCache);
112 }
113 catch (WixException we)
114 {
115 Messaging.Instance.OnMessage(we.Error);
116 continue;
117 }
118 }
119 }
120 }
121}
diff --git a/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs b/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs
new file mode 100644
index 00000000..4caec9b4
--- /dev/null
+++ b/src/WixToolset.Core/Bind/ResolveFieldsCommand.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.Bind
4{
5 using System.Collections.Generic;
6 using WixToolset.Data;
7 using WixToolset.Extensibility;
8
9 /// <summary>
10 /// Resolve source fields in the tables included in the output
11 /// </summary>
12 internal class ResolveFieldsCommand : ICommand
13 {
14 public TableIndexedCollection Tables { private get; set; }
15
16 public ExtractEmbeddedFiles FilesWithEmbeddedFiles { private get; set; }
17
18 public BinderFileManagerCore FileManagerCore { private get; set; }
19
20 public IEnumerable<IBinderFileManager> FileManagers { private get; set; }
21
22 public bool SupportDelayedResolution { private get; set; }
23
24 public string TempFilesLocation { private get; set; }
25
26 public WixVariableResolver WixVariableResolver { private get; set; }
27
28 public IEnumerable<DelayedField> DelayedFields { get; private set; }
29
30 public void Execute()
31 {
32 List<DelayedField> delayedFields = this.SupportDelayedResolution ? new List<DelayedField>() : null;
33
34 foreach (Table table in this.Tables)
35 {
36 foreach (Row row in table.Rows)
37 {
38 foreach (Field field in row.Fields)
39 {
40 bool isDefault = true;
41 bool delayedResolve = false;
42
43 // Check to make sure we're in a scenario where we can handle variable resolution.
44 if (null != delayedFields)
45 {
46 // resolve localization and wix variables
47 if (field.Data is string)
48 {
49 field.Data = this.WixVariableResolver.ResolveVariables(row.SourceLineNumbers, field.AsString(), false, ref isDefault, ref delayedResolve);
50 if (delayedResolve)
51 {
52 delayedFields.Add(new DelayedField(row, field));
53 }
54 }
55 }
56
57 // Move to next row if we've hit an error resolving variables.
58 if (Messaging.Instance.EncounteredError) // TODO: make this error handling more specific to just the failure to resolve variables in this field.
59 {
60 continue;
61 }
62
63 // Resolve file paths
64 if (ColumnType.Object == field.Column.Type)
65 {
66 ObjectField objectField = (ObjectField)field;
67
68 // Skip file resolution if the file is to be deleted.
69 if (RowOperation.Delete == row.Operation)
70 {
71 continue;
72 }
73
74 // File is embedded and path to it was not modified above.
75 if (objectField.EmbeddedFileIndex.HasValue && isDefault)
76 {
77 string extractPath = this.FilesWithEmbeddedFiles.AddEmbeddedFileIndex(objectField.BaseUri, objectField.EmbeddedFileIndex.Value, this.TempFilesLocation);
78
79 // Set the path to the embedded file once where it will be extracted.
80 objectField.Data = extractPath;
81 }
82 else if (null != objectField.Data) // non-compressed file (or localized value)
83 {
84 try
85 {
86 if (OutputType.Patch != this.FileManagerCore.Output.Type) // Normal binding for non-Patch scenario such as link (light.exe)
87 {
88 // keep a copy of the un-resolved data for future replay. This will be saved into wixpdb file
89 if (null == objectField.UnresolvedData)
90 {
91 objectField.UnresolvedData = (string)objectField.Data;
92 }
93
94 // resolve the path to the file
95 objectField.Data = this.ResolveFile((string)objectField.Data, table.Name, row.SourceLineNumbers, BindStage.Normal);
96 }
97 else if (!(this.FileManagerCore.RebaseTarget || this.FileManagerCore.RebaseUpdated)) // Normal binding for Patch Scenario (normal patch, no re-basing logic)
98 {
99 // resolve the path to the file
100 objectField.Data = this.ResolveFile((string)objectField.Data, table.Name, row.SourceLineNumbers, BindStage.Normal);
101 }
102 else // Re-base binding path scenario caused by pyro.exe -bt -bu
103 {
104 // by default, use the resolved Data for file lookup
105 string filePathToResolve = (string)objectField.Data;
106
107 // if -bu is used in pyro command, this condition holds true and the tool
108 // will use pre-resolved source for new wixpdb file
109 if (this.FileManagerCore.RebaseUpdated)
110 {
111 // try to use the unResolved Source if it exists.
112 // New version of wixpdb file keeps a copy of pre-resolved Source. i.e. !(bindpath.test)\foo.dll
113 // Old version of winpdb file does not contain this attribute and the value is null.
114 if (null != objectField.UnresolvedData)
115 {
116 filePathToResolve = objectField.UnresolvedData;
117 }
118 }
119
120 objectField.Data = this.ResolveFile(filePathToResolve, table.Name, row.SourceLineNumbers, BindStage.Updated);
121 }
122 }
123 catch (WixFileNotFoundException)
124 {
125 // display the error with source line information
126 Messaging.Instance.OnMessage(WixErrors.FileNotFound(row.SourceLineNumbers, (string)objectField.Data));
127 }
128 }
129
130 isDefault = true;
131 if (null != objectField.PreviousData)
132 {
133 objectField.PreviousData = this.WixVariableResolver.ResolveVariables(row.SourceLineNumbers, objectField.PreviousData, false, ref isDefault);
134 if (!Messaging.Instance.EncounteredError) // TODO: make this error handling more specific to just the failure to resolve variables in this field.
135 {
136 // file is compressed in a cabinet (and not modified above)
137 if (objectField.PreviousEmbeddedFileIndex.HasValue && isDefault)
138 {
139 // when loading transforms from disk, PreviousBaseUri may not have been set
140 if (null == objectField.PreviousBaseUri)
141 {
142 objectField.PreviousBaseUri = objectField.BaseUri;
143 }
144
145 string extractPath = this.FilesWithEmbeddedFiles.AddEmbeddedFileIndex(objectField.PreviousBaseUri, objectField.PreviousEmbeddedFileIndex.Value, this.TempFilesLocation);
146
147 // set the path to the file once its extracted from the cabinet
148 objectField.PreviousData = extractPath;
149 }
150 else if (null != objectField.PreviousData) // non-compressed file (or localized value)
151 {
152 try
153 {
154 if (!this.FileManagerCore.RebaseTarget && !this.FileManagerCore.RebaseUpdated)
155 {
156 // resolve the path to the file
157 objectField.PreviousData = this.ResolveFile((string)objectField.PreviousData, table.Name, row.SourceLineNumbers, BindStage.Normal);
158 }
159 else
160 {
161 if (this.FileManagerCore.RebaseTarget)
162 {
163 // if -bt is used, it come here
164 // Try to use the original unresolved source from either target build or update build
165 // If both target and updated are of old wixpdb, it behaves the same as today, no re-base logic here
166 // If target is old version and updated is new version, it uses unresolved path from updated build
167 // If both target and updated are of new versions, it uses unresolved path from target build
168 if (null != objectField.UnresolvedPreviousData || null != objectField.UnresolvedData)
169 {
170 objectField.PreviousData = objectField.UnresolvedPreviousData ?? objectField.UnresolvedData;
171 }
172 }
173
174 // resolve the path to the file
175 objectField.PreviousData = this.ResolveFile((string)objectField.PreviousData, table.Name, row.SourceLineNumbers, BindStage.Target);
176
177 }
178 }
179 catch (WixFileNotFoundException)
180 {
181 // display the error with source line information
182 Messaging.Instance.OnMessage(WixErrors.FileNotFound(row.SourceLineNumbers, (string)objectField.PreviousData));
183 }
184 }
185 }
186 }
187 }
188 }
189 }
190 }
191
192 this.DelayedFields = delayedFields;
193 }
194
195 private string ResolveFile(string source, string type, SourceLineNumber sourceLineNumbers, BindStage bindStage = BindStage.Normal)
196 {
197 string path = null;
198 foreach (IBinderFileManager fileManager in this.FileManagers)
199 {
200 path = fileManager.ResolveFile(source, type, sourceLineNumbers, bindStage);
201 if (null != path)
202 {
203 break;
204 }
205 }
206
207 if (null == path)
208 {
209 throw new WixFileNotFoundException(sourceLineNumbers, source, type);
210 }
211
212 return path;
213 }
214 }
215}
diff --git a/src/WixToolset.Core/Bind/ResolvedDirectory.cs b/src/WixToolset.Core/Bind/ResolvedDirectory.cs
new file mode 100644
index 00000000..6985f95d
--- /dev/null
+++ b/src/WixToolset.Core/Bind/ResolvedDirectory.cs
@@ -0,0 +1,31 @@
1// Copyright (c) .NET Foundation and 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.Bind
4{
5 /// <summary>
6 /// Structure used for resolved directory information.
7 /// </summary>
8 internal struct ResolvedDirectory
9 {
10 /// <summary>The directory parent.</summary>
11 public string DirectoryParent;
12
13 /// <summary>The name of this directory.</summary>
14 public string Name;
15
16 /// <summary>The path of this directory.</summary>
17 public string Path;
18
19 /// <summary>
20 /// Constructor for ResolvedDirectory.
21 /// </summary>
22 /// <param name="directoryParent">Parent directory.</param>
23 /// <param name="name">The directory name.</param>
24 public ResolvedDirectory(string directoryParent, string name)
25 {
26 this.DirectoryParent = directoryParent;
27 this.Name = name;
28 this.Path = null;
29 }
30 }
31}
diff --git a/src/WixToolset.Core/Bind/TransferFilesCommand.cs b/src/WixToolset.Core/Bind/TransferFilesCommand.cs
new file mode 100644
index 00000000..719b8b20
--- /dev/null
+++ b/src/WixToolset.Core/Bind/TransferFilesCommand.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.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Security.AccessControl;
9 using WixToolset.Data;
10 using WixToolset.Extensibility;
11
12 internal class TransferFilesCommand : ICommand
13 {
14 public IEnumerable<IBinderFileManager> FileManagers { private get; set; }
15
16 public IEnumerable<FileTransfer> FileTransfers { private get; set; }
17
18 public bool SuppressAclReset { private get; set; }
19
20 public void Execute()
21 {
22 List<string> destinationFiles = new List<string>();
23
24 foreach (FileTransfer fileTransfer in this.FileTransfers)
25 {
26 string fileSource = this.ResolveFile(fileTransfer.Source, fileTransfer.Type, fileTransfer.SourceLineNumbers, BindStage.Normal);
27
28 // If the source and destination are identical, then there's nothing to do here
29 if (0 == String.Compare(fileSource, fileTransfer.Destination, StringComparison.OrdinalIgnoreCase))
30 {
31 fileTransfer.Redundant = true;
32 continue;
33 }
34
35 bool retry = false;
36 do
37 {
38 try
39 {
40 if (fileTransfer.Move)
41 {
42 Messaging.Instance.OnMessage(WixVerboses.MoveFile(fileSource, fileTransfer.Destination));
43 this.TransferFile(true, fileSource, fileTransfer.Destination);
44 }
45 else
46 {
47 Messaging.Instance.OnMessage(WixVerboses.CopyFile(fileSource, fileTransfer.Destination));
48 this.TransferFile(false, fileSource, fileTransfer.Destination);
49 }
50
51 retry = false;
52 destinationFiles.Add(fileTransfer.Destination);
53 }
54 catch (FileNotFoundException e)
55 {
56 throw new WixFileNotFoundException(e.FileName);
57 }
58 catch (DirectoryNotFoundException)
59 {
60 // if we already retried, give up
61 if (retry)
62 {
63 throw;
64 }
65
66 string directory = Path.GetDirectoryName(fileTransfer.Destination);
67 Messaging.Instance.OnMessage(WixVerboses.CreateDirectory(directory));
68 Directory.CreateDirectory(directory);
69 retry = true;
70 }
71 catch (UnauthorizedAccessException)
72 {
73 // if we already retried, give up
74 if (retry)
75 {
76 throw;
77 }
78
79 if (File.Exists(fileTransfer.Destination))
80 {
81 Messaging.Instance.OnMessage(WixVerboses.RemoveDestinationFile(fileTransfer.Destination));
82
83 // try to ensure the file is not read-only
84 FileAttributes attributes = File.GetAttributes(fileTransfer.Destination);
85 try
86 {
87 File.SetAttributes(fileTransfer.Destination, attributes & ~FileAttributes.ReadOnly);
88 }
89 catch (ArgumentException) // thrown for unauthorized access errors
90 {
91 throw new WixException(WixErrors.UnauthorizedAccess(fileTransfer.Destination));
92 }
93
94 // try to delete the file
95 try
96 {
97 File.Delete(fileTransfer.Destination);
98 }
99 catch (IOException)
100 {
101 throw new WixException(WixErrors.FileInUse(null, fileTransfer.Destination));
102 }
103
104 retry = true;
105 }
106 else // no idea what just happened, bail
107 {
108 throw;
109 }
110 }
111 catch (IOException)
112 {
113 // if we already retried, give up
114 if (retry)
115 {
116 throw;
117 }
118
119 if (File.Exists(fileTransfer.Destination))
120 {
121 Messaging.Instance.OnMessage(WixVerboses.RemoveDestinationFile(fileTransfer.Destination));
122
123 // ensure the file is not read-only, then delete it
124 FileAttributes attributes = File.GetAttributes(fileTransfer.Destination);
125 File.SetAttributes(fileTransfer.Destination, attributes & ~FileAttributes.ReadOnly);
126 try
127 {
128 File.Delete(fileTransfer.Destination);
129 }
130 catch (IOException)
131 {
132 throw new WixException(WixErrors.FileInUse(null, fileTransfer.Destination));
133 }
134
135 retry = true;
136 }
137 else // no idea what just happened, bail
138 {
139 throw;
140 }
141 }
142 } while (retry);
143 }
144
145 // Finally, if there were any files remove the ACL that may have been added to
146 // during the file transfer process.
147 if (0 < destinationFiles.Count && !this.SuppressAclReset)
148 {
149 var aclReset = new FileSecurity();
150 aclReset.SetAccessRuleProtection(false, false);
151
152 try
153 {
154 //WixToolset.Core.Native.NativeMethods.ResetAcls(destinationFiles.ToArray(), (uint)destinationFiles.Count);
155
156 foreach (var file in destinationFiles)
157 {
158 new FileInfo(file).SetAccessControl(aclReset);
159 }
160 }
161 catch
162 {
163 Messaging.Instance.OnMessage(WixWarnings.UnableToResetAcls());
164 }
165 }
166 }
167
168 private string ResolveFile(string source, string type, SourceLineNumber sourceLineNumbers, BindStage bindStage)
169 {
170 string path = null;
171 foreach (IBinderFileManager fileManager in this.FileManagers)
172 {
173 path = fileManager.ResolveFile(source, type, sourceLineNumbers, bindStage);
174 if (null != path)
175 {
176 break;
177 }
178 }
179
180 if (null == path)
181 {
182 throw new WixFileNotFoundException(sourceLineNumbers, source, type);
183 }
184
185 return path;
186 }
187
188 private void TransferFile(bool move, string source, string destination)
189 {
190 bool complete = false;
191 foreach (IBinderFileManager fileManager in this.FileManagers)
192 {
193 if (move)
194 {
195 complete = fileManager.MoveFile(source, destination, true);
196 }
197 else
198 {
199 complete = fileManager.CopyFile(source, destination, true);
200 }
201
202 if (complete)
203 {
204 break;
205 }
206 }
207
208 if (!complete)
209 {
210 throw new InvalidOperationException(); // TODO: something needs to be said here that none of the binder file managers returned a result.
211 }
212 }
213 }
214}
diff --git a/src/WixToolset.Core/Binder.cs b/src/WixToolset.Core/Binder.cs
new file mode 100644
index 00000000..18ad2d62
--- /dev/null
+++ b/src/WixToolset.Core/Binder.cs
@@ -0,0 +1,686 @@
1// Copyright (c) .NET Foundation and 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.Collections;
7 using System.Collections.Generic;
8 using System.Diagnostics;
9 using System.Globalization;
10 using System.IO;
11 using System.Linq;
12 using System.Reflection;
13 using WixToolset.Bind;
14 using WixToolset.Data;
15 using WixToolset.Data.Rows;
16 using WixToolset.Extensibility;
17 using WixToolset.Msi;
18
19 // TODO: (4.0) Refactor so that these don't need to be copied.
20 // Copied verbatim from ext\UtilExtension\wixext\UtilCompiler.cs
21 [Flags]
22 internal enum WixFileSearchAttributes
23 {
24 Default = 0x001,
25 MinVersionInclusive = 0x002,
26 MaxVersionInclusive = 0x004,
27 MinSizeInclusive = 0x008,
28 MaxSizeInclusive = 0x010,
29 MinDateInclusive = 0x020,
30 MaxDateInclusive = 0x040,
31 WantVersion = 0x080,
32 WantExists = 0x100,
33 IsDirectory = 0x200,
34 }
35
36 [Flags]
37 internal enum WixRegistrySearchAttributes
38 {
39 Raw = 0x01,
40 Compatible = 0x02,
41 ExpandEnvironmentVariables = 0x04,
42 WantValue = 0x08,
43 WantExists = 0x10,
44 Win64 = 0x20,
45 }
46
47 internal enum WixComponentSearchAttributes
48 {
49 KeyPath = 0x1,
50 State = 0x2,
51 WantDirectory = 0x4,
52 }
53
54 [Flags]
55 internal enum WixProductSearchAttributes
56 {
57 Version = 0x1,
58 Language = 0x2,
59 State = 0x4,
60 Assignment = 0x8,
61 UpgradeCode = 0x10,
62 }
63
64 /// <summary>
65 /// Binder of the WiX toolset.
66 /// </summary>
67 public sealed class Binder
68 {
69 private BinderCore core;
70 private BinderFileManagerCore fileManagerCore;
71 private List<IBinderExtension> extensions;
72 private List<IBinderFileManager> fileManagers;
73 private List<InspectorExtension> inspectorExtensions;
74
75 public Binder()
76 {
77 this.DefaultCompressionLevel = CompressionLevel.High;
78
79 this.BindPaths = new List<BindPath>();
80 this.TargetBindPaths = new List<BindPath>();
81 this.UpdatedBindPaths = new List<BindPath>();
82
83 this.extensions = new List<IBinderExtension>();
84 this.fileManagers = new List<IBinderFileManager>();
85 this.inspectorExtensions = new List<InspectorExtension>();
86
87 this.Ices = new List<string>();
88 this.SuppressIces = new List<string>();
89 }
90
91 public string ContentsFile { private get; set; }
92
93 public string OutputsFile { private get; set; }
94
95 public string BuiltOutputsFile { private get; set; }
96
97 public string WixprojectFile { private get; set; }
98
99 /// <summary>
100 /// Gets the list of bindpaths.
101 /// </summary>
102 public List<BindPath> BindPaths { get; private set; }
103
104 /// <summary>
105 /// Gets the list of target bindpaths.
106 /// </summary>
107 public List<BindPath> TargetBindPaths { get; private set; }
108
109 /// <summary>
110 /// Gets the list of updated bindpaths.
111 /// </summary>
112 public List<BindPath> UpdatedBindPaths { get; private set; }
113
114 /// <summary>
115 /// Gets or sets the option to enable building binary delta patches.
116 /// </summary>
117 /// <value>The option to enable building binary delta patches.</value>
118 public bool DeltaBinaryPatch { get; set; }
119
120 /// <summary>
121 /// Gets or sets the cabinet cache location.
122 /// </summary>
123 public string CabCachePath { get; set; }
124
125 /// <summary>
126 /// Gets or sets the number of threads to use for cabinet creation.
127 /// </summary>
128 /// <value>The number of threads to use for cabinet creation.</value>
129 public int CabbingThreadCount { get; set; }
130
131 /// <summary>
132 /// Gets or sets the default compression level to use for cabinets
133 /// that don't have their compression level explicitly set.
134 /// </summary>
135 public CompressionLevel DefaultCompressionLevel { get; set; }
136
137 /// <summary>
138 /// Gets and sets the location to save the WixPdb.
139 /// </summary>
140 /// <value>The location in which to save the WixPdb. Null if the the WixPdb should not be output.</value>
141 public string PdbFile { get; set; }
142
143 public List<string> Ices { get; private set; }
144
145 public List<string> SuppressIces { get; private set; }
146
147 /// <summary>
148 /// Gets and sets the option to suppress resetting ACLs by the binder.
149 /// </summary>
150 /// <value>The option to suppress resetting ACLs by the binder.</value>
151 public bool SuppressAclReset { get; set; }
152
153 /// <summary>
154 /// Gets and sets the option to suppress creating an image for MSI/MSM.
155 /// </summary>
156 /// <value>The option to suppress creating an image for MSI/MSM.</value>
157 public bool SuppressLayout { get; set; }
158
159 /// <summary>
160 /// Gets and sets the option to suppress MSI/MSM validation.
161 /// </summary>
162 /// <value>The option to suppress MSI/MSM validation.</value>
163 /// <remarks>This must be set before calling Bind.</remarks>
164 public bool SuppressValidation { get; set; }
165
166 /// <summary>
167 /// Gets and sets the option to suppress adding _Validation table rows.
168 /// </summary>
169 public bool SuppressAddingValidationRows { get; set; }
170
171 /// <summary>
172 /// Gets or sets the localizer.
173 /// </summary>
174 /// <value>The localizer.</value>
175 public Localizer Localizer { get; set; }
176
177 /// <summary>
178 /// Gets or sets the temporary path for the Binder. If left null, the binder
179 /// will use %TEMP% environment variable.
180 /// </summary>
181 /// <value>Path to temp files.</value>
182 public string TempFilesLocation { get; set; }
183
184 /// <summary>
185 /// Gets or sets the Wix variable resolver.
186 /// </summary>
187 /// <value>The Wix variable resolver.</value>
188 public WixVariableResolver WixVariableResolver { get; set; }
189
190 /// <summary>
191 /// Add a binder extension.
192 /// </summary>
193 /// <param name="extension">New extension.</param>
194 public void AddExtension(IBinderExtension extension)
195 {
196 this.extensions.Add(extension);
197 }
198
199 /// <summary>
200 /// Add a file manager extension.
201 /// </summary>
202 /// <param name="extension">New file manager.</param>
203 public void AddExtension(IBinderFileManager extension)
204 {
205 this.fileManagers.Add(extension);
206 }
207
208 /// <summary>
209 /// Binds an output.
210 /// </summary>
211 /// <param name="output">The output to bind.</param>
212 /// <param name="file">The Windows Installer file to create.</param>
213 /// <remarks>The Binder.DeleteTempFiles method should be called after calling this method.</remarks>
214 /// <returns>true if binding completed successfully; false otherwise</returns>
215 public bool Bind(Output output, string file)
216 {
217 // Ensure the cabinet cache path exists if we are going to use it.
218 if (!String.IsNullOrEmpty(this.CabCachePath))
219 {
220 Directory.CreateDirectory(this.CabCachePath);
221 }
222
223 this.fileManagerCore = new BinderFileManagerCore();
224 this.fileManagerCore.CabCachePath = this.CabCachePath;
225 this.fileManagerCore.Output = output;
226 this.fileManagerCore.TempFilesLocation = this.TempFilesLocation;
227 this.fileManagerCore.AddBindPaths(this.BindPaths, BindStage.Normal);
228 this.fileManagerCore.AddBindPaths(this.TargetBindPaths, BindStage.Target);
229 this.fileManagerCore.AddBindPaths(this.UpdatedBindPaths, BindStage.Updated);
230 foreach (IBinderFileManager fileManager in this.fileManagers)
231 {
232 fileManager.Core = this.fileManagerCore;
233 }
234
235 this.core = new BinderCore();
236 this.core.FileManagerCore = this.fileManagerCore;
237
238 this.WriteBuildInfoTable(output, file);
239
240 // Initialize extensions.
241 foreach (IBinderExtension extension in this.extensions)
242 {
243 extension.Core = this.core;
244
245 extension.Initialize(output);
246 }
247
248 // Gather all the wix variables.
249 Table wixVariableTable = output.Tables["WixVariable"];
250 if (null != wixVariableTable)
251 {
252 foreach (WixVariableRow wixVariableRow in wixVariableTable.Rows)
253 {
254 this.WixVariableResolver.AddVariable(wixVariableRow);
255 }
256 }
257
258 IEnumerable<FileTransfer> fileTransfers = null;
259 IEnumerable<string> contentPaths = null;
260
261 switch (output.Type)
262 {
263 case OutputType.Bundle:
264 this.BindBundle(output, file, out fileTransfers, out contentPaths);
265 break;
266
267 case OutputType.Transform:
268 this.BindTransform(output, file);
269 break;
270
271 default:
272 this.BindDatabase(output, file, out fileTransfers, out contentPaths);
273 break;
274 }
275
276
277 // Layout media
278 try
279 {
280 this.LayoutMedia(fileTransfers);
281 }
282 finally
283 {
284 if (!String.IsNullOrEmpty(this.ContentsFile) && contentPaths != null)
285 {
286 this.CreateContentsFile(this.ContentsFile, contentPaths);
287 }
288
289 if (!String.IsNullOrEmpty(this.OutputsFile) && fileTransfers != null)
290 {
291 this.CreateOutputsFile(this.OutputsFile, fileTransfers, this.PdbFile);
292 }
293
294 if (!String.IsNullOrEmpty(this.BuiltOutputsFile) && fileTransfers != null)
295 {
296 this.CreateBuiltOutputsFile(this.BuiltOutputsFile, fileTransfers, this.PdbFile);
297 }
298 }
299
300 this.core = null;
301
302 return Messaging.Instance.EncounteredError;
303 }
304
305 /// <summary>
306 /// Does any housekeeping after Bind.
307 /// </summary>
308 /// <param name="tidy">Whether or not any actual tidying should be done.</param>
309 public void Cleanup(bool tidy)
310 {
311 if (tidy)
312 {
313 if (!this.DeleteTempFiles())
314 {
315 this.core.OnMessage(WixWarnings.FailedToDeleteTempDir(this.TempFilesLocation));
316 }
317 }
318 else
319 {
320 this.core.OnMessage(WixVerboses.BinderTempDirLocatedAt(this.TempFilesLocation));
321 }
322 }
323
324 /// <summary>
325 /// Cleans up the temp files used by the Binder.
326 /// </summary>
327 /// <returns>True if all files were deleted, false otherwise.</returns>
328 private bool DeleteTempFiles()
329 {
330 bool deleted = Common.DeleteTempFiles(this.TempFilesLocation, this.core);
331 return deleted;
332 }
333
334 /// <summary>
335 /// Populates the WixBuildInfo table in an output.
336 /// </summary>
337 /// <param name="output">The output.</param>
338 /// <param name="databaseFile">The output file if OutputFile not set.</param>
339 private void WriteBuildInfoTable(Output output, string outputFile)
340 {
341 Table buildInfoTable = output.EnsureTable(this.core.TableDefinitions["WixBuildInfo"]);
342 Row buildInfoRow = buildInfoTable.CreateRow(null);
343
344 Assembly executingAssembly = Assembly.GetExecutingAssembly();
345 FileVersionInfo fileVersion = FileVersionInfo.GetVersionInfo(executingAssembly.Location);
346 buildInfoRow[0] = fileVersion.FileVersion;
347 buildInfoRow[1] = outputFile;
348
349 if (!String.IsNullOrEmpty(this.WixprojectFile))
350 {
351 buildInfoRow[2] = this.WixprojectFile;
352 }
353
354 if (!String.IsNullOrEmpty(this.PdbFile))
355 {
356 buildInfoRow[3] = this.PdbFile;
357 }
358 }
359
360 /// <summary>
361 /// Binds a bundle.
362 /// </summary>
363 /// <param name="bundle">The bundle to bind.</param>
364 /// <param name="bundleFile">The bundle to create.</param>
365 private void BindBundle(Output bundle, string bundleFile, out IEnumerable<FileTransfer> fileTransfers, out IEnumerable<string> contentPaths)
366 {
367 BindBundleCommand command = new BindBundleCommand();
368 command.DefaultCompressionLevel = this.DefaultCompressionLevel;
369 command.Extensions = this.extensions;
370 command.FileManagerCore = this.fileManagerCore;
371 command.FileManagers = this.fileManagers;
372 command.Output = bundle;
373 command.OutputPath = bundleFile;
374 command.PdbFile = this.PdbFile;
375 command.TableDefinitions = this.core.TableDefinitions;
376 command.TempFilesLocation = this.TempFilesLocation;
377 command.WixVariableResolver = this.WixVariableResolver;
378 command.Execute();
379
380 fileTransfers = command.FileTransfers;
381 contentPaths = command.ContentFilePaths;
382 }
383
384 /// <summary>
385 /// Binds a databse.
386 /// </summary>
387 /// <param name="output">The output to bind.</param>
388 /// <param name="databaseFile">The database file to create.</param>
389 private void BindDatabase(Output output, string databaseFile, out IEnumerable<FileTransfer> fileTransfers, out IEnumerable<string> contentPaths)
390 {
391 Validator validator = null;
392
393 // tell the binder about the validator if validation isn't suppressed
394 if (!this.SuppressValidation && (OutputType.Module == output.Type || OutputType.Product == output.Type))
395 {
396 validator = new Validator();
397 validator.TempFilesLocation = Path.Combine(this.TempFilesLocation, "validate");
398
399 // set the default cube file
400 string lightDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
401 string cubePath = (OutputType.Module == output.Type) ? Path.Combine(lightDirectory, "mergemod.cub") : Path.Combine(lightDirectory, "darice.cub");
402 validator.AddCubeFile(cubePath);
403
404 // by default, disable ICEs that have equivalent-or-better checks in WiX
405 this.SuppressIces.Add("ICE08");
406 this.SuppressIces.Add("ICE33");
407 this.SuppressIces.Add("ICE47");
408 this.SuppressIces.Add("ICE66");
409
410 // set the ICEs
411 validator.ICEs = this.Ices.ToArray();
412
413 // set the suppressed ICEs
414 validator.SuppressedICEs = this.SuppressIces.ToArray();
415 }
416
417 BindDatabaseCommand command = new BindDatabaseCommand();
418 command.CabbingThreadCount = this.CabbingThreadCount;
419 command.Codepage = this.Localizer == null ? -1 : this.Localizer.Codepage;
420 command.DefaultCompressionLevel = this.DefaultCompressionLevel;
421 command.Extensions = this.extensions;
422 command.FileManagerCore = this.fileManagerCore;
423 command.FileManagers = this.fileManagers;
424 command.InspectorExtensions = this.inspectorExtensions;
425 command.Localizer = this.Localizer;
426 command.PdbFile = this.PdbFile;
427 command.Output = output;
428 command.OutputPath = databaseFile;
429 command.SuppressAddingValidationRows = this.SuppressAddingValidationRows;
430 command.SuppressLayout = this.SuppressLayout;
431 command.TableDefinitions = this.core.TableDefinitions;
432 command.TempFilesLocation = this.TempFilesLocation;
433 command.Validator = validator;
434 command.WixVariableResolver = this.WixVariableResolver;
435 command.Execute();
436
437 fileTransfers = command.FileTransfers;
438 contentPaths = command.ContentFilePaths;
439 }
440
441 /// <summary>
442 /// Binds a transform.
443 /// </summary>
444 /// <param name="transform">The transform to bind.</param>
445 /// <param name="outputPath">The transform to create.</param>
446 private void BindTransform(Output transform, string outputPath)
447 {
448 BindTransformCommand command = new BindTransformCommand();
449 command.Extensions = this.extensions;
450 command.FileManagers = this.fileManagers;
451 command.TableDefinitions = this.core.TableDefinitions;
452 command.TempFilesLocation = this.TempFilesLocation;
453 command.Transform = transform;
454 command.OutputPath = outputPath;
455 command.Execute();
456 }
457
458 /// <summary>
459 /// Final step in binding that transfers (moves/copies) all files generated into the appropriate
460 /// location in the source image
461 /// </summary>
462 /// <param name="fileTransfers">List of files to transfer.</param>
463 private void LayoutMedia(IEnumerable<FileTransfer> transfers)
464 {
465 if (null != transfers && transfers.Any())
466 {
467 this.core.OnMessage(WixVerboses.LayingOutMedia());
468
469 TransferFilesCommand command = new TransferFilesCommand();
470 command.FileManagers = this.fileManagers;
471 command.FileTransfers = transfers;
472 command.SuppressAclReset = this.SuppressAclReset;
473 command.Execute();
474 }
475 }
476
477 /// <summary>
478 /// Get the source path of a directory.
479 /// </summary>
480 /// <param name="directories">All cached directories.</param>
481 /// <param name="componentIdGenSeeds">Hash table of Component GUID generation seeds indexed by directory id.</param>
482 /// <param name="directory">Directory identifier.</param>
483 /// <param name="canonicalize">Canonicalize the path for standard directories.</param>
484 /// <returns>Source path of a directory.</returns>
485 internal static string GetDirectoryPath(Hashtable directories, Hashtable componentIdGenSeeds, string directory, bool canonicalize)
486 {
487 if (!directories.Contains(directory))
488 {
489 throw new WixException(WixErrors.ExpectedDirectory(directory));
490 }
491
492 ResolvedDirectory resolvedDirectory = (ResolvedDirectory)directories[directory];
493
494 if (null == resolvedDirectory.Path)
495 {
496 if (null != componentIdGenSeeds && componentIdGenSeeds.Contains(directory))
497 {
498 resolvedDirectory.Path = (string)componentIdGenSeeds[directory];
499 }
500 else if (canonicalize && WindowsInstallerStandard.IsStandardDirectory(directory))
501 {
502 // when canonicalization is on, standard directories are treated equally
503 resolvedDirectory.Path = directory;
504 }
505 else
506 {
507 string name = resolvedDirectory.Name;
508
509 if (canonicalize && null != name)
510 {
511 name = name.ToLower(CultureInfo.InvariantCulture);
512 }
513
514 if (String.IsNullOrEmpty(resolvedDirectory.DirectoryParent))
515 {
516 resolvedDirectory.Path = name;
517 }
518 else
519 {
520 string parentPath = GetDirectoryPath(directories, componentIdGenSeeds, resolvedDirectory.DirectoryParent, canonicalize);
521
522 if (null != resolvedDirectory.Name)
523 {
524 resolvedDirectory.Path = Path.Combine(parentPath, name);
525 }
526 else
527 {
528 resolvedDirectory.Path = parentPath;
529 }
530 }
531 }
532 }
533
534 return resolvedDirectory.Path;
535 }
536
537 /// <summary>
538 /// Gets the source path of a file.
539 /// </summary>
540 /// <param name="directories">All cached directories in <see cref="ResolvedDirectory"/>.</param>
541 /// <param name="directoryId">Parent directory identifier.</param>
542 /// <param name="fileName">File name (in long|source format).</param>
543 /// <param name="compressed">Specifies the package is compressed.</param>
544 /// <param name="useLongName">Specifies the package uses long file names.</param>
545 /// <returns>Source path of file relative to package directory.</returns>
546 internal static string GetFileSourcePath(Hashtable directories, string directoryId, string fileName, bool compressed, bool useLongName)
547 {
548 string fileSourcePath = Installer.GetName(fileName, true, useLongName);
549
550 if (compressed)
551 {
552 // Use just the file name of the file since all uncompressed files must appear
553 // in the root of the image in a compressed package.
554 }
555 else
556 {
557 // Get the relative path of where we want the file to be layed out as specified
558 // in the Directory table.
559 string directoryPath = Binder.GetDirectoryPath(directories, null, directoryId, false);
560 fileSourcePath = Path.Combine(directoryPath, fileSourcePath);
561 }
562
563 // Strip off "SourceDir" if it's still on there.
564 if (fileSourcePath.StartsWith("SourceDir\\", StringComparison.Ordinal))
565 {
566 fileSourcePath = fileSourcePath.Substring(10);
567 }
568
569 return fileSourcePath;
570 }
571
572 /// <summary>
573 /// Writes the paths to the content files included in the package to a text file.
574 /// </summary>
575 /// <param name="path">Path to write file.</param>
576 /// <param name="contentFilePaths">Collection of paths to content files that will be written to file.</param>
577 private void CreateContentsFile(string path, IEnumerable<string> contentFilePaths)
578 {
579 string directory = Path.GetDirectoryName(path);
580 if (!Directory.Exists(directory))
581 {
582 Directory.CreateDirectory(directory);
583 }
584
585 using (StreamWriter contents = new StreamWriter(path, false))
586 {
587 foreach (string contentPath in contentFilePaths)
588 {
589 contents.WriteLine(contentPath);
590 }
591 }
592 }
593
594 /// <summary>
595 /// Writes the paths to the content files included in the bundle to a text file.
596 /// </summary>
597 /// <param name="path">Path to write file.</param>
598 /// <param name="payloads">Collection of payloads whose source will be written to file.</param>
599 private void CreateContentsFile(string path, IEnumerable<WixBundlePayloadRow> payloads)
600 {
601 string directory = Path.GetDirectoryName(path);
602 if (!Directory.Exists(directory))
603 {
604 Directory.CreateDirectory(directory);
605 }
606
607 using (StreamWriter contents = new StreamWriter(path, false))
608 {
609 foreach (WixBundlePayloadRow payload in payloads)
610 {
611 if (payload.ContentFile)
612 {
613 contents.WriteLine(payload.FullFileName);
614 }
615 }
616 }
617 }
618
619 /// <summary>
620 /// Writes the paths to the output files to a text file.
621 /// </summary>
622 /// <param name="path">Path to write file.</param>
623 /// <param name="fileTransfers">Collection of files that were transferred to the output directory.</param>
624 /// <param name="pdbPath">Optional path to created .wixpdb.</param>
625 private void CreateOutputsFile(string path, IEnumerable<FileTransfer> fileTransfers, string pdbPath)
626 {
627 string directory = Path.GetDirectoryName(path);
628 if (!Directory.Exists(directory))
629 {
630 Directory.CreateDirectory(directory);
631 }
632
633 using (StreamWriter outputs = new StreamWriter(path, false))
634 {
635 foreach (FileTransfer fileTransfer in fileTransfers)
636 {
637 // Don't list files where the source is the same as the destination since
638 // that might be the only place the file exists. The outputs file is often
639 // used to delete stuff and losing the original source would be bad.
640 if (!fileTransfer.Redundant)
641 {
642 outputs.WriteLine(fileTransfer.Destination);
643 }
644 }
645
646 if (!String.IsNullOrEmpty(pdbPath))
647 {
648 outputs.WriteLine(Path.GetFullPath(pdbPath));
649 }
650 }
651 }
652
653 /// <summary>
654 /// Writes the paths to the built output files to a text file.
655 /// </summary>
656 /// <param name="path">Path to write file.</param>
657 /// <param name="fileTransfers">Collection of files that were transferred to the output directory.</param>
658 /// <param name="pdbPath">Optional path to created .wixpdb.</param>
659 private void CreateBuiltOutputsFile(string path, IEnumerable<FileTransfer> fileTransfers, string pdbPath)
660 {
661 string directory = Path.GetDirectoryName(path);
662 if (!Directory.Exists(directory))
663 {
664 Directory.CreateDirectory(directory);
665 }
666
667 using (StreamWriter outputs = new StreamWriter(path, false))
668 {
669 foreach (FileTransfer fileTransfer in fileTransfers)
670 {
671 // Only write the built file transfers. Also, skip redundant
672 // files for the same reason spelled out in this.CreateOutputsFile().
673 if (fileTransfer.Built && !fileTransfer.Redundant)
674 {
675 outputs.WriteLine(fileTransfer.Destination);
676 }
677 }
678
679 if (!String.IsNullOrEmpty(pdbPath))
680 {
681 outputs.WriteLine(Path.GetFullPath(pdbPath));
682 }
683 }
684 }
685 }
686}
diff --git a/src/WixToolset.Core/BinderCore.cs b/src/WixToolset.Core/BinderCore.cs
new file mode 100644
index 00000000..0feae0b2
--- /dev/null
+++ b/src/WixToolset.Core/BinderCore.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 WixToolset
4{
5 using WixToolset.Data;
6 using WixToolset.Extensibility;
7
8 /// <summary>
9 /// Core class for the binder.
10 /// </summary>
11 internal class BinderCore : IBinderCore
12 {
13 /// <summary>
14 /// Constructor for binder core.
15 /// </summary>
16 internal BinderCore()
17 {
18 this.TableDefinitions = new TableDefinitionCollection(WindowsInstallerStandard.GetTableDefinitions());
19 }
20
21 public IBinderFileManagerCore FileManagerCore { get; set; }
22
23 /// <summary>
24 /// Gets whether the binder core encountered an error while processing.
25 /// </summary>
26 /// <value>Flag if core encountered an error during processing.</value>
27 public bool EncounteredError
28 {
29 get { return Messaging.Instance.EncounteredError; }
30 }
31
32 /// <summary>
33 /// Gets the table definitions used by the Binder.
34 /// </summary>
35 /// <value>Table definitions used by the binder.</value>
36 public TableDefinitionCollection TableDefinitions { get; private set; }
37
38 /// <summary>
39 /// Generate an identifier by hashing data from the row.
40 /// </summary>
41 /// <param name="prefix">Three letter or less prefix for generated row identifier.</param>
42 /// <param name="args">Information to hash.</param>
43 /// <returns>The generated identifier.</returns>
44 public string CreateIdentifier(string prefix, params string[] args)
45 {
46 return Common.GenerateIdentifier(prefix, args);
47 }
48
49 /// <summary>
50 /// Sends a message to the message delegate if there is one.
51 /// </summary>
52 /// <param name="mea">Message event arguments.</param>
53 public void OnMessage(MessageEventArgs e)
54 {
55 Messaging.Instance.OnMessage(e);
56 }
57 }
58}
diff --git a/src/WixToolset.Core/BinderFileManager.cs b/src/WixToolset.Core/BinderFileManager.cs
new file mode 100644
index 00000000..0da54002
--- /dev/null
+++ b/src/WixToolset.Core/BinderFileManager.cs
@@ -0,0 +1,370 @@
1// Copyright (c) .NET Foundation and 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.Collections.Generic;
7 using System.Diagnostics.CodeAnalysis;
8 using System.IO;
9 using System.Linq;
10 using System.Runtime.InteropServices;
11 using WixToolset.Data;
12 using WixToolset.Data.Rows;
13 using WixToolset.Extensibility;
14
15 /// <summary>
16 /// Base class for creating a binder file manager.
17 /// </summary>
18 public class BinderFileManager : IBinderFileManager
19 {
20 /// <summary>
21 /// Gets or sets the file manager core.
22 /// </summary>
23 public IBinderFileManagerCore Core { get; set; }
24
25 /// <summary>
26 /// Compares two files to determine if they are equivalent.
27 /// </summary>
28 /// <param name="targetFile">The target file.</param>
29 /// <param name="updatedFile">The updated file.</param>
30 /// <returns>true if the files are equal; false otherwise.</returns>
31 public virtual bool? CompareFiles(string targetFile, string updatedFile)
32 {
33 FileInfo targetFileInfo = new FileInfo(targetFile);
34 FileInfo updatedFileInfo = new FileInfo(updatedFile);
35
36 if (targetFileInfo.Length != updatedFileInfo.Length)
37 {
38 return false;
39 }
40
41 using (FileStream targetStream = File.OpenRead(targetFile))
42 {
43 using (FileStream updatedStream = File.OpenRead(updatedFile))
44 {
45 if (targetStream.Length != updatedStream.Length)
46 {
47 return false;
48 }
49
50 // Using a larger buffer than the default buffer of 4 * 1024 used by FileStream.ReadByte improves performance.
51 // The buffer size is based on user feedback. Based on performance results, a better buffer size may be determined.
52 byte[] targetBuffer = new byte[16 * 1024];
53 byte[] updatedBuffer = new byte[16 * 1024];
54 int targetReadLength;
55 int updatedReadLength;
56 do
57 {
58 targetReadLength = targetStream.Read(targetBuffer, 0, targetBuffer.Length);
59 updatedReadLength = updatedStream.Read(updatedBuffer, 0, updatedBuffer.Length);
60
61 if (targetReadLength != updatedReadLength)
62 {
63 return false;
64 }
65
66 for (int i = 0; i < targetReadLength; ++i)
67 {
68 if (targetBuffer[i] != updatedBuffer[i])
69 {
70 return false;
71 }
72 }
73
74 } while (0 < targetReadLength);
75 }
76 }
77
78 return true;
79 }
80
81 /// <summary>
82 /// Resolves the source path of a file.
83 /// </summary>
84 /// <param name="source">Original source value.</param>
85 /// <param name="type">Optional type of source file being resolved.</param>
86 /// <param name="sourceLineNumbers">Optional source line of source file being resolved.</param>
87 /// <param name="bindStage">The binding stage used to determine what collection of bind paths will be used</param>
88 /// <returns>Should return a valid path for the stream to be imported.</returns>
89 public virtual string ResolveFile(string source, string type, SourceLineNumber sourceLineNumbers, BindStage bindStage)
90 {
91 if (String.IsNullOrEmpty(source))
92 {
93 throw new ArgumentNullException("source");
94 }
95
96 if (BinderFileManager.CheckFileExists(source)) // if the file exists, we're good to go.
97 {
98 return source;
99 }
100 else if (Path.IsPathRooted(source)) // path is rooted so bindpaths won't help, bail since the file apparently doesn't exist.
101 {
102 return null;
103 }
104 else // not a rooted path so let's try applying all the different source resolution options.
105 {
106 const string bindPathOpenString = "!(bindpath.";
107
108 string bindName = String.Empty;
109 string path = source;
110 string pathWithoutSourceDir = null;
111
112 if (source.StartsWith(bindPathOpenString, StringComparison.Ordinal))
113 {
114 int closeParen = source.IndexOf(')', bindPathOpenString.Length);
115 if (-1 != closeParen)
116 {
117 bindName = source.Substring(bindPathOpenString.Length, closeParen - bindPathOpenString.Length);
118 path = source.Substring(bindPathOpenString.Length + bindName.Length + 1); // +1 for the closing brace.
119 path = path.TrimStart('\\'); // remove starting '\\' char so the path doesn't look rooted.
120 }
121 }
122 else if (source.StartsWith("SourceDir\\", StringComparison.Ordinal) || source.StartsWith("SourceDir/", StringComparison.Ordinal))
123 {
124 pathWithoutSourceDir = path.Substring(10);
125 }
126
127 var bindPaths = this.Core.GetBindPaths(bindStage, bindName);
128 foreach (string bindPath in bindPaths)
129 {
130 string filePath;
131 if (!String.IsNullOrEmpty(pathWithoutSourceDir))
132 {
133 filePath = Path.Combine(bindPath, pathWithoutSourceDir);
134 if (BinderFileManager.CheckFileExists(filePath))
135 {
136 return filePath;
137 }
138 }
139
140 filePath = Path.Combine(bindPath, path);
141 if (BinderFileManager.CheckFileExists(filePath))
142 {
143 return filePath;
144 }
145 }
146 }
147
148 // Didn't find the file.
149 return null;
150 }
151
152 /// <summary>
153 /// Resolves the source path of a file related to another file's source.
154 /// </summary>
155 /// <param name="source">Original source value.</param>
156 /// <param name="relatedSource">Source related to original source.</param>
157 /// <param name="type">Optional type of source file being resolved.</param>
158 /// <param name="sourceLineNumbers">Optional source line of source file being resolved.</param>
159 /// <param name="bindStage">The binding stage used to determine what collection of bind paths will be used</param>
160 /// <returns>Should return a valid path for the stream to be imported.</returns>
161 public virtual string ResolveRelatedFile(string source, string relatedSource, string type, SourceLineNumber sourceLineNumbers, BindStage bindStage)
162 {
163 string resolvedSource = this.ResolveFile(source, type, sourceLineNumbers, bindStage);
164 return Path.Combine(Path.GetDirectoryName(resolvedSource), relatedSource);
165 }
166
167 /// <summary>
168 /// Resolves the source path of a cabinet file.
169 /// </summary>
170 /// <param name="cabinetPath">Default path to cabinet to generate.</param>
171 /// <param name="filesWithPath">Collection of files in this cabinet.</param>
172 /// <returns>The CabinetBuildOption and path to build the . By default the cabinet is built and moved to its target location.</returns>
173 [SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")]
174 public virtual ResolvedCabinet ResolveCabinet(string cabinetPath, IEnumerable<BindFileWithPath> filesWithPath)
175 {
176 if (null == filesWithPath)
177 {
178 throw new ArgumentNullException("fileRows");
179 }
180
181 // By default cabinet should be built and moved to the suggested location.
182 ResolvedCabinet resolved = new ResolvedCabinet() { BuildOption = CabinetBuildOption.BuildAndMove, Path = cabinetPath };
183
184 // If a cabinet cache path was provided, change the location for the cabinet
185 // to be built to and check if there is a cabinet that can be reused.
186 if (!String.IsNullOrEmpty(this.Core.CabCachePath))
187 {
188 string cabinetName = Path.GetFileName(cabinetPath);
189 resolved.Path = Path.Combine(this.Core.CabCachePath, cabinetName);
190
191 if (BinderFileManager.CheckFileExists(resolved.Path))
192 {
193 // Assume that none of the following are true:
194 // 1. any files are added or removed
195 // 2. order of files changed or names changed
196 // 3. modified time changed
197 bool cabinetValid = true;
198
199 // Need to force garbage collection of WixEnumerateCab to ensure the handle
200 // associated with it is closed before it is reused.
201 using (Cab.WixEnumerateCab wixEnumerateCab = new Cab.WixEnumerateCab())
202 {
203 List<CabinetFileInfo> fileList = wixEnumerateCab.Enumerate(resolved.Path);
204
205 if (filesWithPath.Count() != fileList.Count)
206 {
207 cabinetValid = false;
208 }
209 else
210 {
211 int i = 0;
212 foreach (BindFileWithPath file in filesWithPath)
213 {
214 // First check that the file identifiers match because that is quick and easy.
215 CabinetFileInfo cabFileInfo = fileList[i];
216 cabinetValid = (cabFileInfo.FileId == file.Id);
217 if (cabinetValid)
218 {
219 // Still valid so ensure the file sizes are the same.
220 FileInfo fileInfo = new FileInfo(file.Path);
221 cabinetValid = (cabFileInfo.Size == fileInfo.Length);
222 if (cabinetValid)
223 {
224 // Still valid so ensure the source time stamp hasn't changed. Thus we need
225 // to convert the source file time stamp into a cabinet compatible data/time.
226 ushort sourceCabDate;
227 ushort sourceCabTime;
228
229 WixToolset.Core.Native.CabInterop.DateTimeToCabDateAndTime(fileInfo.LastWriteTime, out sourceCabDate, out sourceCabTime);
230 cabinetValid = (cabFileInfo.Date == sourceCabDate && cabFileInfo.Time == sourceCabTime);
231 }
232 }
233
234 if (!cabinetValid)
235 {
236 break;
237 }
238
239 i++;
240 }
241 }
242 }
243
244 resolved.BuildOption = cabinetValid ? CabinetBuildOption.Copy : CabinetBuildOption.BuildAndCopy;
245 }
246 }
247
248 return resolved;
249 }
250
251 /// <summary>
252 /// Resolve the layout path of a media.
253 /// </summary>
254 /// <param name="mediaRow">The media's row.</param>
255 /// <param name="mediaLayoutDirectory">The layout directory provided by the Media element.</param>
256 /// <param name="layoutDirectory">The layout directory for the setup image.</param>
257 /// <returns>The layout path for the media.</returns>
258 public virtual string ResolveMedia(MediaRow mediaRow, string mediaLayoutDirectory, string layoutDirectory)
259 {
260 return null;
261 }
262
263 /// <summary>
264 /// Resolves the URL to a file.
265 /// </summary>
266 /// <param name="url">URL that may be a format string for the id and fileName.</param>
267 /// <param name="packageId">Identity of the package (if payload is not part of a package) the URL points to. NULL if not part of a package.</param>
268 /// <param name="payloadId">Identity of the payload the URL points to.</param>
269 /// <param name="fileName">File name the URL points at.</param>
270 /// <param name="fallbackUrl">Optional URL to use if the URL provided is empty.</param>
271 /// <returns>An absolute URL or null if no URL is provided.</returns>
272 public virtual string ResolveUrl(string url, string fallbackUrl, string packageId, string payloadId, string fileName)
273 {
274 // If a URL was not specified but there is a fallback URL that has a format specifier in it
275 // then use the fallback URL formatter for this URL.
276 if (String.IsNullOrEmpty(url) && !String.IsNullOrEmpty(fallbackUrl))
277 {
278 string formattedFallbackUrl = String.Format(fallbackUrl, packageId, payloadId, fileName);
279 if (!String.Equals(fallbackUrl, formattedFallbackUrl, StringComparison.OrdinalIgnoreCase))
280 {
281 url = fallbackUrl;
282 }
283 }
284
285 if (!String.IsNullOrEmpty(url))
286 {
287 string formattedUrl = String.Format(url, packageId, payloadId, fileName);
288
289 Uri canonicalUri;
290 if (Uri.TryCreate(formattedUrl, UriKind.Absolute, out canonicalUri))
291 {
292 url = canonicalUri.AbsoluteUri;
293 }
294 else
295 {
296 url = null;
297 }
298 }
299
300 return url;
301 }
302
303 /// <summary>
304 /// Copies a file.
305 /// </summary>
306 /// <param name="source">The file to copy.</param>
307 /// <param name="destination">The destination file.</param>
308 /// <param name="overwrite">true if the destination file can be overwritten; otherwise, false.</param>
309 public virtual bool CopyFile(string source, string destination, bool overwrite)
310 {
311 if (overwrite && File.Exists(destination))
312 {
313 File.Delete(destination);
314 }
315
316 if (!CreateHardLink(destination, source, IntPtr.Zero))
317 {
318#if DEBUG
319 int er = Marshal.GetLastWin32Error();
320#endif
321
322 File.Copy(source, destination, overwrite);
323 }
324
325 return true;
326 }
327
328 /// <summary>
329 /// Moves a file.
330 /// </summary>
331 /// <param name="source">The file to move.</param>
332 /// <param name="destination">The destination file.</param>
333 public virtual bool MoveFile(string source, string destination, bool overwrite)
334 {
335 if (overwrite && File.Exists(destination))
336 {
337 File.Delete(destination);
338 }
339
340 var directory = Path.GetDirectoryName(destination);
341 if (!String.IsNullOrEmpty(directory))
342 {
343 Directory.CreateDirectory(directory);
344 }
345
346 File.Move(source, destination);
347 return true;
348 }
349
350 /// <summary>
351 /// Checks if a path exists, and throws a well known error for invalid paths.
352 /// </summary>
353 /// <param name="path">Path to check.</param>
354 /// <returns>True if path exists.</returns>
355 private static bool CheckFileExists(string path)
356 {
357 try
358 {
359 return File.Exists(path);
360 }
361 catch (ArgumentException)
362 {
363 throw new WixException(WixErrors.IllegalCharactersInPath(path));
364 }
365 }
366
367 [DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
368 private static extern bool CreateHardLink(string lpFileName, string lpExistingFileName, IntPtr lpSecurityAttributes);
369 }
370}
diff --git a/src/WixToolset.Core/BinderFileManagerCore.cs b/src/WixToolset.Core/BinderFileManagerCore.cs
new file mode 100644
index 00000000..6a5e1d5e
--- /dev/null
+++ b/src/WixToolset.Core/BinderFileManagerCore.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
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using WixToolset.Data;
9 using WixToolset.Extensibility;
10
11 public class BinderFileManagerCore : IBinderFileManagerCore
12 {
13 private Dictionary<string, List<string>>[] bindPaths;
14
15 /// <summary>
16 /// Instantiate a new BinderFileManager.
17 /// </summary>
18 public BinderFileManagerCore()
19 {
20 this.bindPaths = new Dictionary<string, List<string>>[3];
21 this.bindPaths[(int)BindStage.Normal] = new Dictionary<string, List<string>>();
22 this.bindPaths[(int)BindStage.Target] = new Dictionary<string, List<string>>();
23 this.bindPaths[(int)BindStage.Updated] = new Dictionary<string, List<string>>();
24 }
25
26 /// <summary>
27 /// Gets or sets the path to cabinet cache.
28 /// </summary>
29 /// <value>The path to cabinet cache.</value>
30 public string CabCachePath { get; set; }
31
32 /// <summary>
33 /// Gets or sets the active subStorage used for binding.
34 /// </summary>
35 /// <value>The subStorage object.</value>
36 public SubStorage ActiveSubStorage { get; set; }
37
38 /// <summary>
39 /// Gets or sets the output object used for binding.
40 /// </summary>
41 /// <value>The output object.</value>
42 public Output Output { get; set; }
43
44 /// <summary>
45 /// Gets or sets the path to the temp files location.
46 /// </summary>
47 /// <value>The path to the temp files location.</value>
48 public string TempFilesLocation { get; set; }
49
50 /// <summary>
51 /// Gets the property if re-basing target is true or false
52 /// </summary>
53 /// <value>It returns true if target bind path is to be replaced, otherwise false.</value>
54 public bool RebaseTarget
55 {
56 get { return this.bindPaths[(int)BindStage.Target].Any(); }
57 }
58
59 /// <summary>
60 /// Gets the property if re-basing updated build is true or false
61 /// </summary>
62 /// <value>It returns true if updated bind path is to be replaced, otherwise false.</value>
63 public bool RebaseUpdated
64 {
65 get { return this.bindPaths[(int)BindStage.Updated].Any(); }
66 }
67
68 public void AddBindPaths(IEnumerable<BindPath> paths, BindStage stage)
69 {
70 Dictionary<string, List<string>> dict = this.bindPaths[(int)stage];
71
72 foreach (BindPath bindPath in paths)
73 {
74 List<string> values;
75 if (!dict.TryGetValue(bindPath.Name, out values))
76 {
77 values = new List<string>();
78 dict.Add(bindPath.Name, values);
79 }
80
81 if (!values.Contains(bindPath.Path))
82 {
83 values.Add(bindPath.Path);
84 }
85 }
86 }
87
88 public IEnumerable<string> GetBindPaths(BindStage stage = BindStage.Normal, string name = null)
89 {
90 List<string> paths;
91 if (this.bindPaths[(int)stage].TryGetValue(name ?? String.Empty, out paths))
92 {
93 return paths;
94 }
95
96 return Enumerable.Empty<string>();
97 }
98
99 /// <summary>
100 /// Sends a message to the message delegate if there is one.
101 /// </summary>
102 /// <param name="e">Message event arguments.</param>
103 public void OnMessage(MessageEventArgs e)
104 {
105 Messaging.Instance.OnMessage(e);
106 }
107 }
108}
diff --git a/src/WixToolset.Core/CLR/Interop/CLRInterop.cs b/src/WixToolset.Core/CLR/Interop/CLRInterop.cs
new file mode 100644
index 00000000..4157f23a
--- /dev/null
+++ b/src/WixToolset.Core/CLR/Interop/CLRInterop.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.Clr.Interop
4{
5 using System;
6 using System.Runtime.InteropServices;
7
8 /// <summary>
9 /// Interop class for mscorwks.dll assembly name APIs.
10 /// </summary>
11 internal sealed class ClrInterop
12 {
13 private static readonly Guid referenceIdentityGuid = new Guid("6eaf5ace-7917-4f3c-b129-e046a9704766");
14
15 /// <summary>
16 /// Protect the constructor.
17 /// </summary>
18 private ClrInterop()
19 {
20 }
21
22 /// <summary>
23 /// Represents a reference to the unique signature of a code object.
24 /// </summary>
25 [ComImport]
26 [Guid("6eaf5ace-7917-4f3c-b129-e046a9704766")]
27 [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
28 internal interface IReferenceIdentity
29 {
30 /// <summary>
31 /// Get an assembly attribute.
32 /// </summary>
33 /// <param name="attributeNamespace">Attribute namespace.</param>
34 /// <param name="attributeName">Attribute name.</param>
35 /// <returns>The assembly attribute.</returns>
36 [return: MarshalAs(UnmanagedType.LPWStr)]
37 string GetAttribute(
38 [In, MarshalAs(UnmanagedType.LPWStr)] string attributeNamespace,
39 [In, MarshalAs(UnmanagedType.LPWStr)] string attributeName);
40
41 /// <summary>
42 /// Set an assembly attribute.
43 /// </summary>
44 /// <param name="attributeNamespace">Attribute namespace.</param>
45 /// <param name="attributeName">Attribute name.</param>
46 /// <param name="attributeValue">Attribute value.</param>
47 void SetAttribute(
48 [In, MarshalAs(UnmanagedType.LPWStr)] string attributeNamespace,
49 [In, MarshalAs(UnmanagedType.LPWStr)] string attributeName,
50 [In, MarshalAs(UnmanagedType.LPWStr)] string attributeValue);
51
52 /// <summary>
53 /// Get an iterator for the assembly's attributes.
54 /// </summary>
55 /// <returns>Assembly attribute enumerator.</returns>
56 IEnumIDENTITY_ATTRIBUTE EnumAttributes();
57
58 /// <summary>
59 /// Clone an IReferenceIdentity.
60 /// </summary>
61 /// <param name="countOfDeltas">Count of deltas.</param>
62 /// <param name="deltas">The deltas.</param>
63 /// <returns>Cloned IReferenceIdentity.</returns>
64 IReferenceIdentity Clone(
65 [In] IntPtr /*SIZE_T*/ countOfDeltas,
66 [In, MarshalAs(UnmanagedType.LPArray)] IDENTITY_ATTRIBUTE[] deltas);
67 }
68
69 /// <summary>
70 /// IEnumIDENTITY_ATTRIBUTE interface.
71 /// </summary>
72 [ComImport]
73 [Guid("9cdaae75-246e-4b00-a26d-b9aec137a3eb")]
74 [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
75 internal interface IEnumIDENTITY_ATTRIBUTE
76 {
77 /// <summary>
78 /// Gets the next attributes.
79 /// </summary>
80 /// <param name="celt">Count of elements.</param>
81 /// <param name="attributes">Array of attributes being returned.</param>
82 /// <returns>The next attribute.</returns>
83 uint Next(
84 [In] uint celt,
85 [Out, MarshalAs(UnmanagedType.LPArray)] IDENTITY_ATTRIBUTE[] attributes);
86
87 /// <summary>
88 /// Copy the current attribute into a buffer.
89 /// </summary>
90 /// <param name="available">Number of available bytes.</param>
91 /// <param name="data">Buffer into which attribute should be written.</param>
92 /// <returns>Pointer to buffer containing the attribute.</returns>
93 IntPtr CurrentIntoBuffer(
94 [In] IntPtr /*SIZE_T*/ available,
95 [Out, MarshalAs(UnmanagedType.LPArray)] byte[] data);
96
97 /// <summary>
98 /// Skip past a number of elements.
99 /// </summary>
100 /// <param name="celt">Count of elements to skip.</param>
101 void Skip([In] uint celt);
102
103 /// <summary>
104 /// Reset the enumeration to the beginning.
105 /// </summary>
106 void Reset();
107
108 /// <summary>
109 /// Clone this attribute enumeration.
110 /// </summary>
111 /// <returns>Clone of a IEnumIDENTITY_ATTRIBUTE.</returns>
112 IEnumIDENTITY_ATTRIBUTE Clone();
113 }
114
115 /// <summary>
116 /// Gets the guid.
117 /// </summary>
118 public static Guid ReferenceIdentityGuid
119 {
120 get { return referenceIdentityGuid; }
121 }
122
123 /// <summary>
124 /// Gets an interface pointer to an object with the specified IID, in the assembly at the specified file path.
125 /// </summary>
126 /// <param name="wszAssemblyPath">A valid path to the requested assembly.</param>
127 /// <param name="riid">The IID of the interface to return.</param>
128 /// <param name="i">The returned interface pointer.</param>
129 /// <returns>The error code.</returns>
130 [DllImport("mscorwks.dll", CharSet = CharSet.Unicode, EntryPoint = "GetAssemblyIdentityFromFile")]
131 internal static extern uint GetAssemblyIdentityFromFile(System.String wszAssemblyPath, ref Guid riid, out IReferenceIdentity i);
132
133 /// <summary>
134 /// Assembly attributes. Contains data about an IReferenceIdentity.
135 /// </summary>
136 [StructLayout(LayoutKind.Sequential)]
137 internal struct IDENTITY_ATTRIBUTE
138 {
139 [MarshalAs(UnmanagedType.LPWStr)]
140 public string AttributeNamespace;
141 [MarshalAs(UnmanagedType.LPWStr)]
142 public string AttributeName;
143 [MarshalAs(UnmanagedType.LPWStr)]
144 public string AttributeValue;
145 }
146 }
147}
diff --git a/src/WixToolset.Core/Cab/CabinetFileInfo.cs b/src/WixToolset.Core/Cab/CabinetFileInfo.cs
new file mode 100644
index 00000000..849bb3bb
--- /dev/null
+++ b/src/WixToolset.Core/Cab/CabinetFileInfo.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 WixToolset
4{
5 using System;
6
7 /// <summary>
8 /// Properties of a file in a cabinet.
9 /// </summary>
10 internal sealed class CabinetFileInfo
11 {
12 private string fileId;
13 private ushort date;
14 private ushort time;
15 private int size;
16
17 /// <summary>
18 /// Constructs CabinetFileInfo
19 /// </summary>
20 /// <param name="fileId">File Id</param>
21 /// <param name="date">Last modified date (MS-DOS time)</param>
22 /// <param name="time">Last modified time (MS-DOS time)</param>
23 public CabinetFileInfo(string fileId, ushort date, ushort time, int size)
24 {
25 this.fileId = fileId;
26 this.date = date;
27 this.time = time;
28 this.size = size;
29 }
30
31 /// <summary>
32 /// Gets the file Id of the file.
33 /// </summary>
34 /// <value>file Id</value>
35 public string FileId
36 {
37 get { return this.fileId; }
38 }
39
40 /// <summary>
41 /// Gets modified date (DOS format).
42 /// </summary>
43 public ushort Date
44 {
45 get { return this.date; }
46 }
47
48 /// <summary>
49 /// Gets modified time (DOS format).
50 /// </summary>
51 public ushort Time
52 {
53 get { return this.time; }
54 }
55
56 /// <summary>
57 /// Gets the size of the file in bytes.
58 /// </summary>
59 public int Size
60 {
61 get { return this.size; }
62 }
63 }
64}
diff --git a/src/WixToolset.Core/Cab/Interop/CabInterop.cs b/src/WixToolset.Core/Cab/Interop/CabInterop.cs
new file mode 100644
index 00000000..6c1ae2c1
--- /dev/null
+++ b/src/WixToolset.Core/Cab/Interop/CabInterop.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
3#if false
4
5namespace WixToolset.Cab.Interop
6{
7 using System;
8 using System.Diagnostics.CodeAnalysis;
9 using System.Text;
10 using System.Runtime.InteropServices;
11 using WixToolset.Msi;
12 using WixToolset.Msi.Interop;
13
14 /// <summary>
15 /// The native methods.
16 /// </summary>
17 public sealed class NativeMethods
18 {
19 /// <summary>
20 /// Starts creating a cabinet.
21 /// </summary>
22 /// <param name="cabinetName">Name of cabinet to create.</param>
23 /// <param name="cabinetDirectory">Directory to create cabinet in.</param>
24 /// <param name="maxFiles">Maximum number of files that will be added to cabinet.</param>
25 /// <param name="maxSize">Maximum size of the cabinet.</param>
26 /// <param name="maxThreshold">Maximum threshold in the cabinet.</param>
27 /// <param name="compressionType">Type of compression to use in the cabinet.</param>
28 /// <param name="contextHandle">Handle to opened cabinet.</param>
29 [DllImport("winterop.dll", EntryPoint = "CreateCabBegin", CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)]
30 internal static extern void CreateCabBegin(string cabinetName, string cabinetDirectory, uint maxFiles, uint maxSize, uint maxThreshold, uint compressionType, out IntPtr contextHandle);
31
32 /// <summary>
33 /// Adds a file to an open cabinet.
34 /// </summary>
35 /// <param name="file">Full path to file to add to cabinet.</param>
36 /// <param name="token">Name of file in cabinet.</param>
37 /// <param name="contextHandle">Handle to open cabinet.</param>
38 [DllImport("winterop.dll", EntryPoint = "CreateCabAddFile", CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)]
39 internal static extern void CreateCabAddFile(string file, string token, MsiInterop.MSIFILEHASHINFO fileHash, IntPtr contextHandle);
40
41 /// <summary>
42 /// Closes a cabinet.
43 /// </summary>
44 /// <param name="contextHandle">Handle to open cabinet to close.</param>
45 /// <param name="newCabNamesCallBackAddress">Address of Binder's cabinet split callback</param>
46 [DllImport("winterop.dll", EntryPoint = "CreateCabFinish", CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)]
47 internal static extern void CreateCabFinish(IntPtr contextHandle, IntPtr newCabNamesCallBackAddress);
48
49 /// <summary>
50 /// Cancels cabinet creation.
51 /// </summary>
52 /// <param name="contextHandle">Handle to open cabinet to cancel.</param>
53 [DllImport("winterop.dll", EntryPoint = "CreateCabCancel", CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)]
54 internal static extern void CreateCabCancel(IntPtr contextHandle);
55
56 /// <summary>
57 /// Initializes cabinet extraction.
58 /// </summary>
59 [DllImport("winterop.dll", EntryPoint = "ExtractCabBegin", CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)]
60 internal static extern void ExtractCabBegin();
61
62 /// <summary>
63 /// Extracts files from cabinet.
64 /// </summary>
65 /// <param name="cabinet">Path to cabinet to extract files from.</param>
66 /// <param name="extractDirectory">Directory to extract files to.</param>
67 [DllImport("winterop.dll", EntryPoint = "ExtractCab", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true, PreserveSig = false)]
68 internal static extern void ExtractCab(string cabinet, string extractDirectory);
69
70 /// <summary>
71 /// Cleans up after cabinet extraction.
72 /// </summary>
73 [DllImport("winterop.dll", EntryPoint = "ExtractCabFinish", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
74 internal static extern void ExtractCabFinish();
75
76 /// <summary>
77 /// Initializes cabinet enumeration.
78 /// </summary>
79 [DllImport("winterop.dll", EntryPoint = "EnumerateCabBegin", CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)]
80 internal static extern void EnumerateCabBegin();
81
82 /// <summary>
83 /// Enumerates files from cabinet.
84 /// </summary>
85 /// <param name="cabinet">Path to cabinet to enumerate files from.</param>
86 /// <param name="notify">callback that gets each file.</param>
87 [DllImport("winterop.dll", EntryPoint = "EnumerateCab", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true, PreserveSig = false)]
88 internal static extern void EnumerateCab(string cabinet, CabInterop.PFNNOTIFY notify);
89
90 /// <summary>
91 /// Cleans up after cabinet enumeration.
92 /// </summary>
93 [DllImport("winterop.dll", EntryPoint = "EnumerateCabFinish", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
94 internal static extern void EnumerateCabFinish();
95
96 /// <summary>
97 /// Resets the DACL on an array of files to "empty".
98 /// </summary>
99 /// <param name="files">Array of file reset ACL to "empty".</param>
100 /// <param name="fileCount">Number of file paths in array.</param>
101 [DllImport("winterop.dll", EntryPoint = "ResetAcls", CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)]
102 internal static extern void ResetAcls(string[] files, uint fileCount);
103
104 /// <summary>
105 /// Gets the hash of the pCertContext->pCertInfo->SubjectPublicKeyInfo using ::CryptHashPublicKeyInfo() which does not seem
106 /// to be exposed by .NET Frameowkr.
107 /// </summary>
108 /// <param name="certContext">Pointer to a CERT_CONTEXT struct with public key information to hash.</param>
109 /// <param name="fileCount">Number of file paths in array.</param>
110 [DllImport("winterop.dll", EntryPoint = "HashPublicKeyInfo", CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)]
111 internal static extern void HashPublicKeyInfo(IntPtr certContext, byte[] publicKeyInfoHashed, ref uint sizePublicKeyInfoHashed);
112
113 /// <summary>
114 /// Converts file time to a local file time.
115 /// </summary>
116 /// <param name="fileTime">file time</param>
117 /// <param name="localTime">local file time</param>
118 /// <returns>true if successful, false otherwise</returns>
119 [DllImport("kernel32.dll", SetLastError = true)]
120 [return: MarshalAs(UnmanagedType.Bool)]
121 internal static extern bool FileTimeToLocalFileTime(ref long fileTime, ref long localTime);
122
123 /// <summary>
124 /// Converts file time to a MS-DOS time.
125 /// </summary>
126 /// <param name="fileTime">file time</param>
127 /// <param name="wFatDate">MS-DOS date</param>
128 /// <param name="wFatTime">MS-DOS time</param>
129 /// <returns>true if successful, false otherwise</returns>
130 [DllImport("kernel32.dll", SetLastError = true)]
131 [return: MarshalAs(UnmanagedType.Bool)]
132 internal static extern bool FileTimeToDosDateTime(ref long fileTime, out ushort wFatDate, out ushort wFatTime);
133 }
134
135 /// <summary>
136 /// Interop class for the winterop.dll.
137 /// </summary>
138 internal static class CabInterop
139 {
140 /// <summary>
141 /// Delegate type that's called by cabinet api for every file in cabinet.
142 /// </summary>
143 /// <param name="fdint">NOTIFICATIONTYPE</param>
144 /// <param name="pfdin">NOTIFICATION</param>
145 /// <returns>0 for success, -1 otherwise</returns>
146 public delegate Int32 PFNNOTIFY(NOTIFICATIONTYPE fdint, NOTIFICATION pfdin);
147
148 /// <summary>
149 /// Wraps FDINOTIFICATIONTYPE.
150 /// </summary>
151 public enum NOTIFICATIONTYPE : int
152 {
153 /// <summary>Info about the cabinet.</summary>
154 CABINET_INFO,
155 /// <summary>One or more files are continued.</summary>
156 PARTIAL_FILE,
157 /// <summary>Called for each file in cabinet.</summary>
158 COPY_FILE,
159 /// <summary>Called after all of the data has been written to a target file.</summary>
160 CLOSE_FILE_INFO,
161 /// <summary>A file is continued to the next cabinet.</summary>
162 NEXT_CABINET,
163 /// <summary>Called once after a call to FDICopy() starts scanning a CAB's CFFILE entries, and again when there are no more CFFILE entries.</summary>
164 ENUMERATE,
165 }
166
167 /// <summary>
168 /// Converts DateTime to MS-DOS date and time which cabinet uses.
169 /// </summary>
170 /// <param name="dateTime">DateTime</param>
171 /// <param name="cabDate">MS-DOS date</param>
172 /// <param name="cabTime">MS-DOS time</param>
173 public static void DateTimeToCabDateAndTime(DateTime dateTime, out ushort cabDate, out ushort cabTime)
174 {
175 // dateTime.ToLocalTime() does not match FileTimeToLocalFileTime() for some reason.
176 // so we need to call FileTimeToLocalFileTime() from kernel32.dll.
177 long filetime = dateTime.ToFileTime();
178 long localTime = 0;
179 NativeMethods.FileTimeToLocalFileTime(ref filetime, ref localTime);
180 NativeMethods.FileTimeToDosDateTime(ref localTime, out cabDate, out cabTime);
181 }
182
183 /// <summary>
184 /// Wraps FDINOTIFICATION.
185 /// </summary>
186 [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")]
187 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
188 public class NOTIFICATION
189 {
190 private int cb;
191 [MarshalAs(UnmanagedType.LPStr)]
192 private string psz1;
193 [MarshalAs(UnmanagedType.LPStr)]
194 private string psz2;
195 [MarshalAs(UnmanagedType.LPStr)]
196 private string psz3;
197 private IntPtr pv;
198
199 private IntPtr hf;
200
201 private ushort date;
202 private ushort time;
203 private ushort attribs;
204 private ushort setID;
205 private ushort cabinet;
206 private ushort folder;
207 private int fdie;
208
209 /// <summary>
210 /// Uncompressed size of file.
211 /// </summary>
212 public int Cb
213 {
214 get { return this.cb; }
215 }
216
217 /// <summary>
218 /// File name in cabinet.
219 /// </summary>
220 public String Psz1
221 {
222 get { return this.psz1; }
223 }
224
225 /// <summary>
226 /// Name of next disk.
227 /// </summary>
228 public string Psz2
229 {
230 get { return this.psz2; }
231 }
232
233 /// <summary>
234 /// Points to a 256 character buffer.
235 /// </summary>
236 public string Psz3
237 {
238 get { return this.psz3; }
239 }
240
241 /// <summary>
242 /// Value for client.
243 /// </summary>
244 public IntPtr Pv
245 {
246 get { return this.pv; }
247 }
248
249 /// <summary>
250 /// Not used.
251 /// </summary>
252 public Int32 Hf
253 {
254 get { return (Int32)this.hf; }
255 }
256
257 /// <summary>
258 /// Last modified MS-DOS date.
259 /// </summary>
260 public ushort Date
261 {
262 get { return this.date; }
263 }
264
265 /// <summary>
266 /// Last modified MS-DOS time.
267 /// </summary>
268 public ushort Time
269 {
270 get { return this.time; }
271 }
272
273 /// <summary>
274 /// File attributes.
275 /// </summary>
276 public ushort Attribs
277 {
278 get { return this.attribs; }
279 }
280
281 /// <summary>
282 /// Cabinet set ID (a random 16-bit number).
283 /// </summary>
284 public ushort SetID
285 {
286 get { return this.setID; }
287 }
288
289 /// <summary>
290 /// Cabinet number within cabinet set (0-based).
291 /// </summary>
292 public ushort Cabinet
293 {
294 get { return this.cabinet; }
295 }
296
297 /// <summary>
298 /// File's folder index.
299 /// </summary>
300 public ushort Folder
301 {
302 get { return this.folder; }
303 }
304
305 /// <summary>
306 /// Error code.
307 /// </summary>
308 public int Fdie
309 {
310 get { return this.fdie; }
311 }
312 }
313 }
314}
315
316#endif
diff --git a/src/WixToolset.Core/Cab/WixCreateCab.cs b/src/WixToolset.Core/Cab/WixCreateCab.cs
new file mode 100644
index 00000000..8f985a43
--- /dev/null
+++ b/src/WixToolset.Core/Cab/WixCreateCab.cs
@@ -0,0 +1,249 @@
1// Copyright (c) .NET Foundation and 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.Cab
4{
5 using System;
6 using System.Globalization;
7 using System.IO;
8 using System.Runtime.InteropServices;
9 using WixToolset.Bind.Databases;
10 using WixToolset.Core.Native;
11 using WixToolset.Data;
12
13 /// <summary>
14 /// Wrapper class around interop with wixcab.dll to compress files into a cabinet.
15 /// </summary>
16 public sealed class WixCreateCab : IDisposable
17 {
18 private static readonly string CompressionLevelVariable = "WIX_COMPRESSION_LEVEL";
19 private IntPtr handle = IntPtr.Zero;
20 private bool disposed;
21 private int maxSize;
22
23 /// <summary>
24 /// Creates a cabinet.
25 /// </summary>
26 /// <param name="cabName">Name of cabinet to create.</param>
27 /// <param name="cabDir">Directory to create cabinet in.</param>
28 /// <param name="maxFiles">Maximum number of files that will be added to cabinet.</param>
29 /// <param name="maxSize">Maximum size of cabinet.</param>
30 /// <param name="maxThresh">Maximum threshold for each cabinet.</param>
31 /// <param name="compressionLevel">Level of compression to apply.</param>
32 public WixCreateCab(string cabName, string cabDir, int maxFiles, int maxSize, int maxThresh, CompressionLevel compressionLevel)
33 {
34 string compressionLevelVariable = Environment.GetEnvironmentVariable(CompressionLevelVariable);
35 this.maxSize = maxSize;
36
37 try
38 {
39 // Override authored compression level if environment variable is present.
40 if (!String.IsNullOrEmpty(compressionLevelVariable))
41 {
42 compressionLevel = WixCreateCab.CompressionLevelFromString(compressionLevelVariable);
43 }
44 }
45 catch (WixException)
46 {
47 throw new WixException(WixErrors.IllegalEnvironmentVariable(CompressionLevelVariable, compressionLevelVariable));
48 }
49
50 if (String.IsNullOrEmpty(cabDir))
51 {
52 cabDir = Directory.GetCurrentDirectory();
53 }
54
55 try
56 {
57 NativeMethods.CreateCabBegin(cabName, cabDir, (uint)maxFiles, (uint)maxSize, (uint)maxThresh, (uint)compressionLevel, out this.handle);
58 }
59 catch (COMException ce)
60 {
61 // If we get a "the file exists" error, we must have a full temp directory - so report the issue
62 if (0x80070050 == unchecked((uint)ce.ErrorCode))
63 {
64 throw new WixException(WixErrors.FullTempDirectory("WSC", Path.GetTempPath()));
65 }
66
67 throw;
68 }
69 }
70
71 /// <summary>
72 /// Destructor for cabinet creation.
73 /// </summary>
74 ~WixCreateCab()
75 {
76 this.Dispose();
77 }
78
79 /// <summary>
80 /// Converts a compression level from its string to its enum value.
81 /// </summary>
82 /// <param name="compressionLevel">Compression level as a string.</param>
83 /// <returns>CompressionLevel enum value</returns>
84 public static CompressionLevel CompressionLevelFromString(string compressionLevel)
85 {
86 switch (compressionLevel.ToLower(CultureInfo.InvariantCulture))
87 {
88 case "low":
89 return CompressionLevel.Low;
90 case "medium":
91 return CompressionLevel.Medium;
92 case "high":
93 return CompressionLevel.High;
94 case "none":
95 return CompressionLevel.None;
96 case "mszip":
97 return CompressionLevel.Mszip;
98 default:
99 throw new WixException(WixErrors.IllegalCompressionLevel(compressionLevel));
100 }
101 }
102
103 /// <summary>
104 /// Adds a file to the cabinet.
105 /// </summary>
106 /// <param name="fileFacade">The file facade of the file to add.</param>
107 public void AddFile(FileFacade fileFacade)
108 {
109 MsiInterop.MSIFILEHASHINFO hashInterop = new MsiInterop.MSIFILEHASHINFO();
110
111 if (null != fileFacade.Hash)
112 {
113 hashInterop.FileHashInfoSize = 20;
114 hashInterop.Data0 = (int)fileFacade.Hash[2];
115 hashInterop.Data1 = (int)fileFacade.Hash[3];
116 hashInterop.Data2 = (int)fileFacade.Hash[4];
117 hashInterop.Data3 = (int)fileFacade.Hash[5];
118
119 this.AddFile(fileFacade.WixFile.Source, fileFacade.File.File, hashInterop);
120 }
121 else
122 {
123 this.AddFile(fileFacade.WixFile.Source, fileFacade.File.File);
124 }
125 }
126
127 /// <summary>
128 /// Adds a file to the cabinet.
129 /// </summary>
130 /// <param name="file">The file to add.</param>
131 /// <param name="token">The token for the file.</param>
132 public void AddFile(string file, string token)
133 {
134 this.AddFile(file, token, null);
135 }
136
137 /// <summary>
138 /// Adds a file to the cabinet with an optional MSI file hash.
139 /// </summary>
140 /// <param name="file">The file to add.</param>
141 /// <param name="token">The token for the file.</param>
142 /// <param name="fileHash">The MSI file hash of the file.</param>
143 private void AddFile(string file, string token, MsiInterop.MSIFILEHASHINFO fileHash)
144 {
145 try
146 {
147 NativeMethods.CreateCabAddFile(file, token, fileHash, this.handle);
148 }
149 catch (COMException ce)
150 {
151 if (0x80004005 == unchecked((uint)ce.ErrorCode)) // E_FAIL
152 {
153 throw new WixException(WixErrors.CreateCabAddFileFailed());
154 }
155 else if (0x80070070 == unchecked((uint)ce.ErrorCode)) // ERROR_DISK_FULL
156 {
157 throw new WixException(WixErrors.CreateCabInsufficientDiskSpace());
158 }
159 else
160 {
161 throw;
162 }
163 }
164 catch (DirectoryNotFoundException)
165 {
166 throw new WixFileNotFoundException(file);
167 }
168 catch (FileNotFoundException)
169 {
170 throw new WixFileNotFoundException(file);
171 }
172 }
173
174 /// <summary>
175 /// Complete/commit the cabinet - this must be called before Dispose so that errors will be
176 /// reported on the same thread.
177 /// This Complete should be used with no Cabinet splitting as it has the split cabinet names callback address as Zero
178 /// </summary>
179 public void Complete()
180 {
181 this.Complete(IntPtr.Zero);
182 }
183
184 /// <summary>
185 /// Complete/commit the cabinet - this must be called before Dispose so that errors will be
186 /// reported on the same thread.
187 /// </summary>
188 /// <param name="newCabNamesCallBackAddress">Address of Binder's callback function for Cabinet Splitting</param>
189 public void Complete(IntPtr newCabNamesCallBackAddress)
190 {
191 if (IntPtr.Zero != this.handle)
192 {
193 try
194 {
195 if (newCabNamesCallBackAddress != IntPtr.Zero && this.maxSize != 0)
196 {
197 NativeMethods.CreateCabFinish(this.handle, newCabNamesCallBackAddress);
198 }
199 else
200 {
201 NativeMethods.CreateCabFinish(this.handle, IntPtr.Zero);
202 }
203
204 GC.SuppressFinalize(this);
205 this.disposed = true;
206 }
207 catch (COMException ce)
208 {
209 if (0x80004005 == unchecked((uint)ce.ErrorCode)) // E_FAIL
210 {
211 // This error seems to happen, among other situations, when cabbing more than 0xFFFF files
212 throw new WixException(WixErrors.FinishCabFailed());
213 }
214 else if (0x80070070 == unchecked((uint)ce.ErrorCode)) // ERROR_DISK_FULL
215 {
216 throw new WixException(WixErrors.CreateCabInsufficientDiskSpace());
217 }
218 else
219 {
220 throw;
221 }
222 }
223 finally
224 {
225 this.handle = IntPtr.Zero;
226 }
227 }
228 }
229
230 /// <summary>
231 /// Cancels ("rolls back") the creation of the cabinet.
232 /// Don't throw WiX errors from here, because we're in a different thread, and they won't be reported correctly.
233 /// </summary>
234 public void Dispose()
235 {
236 if (!this.disposed)
237 {
238 if (IntPtr.Zero != this.handle)
239 {
240 NativeMethods.CreateCabCancel(this.handle);
241 this.handle = IntPtr.Zero;
242 }
243
244 GC.SuppressFinalize(this);
245 this.disposed = true;
246 }
247 }
248 }
249}
diff --git a/src/WixToolset.Core/Cab/WixEnumerateCab.cs b/src/WixToolset.Core/Cab/WixEnumerateCab.cs
new file mode 100644
index 00000000..017eeffb
--- /dev/null
+++ b/src/WixToolset.Core/Cab/WixEnumerateCab.cs
@@ -0,0 +1,89 @@
1// Copyright (c) .NET Foundation and 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.Cab
4{
5 using System;
6 using System.Collections.Generic;
7 using WixToolset.Core.Native;
8 using Handle = System.Int32;
9
10 /// <summary>
11 /// Wrapper class around interop with wixcab.dll to enumerate files from a cabinet.
12 /// </summary>
13 internal sealed class WixEnumerateCab : IDisposable
14 {
15 private bool disposed;
16 private List<CabinetFileInfo> fileInfoList;
17 private CabInterop.PFNNOTIFY pfnNotify;
18
19 /// <summary>
20 /// Creates a cabinet enumerator.
21 /// </summary>
22 public WixEnumerateCab()
23 {
24 this.pfnNotify = new CabInterop.PFNNOTIFY(this.Notify);
25 NativeMethods.EnumerateCabBegin();
26 }
27
28 /// <summary>
29 /// Destructor for cabinet enumeration.
30 /// </summary>
31 ~WixEnumerateCab()
32 {
33 this.Dispose();
34 }
35
36 /// <summary>
37 /// Enumerates all files in a cabinet.
38 /// </summary>
39 /// <param name="cabinetFile">path to cabinet</param>
40 /// <returns>list of CabinetFileInfo</returns>
41 internal List<CabinetFileInfo> Enumerate(string cabinetFile)
42 {
43 this.fileInfoList = new List<CabinetFileInfo>();
44
45 // the callback (this.Notify) will populate the list for each file in cabinet
46 NativeMethods.EnumerateCab(cabinetFile, this.pfnNotify);
47
48 return this.fileInfoList;
49 }
50
51 /// <summary>
52 /// Disposes the managed and unmanaged objects in this object.
53 /// </summary>
54 public void Dispose()
55 {
56 if (!this.disposed)
57 {
58 NativeMethods.EnumerateCabFinish();
59
60 GC.SuppressFinalize(this);
61 this.disposed = true;
62 }
63 }
64
65 /// <summary>
66 /// Delegate that's called for every file in cabinet.
67 /// </summary>
68 /// <param name="fdint">NOTIFICATIONTYPE</param>
69 /// <param name="pfdin">NOTIFICATION</param>
70 /// <returns>System.Int32</returns>
71 internal Handle Notify(CabInterop.NOTIFICATIONTYPE fdint, CabInterop.NOTIFICATION pfdin)
72 {
73 // This is FDI's way of notifying us of how many files total are in the cab, accurate even
74 // if the files are split into multiple folders - use it to allocate the precise size we need
75 if (CabInterop.NOTIFICATIONTYPE.ENUMERATE == fdint && 0 == this.fileInfoList.Count)
76 {
77 this.fileInfoList.Capacity = pfdin.Folder;
78 }
79
80 if (fdint == CabInterop.NOTIFICATIONTYPE.COPY_FILE)
81 {
82 CabinetFileInfo fileInfo = new CabinetFileInfo(pfdin.Psz1, pfdin.Date, pfdin.Time, pfdin.Cb);
83 this.fileInfoList.Add(fileInfo);
84 }
85
86 return 0; // tell cabinet api to skip this file.
87 }
88 }
89}
diff --git a/src/WixToolset.Core/Cab/WixExtractCab.cs b/src/WixToolset.Core/Cab/WixExtractCab.cs
new file mode 100644
index 00000000..debdaf15
--- /dev/null
+++ b/src/WixToolset.Core/Cab/WixExtractCab.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.Cab
4{
5 using System;
6 using System.Runtime.InteropServices;
7 using WixToolset.Core.Native;
8
9 /// <summary>
10 /// Wrapper class around interop with wixcab.dll to extract files from a cabinet.
11 /// </summary>
12 public sealed class WixExtractCab : IDisposable
13 {
14 private bool disposed;
15
16 /// <summary>
17 /// Creates a cabinet extractor.
18 /// </summary>
19 public WixExtractCab()
20 {
21 NativeMethods.ExtractCabBegin();
22 }
23
24 /// <summary>
25 /// Destructor for cabinet extraction.
26 /// </summary>
27 ~WixExtractCab()
28 {
29 this.Dispose();
30 }
31
32 /// <summary>
33 /// Extracts all the files from a cabinet to a directory.
34 /// </summary>
35 /// <param name="cabinetFile">Cabinet file to extract from.</param>
36 /// <param name="extractDir">Directory to extract files to.</param>
37 public void Extract(string cabinetFile, string extractDir)
38 {
39 if (null == cabinetFile)
40 {
41 throw new ArgumentNullException("cabinetFile");
42 }
43
44 if (null == extractDir)
45 {
46 throw new ArgumentNullException("extractDir");
47 }
48
49 if (this.disposed)
50 {
51 throw new ObjectDisposedException("WixExtractCab");
52 }
53
54 if (!extractDir.EndsWith("\\", StringComparison.Ordinal))
55 {
56 extractDir = String.Concat(extractDir, "\\");
57 }
58
59 NativeMethods.ExtractCab(cabinetFile, extractDir);
60 }
61
62 /// <summary>
63 /// Disposes the managed and unmanaged objects in this object.
64 /// </summary>
65 public void Dispose()
66 {
67 if (!this.disposed)
68 {
69 NativeMethods.ExtractCabFinish();
70
71 GC.SuppressFinalize(this);
72 this.disposed = true;
73 }
74 }
75 }
76}
diff --git a/src/WixToolset.Core/CommandLine/BuildCommand.cs b/src/WixToolset.Core/CommandLine/BuildCommand.cs
new file mode 100644
index 00000000..ffb48305
--- /dev/null
+++ b/src/WixToolset.Core/CommandLine/BuildCommand.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 WixToolset.Core
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 using WixToolset.Data;
10
11 internal class BuildCommand : ICommand
12 {
13 public BuildCommand(IEnumerable<SourceFile> sources, IDictionary<string, string> preprocessorVariables, IEnumerable<string> locFiles, string outputPath, IEnumerable<string> cultures, string contentsFile, string outputsFile, string builtOutputsFile, string wixProjectFile)
14 {
15 this.LocFiles = locFiles;
16 this.PreprocessorVariables = preprocessorVariables;
17 this.SourceFiles = sources;
18 this.OutputPath = outputPath;
19
20 this.Cultures = cultures;
21 this.ContentsFile = contentsFile;
22 this.OutputsFile = outputsFile;
23 this.BuiltOutputsFile = builtOutputsFile;
24 this.WixProjectFile = wixProjectFile;
25 }
26
27 public IEnumerable<string> LocFiles { get; }
28
29 private IEnumerable<SourceFile> SourceFiles { get; }
30
31 private IDictionary<string, string> PreprocessorVariables { get; }
32
33 private string OutputPath { get; }
34
35 public IEnumerable<string> Cultures { get; }
36
37 public string ContentsFile { get; }
38
39 public string OutputsFile { get; }
40
41 public string BuiltOutputsFile { get; }
42
43 public string WixProjectFile { get; }
44
45 public int Execute()
46 {
47 var intermediates = CompilePhase();
48
49 var sections = intermediates.SelectMany(i => i.Sections).ToList();
50
51 var linker = new Linker();
52
53 var output = linker.Link(sections, OutputType.Product);
54
55 var localizer = new Localizer();
56
57 var binder = new Binder();
58 binder.TempFilesLocation = Path.GetTempPath();
59 binder.WixVariableResolver = new WixVariableResolver();
60 binder.WixVariableResolver.Localizer = localizer;
61 binder.AddExtension(new BinderFileManager());
62 binder.SuppressValidation = true;
63
64 binder.ContentsFile = this.ContentsFile;
65 binder.OutputsFile = this.OutputsFile;
66 binder.BuiltOutputsFile = this.BuiltOutputsFile;
67 binder.WixprojectFile = this.WixProjectFile;
68
69 foreach (var loc in this.LocFiles)
70 {
71 var localization = Localizer.ParseLocalizationFile(loc, linker.TableDefinitions);
72 binder.WixVariableResolver.Localizer.AddLocalization(localization);
73 }
74
75 binder.Bind(output, this.OutputPath);
76
77 return 0;
78 }
79
80 private IEnumerable<Intermediate> CompilePhase()
81 {
82 var intermediates = new List<Intermediate>();
83
84 var preprocessor = new Preprocessor();
85
86 var compiler = new Compiler();
87
88 foreach (var sourceFile in this.SourceFiles)
89 {
90 var document = preprocessor.Process(sourceFile.SourcePath, this.PreprocessorVariables);
91
92 var intermediate = compiler.Compile(document);
93
94 intermediates.Add(intermediate);
95 }
96
97 return intermediates;
98 }
99 }
100}
diff --git a/src/WixToolset.Core/CommandLine/CommandLine.cs b/src/WixToolset.Core/CommandLine/CommandLine.cs
new file mode 100644
index 00000000..440ae9ef
--- /dev/null
+++ b/src/WixToolset.Core/CommandLine/CommandLine.cs
@@ -0,0 +1,592 @@
1// Copyright (c) .NET Foundation and 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 System.Text;
10 using System.Text.RegularExpressions;
11 using WixToolset.Data;
12 using WixToolset.Extensibility;
13
14 internal enum Commands
15 {
16 Unknown,
17 Build,
18 Preprocess,
19 Compile,
20 Link,
21 Bind,
22 }
23
24 public class CommandLine
25 {
26 private CommandLine()
27 {
28 }
29
30 public static string ExpectedArgument { get; } = "expected argument";
31
32 public string ActiveCommand { get; private set; }
33
34 public string[] OriginalArguments { get; private set; }
35
36 public Queue<string> RemainingArguments { get; } = new Queue<string>();
37
38 public ExtensionManager ExtensionManager { get; } = new ExtensionManager();
39
40 public string ErrorArgument { get; set; }
41
42 public bool ShowHelp { get; set; }
43
44 public static ICommand ParseStandardCommandLine(string commandLineString)
45 {
46 var args = CommandLine.ParseArgumentsToArray(commandLineString).ToArray();
47
48 return ParseStandardCommandLine(args);
49 }
50
51 public static ICommand ParseStandardCommandLine(string[] args)
52 {
53 var next = String.Empty;
54
55 var command = Commands.Unknown;
56 var showLogo = true;
57 var showVersion = false;
58 var outputFolder = String.Empty;
59 var outputFile = String.Empty;
60 var sourceFile = String.Empty;
61 var verbose = false;
62 var files = new List<string>();
63 var defines = new List<string>();
64 var includePaths = new List<string>();
65 var locFiles = new List<string>();
66 var suppressedWarnings = new List<int>();
67
68 var cultures = new List<string>();
69 var contentsFile = String.Empty;
70 var outputsFile = String.Empty;
71 var builtOutputsFile = String.Empty;
72 var wixProjectFile = String.Empty;
73
74 var cli = CommandLine.Parse(args, (cmdline, arg) => Enum.TryParse(arg, true, out command), (cmdline, arg) =>
75 {
76 if (cmdline.IsSwitch(arg))
77 {
78 var parameter = arg.TrimStart(new[] { '-', '/' });
79 switch (parameter.ToLowerInvariant())
80 {
81 case "?":
82 case "h":
83 case "help":
84 cmdline.ShowHelp = true;
85 return true;
86
87 case "cultures":
88 cmdline.GetNextArgumentOrError(cultures);
89 return true;
90 case "contentsfile":
91 cmdline.GetNextArgumentOrError(ref contentsFile);
92 return true;
93 case "outputsfile":
94 cmdline.GetNextArgumentOrError(ref outputsFile);
95 return true;
96 case "builtoutputsfile":
97 cmdline.GetNextArgumentOrError(ref builtOutputsFile);
98 return true;
99 case "wixprojectfile":
100 cmdline.GetNextArgumentOrError(ref wixProjectFile);
101 return true;
102
103 case "d":
104 case "define":
105 cmdline.GetNextArgumentOrError(defines);
106 return true;
107
108 case "i":
109 case "includepath":
110 cmdline.GetNextArgumentOrError(includePaths);
111 return true;
112
113 case "loc":
114 cmdline.GetNextArgumentAsFilePathOrError(locFiles, "localization files");
115 return true;
116
117 case "o":
118 case "out":
119 cmdline.GetNextArgumentOrError(ref outputFile);
120 return true;
121
122 case "nologo":
123 showLogo = false;
124 return true;
125
126 case "v":
127 case "verbose":
128 verbose = true;
129 return true;
130
131 case "version":
132 case "-version":
133 showVersion = true;
134 return true;
135 }
136
137 return false;
138 }
139 else
140 {
141 files.AddRange(cmdline.GetFiles(arg, "source code"));
142 return true;
143 }
144 });
145
146 if (showVersion)
147 {
148 return new VersionCommand();
149 }
150
151 if (showLogo)
152 {
153 AppCommon.DisplayToolHeader();
154 }
155
156 if (cli.ShowHelp)
157 {
158 return new HelpCommand(command);
159 }
160
161 switch (command)
162 {
163 case Commands.Build:
164 {
165 var sourceFiles = GatherSourceFiles(files, outputFolder);
166 var variables = GatherPreprocessorVariables(defines);
167 var extensions = cli.ExtensionManager;
168 return new BuildCommand(sourceFiles, variables, locFiles, outputFile, cultures, contentsFile, outputsFile, builtOutputsFile, wixProjectFile);
169 }
170
171 case Commands.Compile:
172 {
173 var sourceFiles = GatherSourceFiles(files, outputFolder);
174 var variables = GatherPreprocessorVariables(defines);
175 return new CompileCommand(sourceFiles, variables);
176 }
177 }
178
179 return null;
180 }
181
182 private static CommandLine Parse(string commandLineString, Func<CommandLine, string, bool> parseArgument)
183 {
184 var arguments = CommandLine.ParseArgumentsToArray(commandLineString).ToArray();
185
186 return CommandLine.Parse(arguments, null, parseArgument);
187 }
188
189 private static CommandLine Parse(string[] commandLineArguments, Func<CommandLine, string, bool> parseArgument)
190 {
191 return CommandLine.Parse(commandLineArguments, null, parseArgument);
192 }
193
194 private static CommandLine Parse(string[] commandLineArguments, Func<CommandLine, string, bool> parseCommand, Func<CommandLine, string, bool> parseArgument)
195 {
196 var cmdline = new CommandLine();
197
198 cmdline.FlattenArgumentsWithResponseFilesIntoOriginalArguments(commandLineArguments);
199
200 cmdline.QueueArgumentsAndLoadExtensions(cmdline.OriginalArguments);
201
202 cmdline.ProcessRemainingArguments(parseArgument, parseCommand);
203
204 return cmdline;
205 }
206
207 private static IEnumerable<SourceFile> GatherSourceFiles(IEnumerable<string> sourceFiles, string intermediateDirectory)
208 {
209 var files = new List<SourceFile>();
210
211 foreach (var item in sourceFiles)
212 {
213 var sourcePath = item;
214 var outputPath = Path.Combine(intermediateDirectory, Path.GetFileNameWithoutExtension(sourcePath) + ".wir");
215
216 files.Add(new SourceFile(sourcePath, outputPath));
217 }
218
219 return files;
220 }
221
222 private static IDictionary<string, string> GatherPreprocessorVariables(IEnumerable<string> defineConstants)
223 {
224 var variables = new Dictionary<string, string>();
225
226 foreach (var pair in defineConstants)
227 {
228 string[] value = pair.Split(new[] { '=' }, 2);
229
230 if (variables.ContainsKey(value[0]))
231 {
232 Messaging.Instance.OnMessage(WixErrors.DuplicateVariableDefinition(value[0], (1 == value.Length) ? String.Empty : value[1], variables[value[0]]));
233 continue;
234 }
235
236 variables.Add(value[0], (1 == value.Length) ? String.Empty : value[1]);
237 }
238
239 return variables;
240 }
241
242
243 /// <summary>
244 /// Get a set of files that possibly have a search pattern in the path (such as '*').
245 /// </summary>
246 /// <param name="searchPath">Search path to find files in.</param>
247 /// <param name="fileType">Type of file; typically "Source".</param>
248 /// <returns>An array of files matching the search path.</returns>
249 /// <remarks>
250 /// This method is written in this verbose way because it needs to support ".." in the path.
251 /// It needs the directory path isolated from the file name in order to use Directory.GetFiles
252 /// or DirectoryInfo.GetFiles. The only way to get this directory path is manually since
253 /// Path.GetDirectoryName does not support ".." in the path.
254 /// </remarks>
255 /// <exception cref="WixFileNotFoundException">Throws WixFileNotFoundException if no file matching the pattern can be found.</exception>
256 public string[] GetFiles(string searchPath, string fileType)
257 {
258 if (null == searchPath)
259 {
260 throw new ArgumentNullException(nameof(searchPath));
261 }
262
263 // Convert alternate directory separators to the standard one.
264 string filePath = searchPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
265 int lastSeparator = filePath.LastIndexOf(Path.DirectorySeparatorChar);
266 string[] files = null;
267
268 try
269 {
270 if (0 > lastSeparator)
271 {
272 files = Directory.GetFiles(".", filePath);
273 }
274 else // found directory separator
275 {
276 files = Directory.GetFiles(filePath.Substring(0, lastSeparator + 1), filePath.Substring(lastSeparator + 1));
277 }
278 }
279 catch (DirectoryNotFoundException)
280 {
281 // Don't let this function throw the DirectoryNotFoundException. This exception
282 // occurs for non-existant directories and invalid characters in the searchPattern.
283 }
284 catch (ArgumentException)
285 {
286 // Don't let this function throw the ArgumentException. This exception
287 // occurs in certain situations such as when passing a malformed UNC path.
288 }
289 catch (IOException)
290 {
291 throw new WixFileNotFoundException(searchPath, fileType);
292 }
293
294 if (null == files || 0 == files.Length)
295 {
296 throw new WixFileNotFoundException(searchPath, fileType);
297 }
298
299 return files;
300 }
301
302 /// <summary>
303 /// Validates that a valid switch (starts with "/" or "-"), and returns a bool indicating its validity
304 /// </summary>
305 /// <param name="args">The list of strings to check.</param>
306 /// <param name="index">The index (in args) of the commandline parameter to be validated.</param>
307 /// <returns>True if a valid switch exists there, false if not.</returns>
308 public bool IsSwitch(string arg)
309 {
310 return arg != null && ('/' == arg[0] || '-' == arg[0]);
311 }
312
313 /// <summary>
314 /// Validates that a valid switch (starts with "/" or "-"), and returns a bool indicating its validity
315 /// </summary>
316 /// <param name="args">The list of strings to check.</param>
317 /// <param name="index">The index (in args) of the commandline parameter to be validated.</param>
318 /// <returns>True if a valid switch exists there, false if not.</returns>
319 public bool IsSwitchAt(IEnumerable<string> args, int index)
320 {
321 var arg = args.ElementAtOrDefault(index);
322 return IsSwitch(arg);
323 }
324
325 public void GetNextArgumentOrError(ref string arg)
326 {
327 this.TryGetNextArgumentOrError(out arg);
328 }
329
330 public void GetNextArgumentOrError(IList<string> args)
331 {
332 if (this.TryGetNextArgumentOrError(out var arg))
333 {
334 args.Add(arg);
335 }
336 }
337
338 public void GetNextArgumentAsFilePathOrError(IList<string> args, string fileType)
339 {
340 if (this.TryGetNextArgumentOrError(out var arg))
341 {
342 foreach (var path in this.GetFiles(arg, fileType))
343 {
344 args.Add(path);
345 }
346 }
347 }
348
349 public bool TryGetNextArgumentOrError(out string arg)
350 {
351 //if (this.RemainingArguments.TryDequeue(out arg) && !this.IsSwitch(arg))
352 if (TryDequeue(this.RemainingArguments, out arg) && !this.IsSwitch(arg))
353 {
354 return true;
355 }
356
357 this.ErrorArgument = arg ?? CommandLine.ExpectedArgument;
358
359 return false;
360 }
361
362 private static bool TryDequeue(Queue<string> q, out string arg)
363 {
364 if (q.Count> 0)
365 {
366 arg = q.Dequeue();
367 return true;
368 }
369
370 arg = null;
371 return false;
372 }
373
374 private void FlattenArgumentsWithResponseFilesIntoOriginalArguments(string[] commandLineArguments)
375 {
376 List<string> args = new List<string>();
377
378 foreach (var arg in commandLineArguments)
379 {
380 if ('@' == arg[0])
381 {
382 var responseFileArguments = CommandLine.ParseResponseFile(arg.Substring(1));
383 args.AddRange(responseFileArguments);
384 }
385 else
386 {
387 args.Add(arg);
388 }
389 }
390
391 this.OriginalArguments = args.ToArray();
392 }
393
394 private void QueueArgumentsAndLoadExtensions(string[] args)
395 {
396 for (var i = 0; i < args.Length; ++i)
397 {
398 var arg = args[i];
399
400 if ("-ext" == arg || "/ext" == arg)
401 {
402 if (!this.IsSwitchAt(args, ++i))
403 {
404 this.ExtensionManager.Load(args[i]);
405 }
406 else
407 {
408 this.ErrorArgument = arg;
409 break;
410 }
411 }
412 else
413 {
414 this.RemainingArguments.Enqueue(arg);
415 }
416 }
417 }
418
419 private void ProcessRemainingArguments(Func<CommandLine, string, bool> parseArgument, Func<CommandLine, string, bool> parseCommand)
420 {
421 var extensions = this.ExtensionManager.Create<IExtensionCommandLine>();
422
423 while (!this.ShowHelp &&
424 String.IsNullOrEmpty(this.ErrorArgument) &&
425 TryDequeue(this.RemainingArguments, out var arg))
426 {
427 if (String.IsNullOrWhiteSpace(arg)) // skip blank arguments.
428 {
429 continue;
430 }
431
432 if ('-' == arg[0] || '/' == arg[0])
433 {
434 if (!parseArgument(this, arg) &&
435 !this.TryParseCommandLineArgumentWithExtension(arg, extensions))
436 {
437 this.ErrorArgument = arg;
438 }
439 }
440 else if (String.IsNullOrEmpty(this.ActiveCommand) && parseCommand != null) // First non-switch must be the command, if commands are supported.
441 {
442 if (parseCommand(this, arg))
443 {
444 this.ActiveCommand = arg;
445 }
446 else
447 {
448 this.ErrorArgument = arg;
449 }
450 }
451 else if (!this.TryParseCommandLineArgumentWithExtension(arg, extensions) &&
452 !parseArgument(this, arg))
453 {
454 this.ErrorArgument = arg;
455 }
456 }
457 }
458
459 private bool TryParseCommandLineArgumentWithExtension(string arg, IEnumerable<IExtensionCommandLine> extensions)
460 {
461 foreach (var extension in extensions)
462 {
463 //if (extension.ParseArgument(this, arg))
464 //{
465 // return true;
466 //}
467 }
468
469 return false;
470 }
471
472 /// <summary>
473 /// Parses a response file.
474 /// </summary>
475 /// <param name="responseFile">The file to parse.</param>
476 /// <returns>The array of arguments.</returns>
477 private static List<string> ParseResponseFile(string responseFile)
478 {
479 string arguments;
480
481 using (StreamReader reader = new StreamReader(responseFile))
482 {
483 arguments = reader.ReadToEnd();
484 }
485
486 return CommandLine.ParseArgumentsToArray(arguments);
487 }
488
489 /// <summary>
490 /// Parses an argument string into an argument array based on whitespace and quoting.
491 /// </summary>
492 /// <param name="arguments">Argument string.</param>
493 /// <returns>Argument array.</returns>
494 private static List<string> ParseArgumentsToArray(string arguments)
495 {
496 // Scan and parse the arguments string, dividing up the arguments based on whitespace.
497 // Unescaped quotes cause whitespace to be ignored, while the quotes themselves are removed.
498 // Quotes may begin and end inside arguments; they don't necessarily just surround whole arguments.
499 // Escaped quotes and escaped backslashes also need to be unescaped by this process.
500
501 // Collects the final list of arguments to be returned.
502 var argsList = new List<string>();
503
504 // True if we are inside an unescaped quote, meaning whitespace should be ignored.
505 var insideQuote = false;
506
507 // Index of the start of the current argument substring; either the start of the argument
508 // or the start of a quoted or unquoted sequence within it.
509 var partStart = 0;
510
511 // The current argument string being built; when completed it will be added to the list.
512 var arg = new StringBuilder();
513
514 for (int i = 0; i <= arguments.Length; i++)
515 {
516 if (i == arguments.Length || (Char.IsWhiteSpace(arguments[i]) && !insideQuote))
517 {
518 // Reached a whitespace separator or the end of the string.
519
520 // Finish building the current argument.
521 arg.Append(arguments.Substring(partStart, i - partStart));
522
523 // Skip over the whitespace character.
524 partStart = i + 1;
525
526 // Add the argument to the list if it's not empty.
527 if (arg.Length > 0)
528 {
529 argsList.Add(CommandLine.ExpandEnvVars(arg.ToString()));
530 arg.Length = 0;
531 }
532 }
533 else if (i > partStart && arguments[i - 1] == '\\')
534 {
535 // Check the character following an unprocessed backslash.
536 // Unescape quotes, and backslashes followed by a quote.
537 if (arguments[i] == '"' || (arguments[i] == '\\' && arguments.Length > i + 1 && arguments[i + 1] == '"'))
538 {
539 // Unescape the quote or backslash by skipping the preceeding backslash.
540 arg.Append(arguments.Substring(partStart, i - 1 - partStart));
541 arg.Append(arguments[i]);
542 partStart = i + 1;
543 }
544 }
545 else if (arguments[i] == '"')
546 {
547 // Add the quoted or unquoted section to the argument string.
548 arg.Append(arguments.Substring(partStart, i - partStart));
549
550 // And skip over the quote character.
551 partStart = i + 1;
552
553 insideQuote = !insideQuote;
554 }
555 }
556
557 return argsList;
558 }
559
560 /// <summary>
561 /// Expand enxironment variables contained in the passed string
562 /// </summary>
563 /// <param name="arguments"></param>
564 /// <returns></returns>
565 private static string ExpandEnvVars(string arguments)
566 {
567 var id = Environment.GetEnvironmentVariables();
568
569 var regex = new Regex("(?<=\\%)(?:[\\w\\.]+)(?=\\%)");
570 MatchCollection matches = regex.Matches(arguments);
571
572 string value = String.Empty;
573 for (int i = 0; i <= (matches.Count - 1); i++)
574 {
575 try
576 {
577 var key = matches[i].Value;
578 regex = new Regex(String.Concat("(?i)(?:\\%)(?:", key, ")(?:\\%)"));
579 value = id[key].ToString();
580 arguments = regex.Replace(arguments, value);
581 }
582 catch (NullReferenceException)
583 {
584 // Collapse unresolved environment variables.
585 arguments = regex.Replace(arguments, value);
586 }
587 }
588
589 return arguments;
590 }
591 }
592}
diff --git a/src/WixToolset.Core/CommandLine/CommandLineHelper.cs b/src/WixToolset.Core/CommandLine/CommandLineHelper.cs
new file mode 100644
index 00000000..86724603
--- /dev/null
+++ b/src/WixToolset.Core/CommandLine/CommandLineHelper.cs
@@ -0,0 +1,255 @@
1// Copyright (c) .NET Foundation and 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.Collections.Generic;
7 using System.IO;
8 using System.Text;
9 using WixToolset.Data;
10 using WixToolset.Extensibility;
11
12 /// <summary>
13 /// Common utilities for Wix command-line processing.
14 /// </summary>
15 public static class CommandLineHelper
16 {
17 /// <summary>
18 /// Get a set of files that possibly have a search pattern in the path (such as '*').
19 /// </summary>
20 /// <param name="searchPath">Search path to find files in.</param>
21 /// <param name="fileType">Type of file; typically "Source".</param>
22 /// <returns>An array of files matching the search path.</returns>
23 /// <remarks>
24 /// This method is written in this verbose way because it needs to support ".." in the path.
25 /// It needs the directory path isolated from the file name in order to use Directory.GetFiles
26 /// or DirectoryInfo.GetFiles. The only way to get this directory path is manually since
27 /// Path.GetDirectoryName does not support ".." in the path.
28 /// </remarks>
29 /// <exception cref="WixFileNotFoundException">Throws WixFileNotFoundException if no file matching the pattern can be found.</exception>
30 public static string[] GetFiles(string searchPath, string fileType)
31 {
32 if (null == searchPath)
33 {
34 throw new ArgumentNullException("searchPath");
35 }
36
37 // convert alternate directory separators to the standard one
38 string filePath = searchPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
39 int lastSeparator = filePath.LastIndexOf(Path.DirectorySeparatorChar);
40 string[] files = null;
41
42 try
43 {
44 if (0 > lastSeparator)
45 {
46 files = Directory.GetFiles(".", filePath);
47 }
48 else // found directory separator
49 {
50 files = Directory.GetFiles(filePath.Substring(0, lastSeparator + 1), filePath.Substring(lastSeparator + 1));
51 }
52 }
53 catch (DirectoryNotFoundException)
54 {
55 // don't let this function throw the DirectoryNotFoundException. (this exception
56 // occurs for non-existant directories and invalid characters in the searchPattern)
57 }
58 catch (ArgumentException)
59 {
60 // don't let this function throw the ArgumentException. (this exception
61 // occurs in certain situations such as when passing a malformed UNC path)
62 }
63 catch (IOException)
64 {
65 throw new WixFileNotFoundException(searchPath, fileType);
66 }
67
68 // file could not be found or path is invalid in some way
69 if (null == files || 0 == files.Length)
70 {
71 throw new WixFileNotFoundException(searchPath, fileType);
72 }
73
74 return files;
75 }
76
77 /// <summary>
78 /// Validates that a valid string parameter (without "/" or "-"), and returns a bool indicating its validity
79 /// </summary>
80 /// <param name="args">The list of strings to check.</param>
81 /// <param name="index">The index (in args) of the commandline parameter to be validated.</param>
82 /// <returns>True if a valid string parameter exists there, false if not.</returns>
83 public static bool IsValidArg(string[] args, int index)
84 {
85 if (args.Length <= index || String.IsNullOrEmpty(args[index]) || '/' == args[index][0] || '-' == args[index][0])
86 {
87 return false;
88 }
89 else
90 {
91 return true;
92 }
93 }
94
95 /// <summary>
96 /// Validates that a commandline parameter is a valid file or directory name, and throws appropriate warnings/errors if not
97 /// </summary>
98 /// <param name="path">The path to test.</param>
99 /// <returns>The string if it is valid, null if it is invalid.</returns>
100 public static string VerifyPath(string path)
101 {
102 return VerifyPath(path, false);
103 }
104
105 /// <summary>
106 /// Validates that a commandline parameter is a valid file or directory name, and throws appropriate warnings/errors if not
107 /// </summary>
108 /// <param name="path">The path to test.</param>
109 /// <param name="allowPrefix">Indicates if a colon-delimited prefix is allowed.</param>
110 /// <returns>The full path if it is valid, null if it is invalid.</returns>
111 public static string VerifyPath(string path, bool allowPrefix)
112 {
113 string fullPath;
114
115 if (0 <= path.IndexOf('\"'))
116 {
117 Messaging.Instance.OnMessage(WixErrors.PathCannotContainQuote(path));
118 return null;
119 }
120
121 try
122 {
123 string prefix = null;
124 if (allowPrefix)
125 {
126 int prefixLength = path.IndexOf('=') + 1;
127 if (0 != prefixLength)
128 {
129 prefix = path.Substring(0, prefixLength);
130 path = path.Substring(prefixLength);
131 }
132 }
133
134 if (String.IsNullOrEmpty(prefix))
135 {
136 fullPath = Path.GetFullPath(path);
137 }
138 else
139 {
140 fullPath = String.Concat(prefix, Path.GetFullPath(path));
141 }
142 }
143 catch (Exception e)
144 {
145 Messaging.Instance.OnMessage(WixErrors.InvalidCommandLineFileName(path, e.Message));
146 return null;
147 }
148
149 return fullPath;
150 }
151
152 /// <summary>
153 /// Validates that a string is a valid bind path, and throws appropriate warnings/errors if not
154 /// </summary>
155 /// <param name="commandlineSwitch">The commandline switch we're parsing (for error display purposes).</param>
156 /// <param name="args">The list of strings to check.</param>
157 /// <param name="index">The index (in args) of the commandline parameter to be parsed.</param>
158 /// <returns>The bind path if it is valid, null if it is invalid.</returns>
159 public static BindPath GetBindPath(string commandlineSwitch, string[] args, int index)
160 {
161 commandlineSwitch = String.Concat("-", commandlineSwitch);
162
163 if (!IsValidArg(args, index))
164 {
165 Messaging.Instance.OnMessage(WixErrors.DirectoryPathRequired(commandlineSwitch));
166 return null;
167 }
168
169 BindPath bindPath = BindPath.Parse(args[index]);
170
171 if (File.Exists(bindPath.Path))
172 {
173 Messaging.Instance.OnMessage(WixErrors.ExpectedDirectoryGotFile(commandlineSwitch, bindPath.Path));
174 return null;
175 }
176
177 bindPath.Path = VerifyPath(bindPath.Path, true);
178 return String.IsNullOrEmpty(bindPath.Path) ? null : bindPath;
179 }
180
181 /// <summary>
182 /// Validates that a commandline parameter is a valid file or directory name, and throws appropriate warnings/errors if not
183 /// </summary>
184 /// <param name="commandlineSwitch">The commandline switch we're parsing (for error display purposes).</param>
185 /// <param name="messageHandler">The messagehandler to report warnings/errors to.</param>
186 /// <param name="args">The list of strings to check.</param>
187 /// <param name="index">The index (in args) of the commandline parameter to be parsed.</param>
188 /// <returns>The string if it is valid, null if it is invalid.</returns>
189 public static string GetFileOrDirectory(string commandlineSwitch, string[] args, int index)
190 {
191 commandlineSwitch = String.Concat("-", commandlineSwitch);
192
193 if (!IsValidArg(args, index))
194 {
195 Messaging.Instance.OnMessage(WixErrors.FileOrDirectoryPathRequired(commandlineSwitch));
196 return null;
197 }
198
199 return VerifyPath(args[index]);
200 }
201
202 /// <summary>
203 /// Validates that a string is a valid directory name, and throws appropriate warnings/errors if not
204 /// </summary>
205 /// <param name="commandlineSwitch">The commandline switch we're parsing (for error display purposes).</param>
206 /// <param name="args">The list of strings to check.</param>
207 /// <param name="index">The index (in args) of the commandline parameter to be parsed.</param>
208 /// <param name="allowPrefix">Indicates if a colon-delimited prefix is allowed.</param>
209 /// <returns>The string if it is valid, null if it is invalid.</returns>
210 public static string GetDirectory(string commandlineSwitch, string[] args, int index, bool allowPrefix = false)
211 {
212 commandlineSwitch = String.Concat("-", commandlineSwitch);
213
214 if (!IsValidArg(args, index))
215 {
216 Messaging.Instance.OnMessage(WixErrors.DirectoryPathRequired(commandlineSwitch));
217 return null;
218 }
219
220 if (File.Exists(args[index]))
221 {
222 Messaging.Instance.OnMessage(WixErrors.ExpectedDirectoryGotFile(commandlineSwitch, args[index]));
223 return null;
224 }
225
226 return VerifyPath(args[index], allowPrefix);
227 }
228
229 /// <summary>
230 /// Validates that a string is a valid filename, and throws appropriate warnings/errors if not
231 /// </summary>
232 /// <param name="commandlineSwitch">The commandline switch we're parsing (for error display purposes).</param>
233 /// <param name="args">The list of strings to check.</param>
234 /// <param name="index">The index (in args) of the commandline parameter to be parsed.</param>
235 /// <returns>The string if it is valid, null if it is invalid.</returns>
236 public static string GetFile(string commandlineSwitch, string[] args, int index)
237 {
238 commandlineSwitch = String.Concat("-", commandlineSwitch);
239
240 if (!IsValidArg(args, index))
241 {
242 Messaging.Instance.OnMessage(WixErrors.FilePathRequired(commandlineSwitch));
243 return null;
244 }
245
246 if (Directory.Exists(args[index]))
247 {
248 Messaging.Instance.OnMessage(WixErrors.ExpectedFileGotDirectory(commandlineSwitch, args[index]));
249 return null;
250 }
251
252 return VerifyPath(args[index]);
253 }
254 }
255}
diff --git a/src/WixToolset.Core/CommandLine/CommandLineOption.cs b/src/WixToolset.Core/CommandLine/CommandLineOption.cs
new file mode 100644
index 00000000..85a654bf
--- /dev/null
+++ b/src/WixToolset.Core/CommandLine/CommandLineOption.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
4{
5 /// <summary>
6 /// A command line option.
7 /// </summary>
8 public struct CommandLineOption
9 {
10 public string Option;
11 public string Description;
12 public int AdditionalArguments;
13
14 /// <summary>
15 /// Instantiates a new BuilderCommandLineOption.
16 /// </summary>
17 /// <param name="option">The option name.</param>
18 /// <param name="description">The description of the option.</param>
19 /// <param name="additionalArguments">Count of additional arguments to require after this switch.</param>
20 public CommandLineOption(string option, string description, int additionalArguments)
21 {
22 this.Option = option;
23 this.Description = description;
24 this.AdditionalArguments = additionalArguments;
25 }
26 }
27}
diff --git a/src/WixToolset.Core/CommandLine/CommandLineResponseFile.cs b/src/WixToolset.Core/CommandLine/CommandLineResponseFile.cs
new file mode 100644
index 00000000..f27296b7
--- /dev/null
+++ b/src/WixToolset.Core/CommandLine/CommandLineResponseFile.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
4{
5 using System;
6 using System.Collections;
7 using System.Collections.Generic;
8 using System.IO;
9 using System.Text;
10 using System.Text.RegularExpressions;
11
12 /// <summary>
13 /// Common utilities for Wix command-line processing.
14 /// </summary>
15 public static class CommandLineResponseFile
16 {
17 /// <summary>
18 /// Parses a response file.
19 /// </summary>
20 /// <param name="responseFile">The file to parse.</param>
21 /// <returns>The array of arguments.</returns>
22 public static string[] Parse(string responseFile)
23 {
24 string arguments;
25
26 using (StreamReader reader = new StreamReader(responseFile))
27 {
28 arguments = reader.ReadToEnd();
29 }
30
31 return CommandLineResponseFile.ParseArgumentsToArray(arguments);
32 }
33
34 /// <summary>
35 /// Parses an argument string into an argument array based on whitespace and quoting.
36 /// </summary>
37 /// <param name="arguments">Argument string.</param>
38 /// <returns>Argument array.</returns>
39 public static string[] ParseArgumentsToArray(string arguments)
40 {
41 // Scan and parse the arguments string, dividing up the arguments based on whitespace.
42 // Unescaped quotes cause whitespace to be ignored, while the quotes themselves are removed.
43 // Quotes may begin and end inside arguments; they don't necessarily just surround whole arguments.
44 // Escaped quotes and escaped backslashes also need to be unescaped by this process.
45
46 // Collects the final list of arguments to be returned.
47 List<string> argsList = new List<string>();
48
49 // True if we are inside an unescaped quote, meaning whitespace should be ignored.
50 bool insideQuote = false;
51
52 // Index of the start of the current argument substring; either the start of the argument
53 // or the start of a quoted or unquoted sequence within it.
54 int partStart = 0;
55
56 // The current argument string being built; when completed it will be added to the list.
57 StringBuilder arg = new StringBuilder();
58
59 for (int i = 0; i <= arguments.Length; i++)
60 {
61 if (i == arguments.Length || (Char.IsWhiteSpace(arguments[i]) && !insideQuote))
62 {
63 // Reached a whitespace separator or the end of the string.
64
65 // Finish building the current argument.
66 arg.Append(arguments.Substring(partStart, i - partStart));
67
68 // Skip over the whitespace character.
69 partStart = i + 1;
70
71 // Add the argument to the list if it's not empty.
72 if (arg.Length > 0)
73 {
74 argsList.Add(CommandLineResponseFile.ExpandEnvVars(arg.ToString()));
75 arg.Length = 0;
76 }
77 }
78 else if (i > partStart && arguments[i - 1] == '\\')
79 {
80 // Check the character following an unprocessed backslash.
81 // Unescape quotes, and backslashes followed by a quote.
82 if (arguments[i] == '"' || (arguments[i] == '\\' && arguments.Length > i + 1 && arguments[i + 1] == '"'))
83 {
84 // Unescape the quote or backslash by skipping the preceeding backslash.
85 arg.Append(arguments.Substring(partStart, i - 1 - partStart));
86 arg.Append(arguments[i]);
87 partStart = i + 1;
88 }
89 }
90 else if (arguments[i] == '"')
91 {
92 // Add the quoted or unquoted section to the argument string.
93 arg.Append(arguments.Substring(partStart, i - partStart));
94
95 // And skip over the quote character.
96 partStart = i + 1;
97
98 insideQuote = !insideQuote;
99 }
100 }
101
102 return argsList.ToArray();
103 }
104
105 /// <summary>
106 /// Expand enxironment variables contained in the passed string
107 /// </summary>
108 /// <param name="arguments"></param>
109 /// <returns></returns>
110 static private string ExpandEnvVars(string arguments)
111 {
112 IDictionary id = Environment.GetEnvironmentVariables();
113
114 Regex regex = new Regex("(?<=\\%)(?:[\\w\\.]+)(?=\\%)");
115 MatchCollection matches = regex.Matches(arguments);
116
117 string value = String.Empty;
118 for (int i = 0; i <= (matches.Count - 1); i++)
119 {
120 try
121 {
122 string key = matches[i].Value;
123 regex = new Regex(String.Concat("(?i)(?:\\%)(?:" , key , ")(?:\\%)"));
124 value = id[key].ToString();
125 arguments = regex.Replace(arguments, value);
126 }
127 catch (NullReferenceException)
128 {
129 // Collapse unresolved environment variables.
130 arguments = regex.Replace(arguments, value);
131 }
132 }
133
134 return arguments;
135 }
136 }
137}
diff --git a/src/WixToolset.Core/CommandLine/CompileCommand.cs b/src/WixToolset.Core/CommandLine/CompileCommand.cs
new file mode 100644
index 00000000..17847b57
--- /dev/null
+++ b/src/WixToolset.Core/CommandLine/CompileCommand.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
4{
5 using System;
6 using System.Collections.Generic;
7 using WixToolset.Data;
8
9 internal class CompileCommand : ICommand
10 {
11 public CompileCommand(IEnumerable<SourceFile> sources, IDictionary<string, string> preprocessorVariables)
12 {
13 this.PreprocessorVariables = preprocessorVariables;
14 this.SourceFiles = sources;
15 }
16
17 private IEnumerable<SourceFile> SourceFiles { get; }
18
19 private IDictionary<string, string> PreprocessorVariables { get; }
20
21 public int Execute()
22 {
23 var preprocessor = new Preprocessor();
24
25 var compiler = new Compiler();
26
27 foreach (var sourceFile in this.SourceFiles)
28 {
29 var document = preprocessor.Process(sourceFile.SourcePath, this.PreprocessorVariables);
30
31 var intermediate = compiler.Compile(document);
32
33 intermediate.Save(sourceFile.OutputPath);
34 }
35
36 return 0;
37 }
38 }
39}
diff --git a/src/WixToolset.Core/CommandLine/HelpCommand.cs b/src/WixToolset.Core/CommandLine/HelpCommand.cs
new file mode 100644
index 00000000..1c101781
--- /dev/null
+++ b/src/WixToolset.Core/CommandLine/HelpCommand.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
4{
5 using System;
6
7 internal class HelpCommand : ICommand
8 {
9 public HelpCommand(Commands command)
10 {
11 this.Command = command;
12 }
13
14 public Commands Command { get; }
15
16 public int Execute()
17 {
18 if (this.Command == Commands.Unknown)
19 {
20 Console.WriteLine();
21 }
22 else
23 {
24 Console.WriteLine();
25 }
26
27 return -1;
28 }
29 }
30}
diff --git a/src/WixToolset.Core/CommandLine/ICommand.cs b/src/WixToolset.Core/CommandLine/ICommand.cs
new file mode 100644
index 00000000..41abbbdc
--- /dev/null
+++ b/src/WixToolset.Core/CommandLine/ICommand.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
3namespace WixToolset.Core
4{
5 public interface ICommand
6 {
7 int Execute();
8 }
9}
diff --git a/src/WixToolset.Core/CommandLine/VersionCommand.cs b/src/WixToolset.Core/CommandLine/VersionCommand.cs
new file mode 100644
index 00000000..a1980a2b
--- /dev/null
+++ b/src/WixToolset.Core/CommandLine/VersionCommand.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
3using System;
4
5namespace WixToolset.Core
6{
7 internal class VersionCommand : ICommand
8 {
9 public int Execute()
10 {
11 Console.WriteLine("wix version {0}", ThisAssembly.AssemblyInformationalVersion);
12 Console.WriteLine();
13
14 return 0;
15 }
16 }
17}
diff --git a/src/WixToolset.Core/Common.cs b/src/WixToolset.Core/Common.cs
new file mode 100644
index 00000000..0404de5e
--- /dev/null
+++ b/src/WixToolset.Core/Common.cs
@@ -0,0 +1,665 @@
1// Copyright (c) .NET Foundation and 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.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.Text.RegularExpressions;
13 using System.Xml;
14 using System.Xml.Linq;
15 using WixToolset.Data;
16
17 /// <summary>
18 /// Common Wix utility methods and types.
19 /// </summary>
20 internal static class Common
21 {
22 //-------------------------------------------------------------------------------------------------
23 // Layout of an Access Mask (from http://technet.microsoft.com/en-us/library/cc783530(WS.10).aspx)
24 //
25 // -------------------------------------------------------------------------------------------------
26 // |31|30|29|28|27|26|25|24|23|22|21|20|19|18|17|16|15|14|13|12|11|10|09|08|07|06|05|04|03|02|01|00|
27 // -------------------------------------------------------------------------------------------------
28 // |GR|GW|GE|GA| Reserved |AS|StandardAccessRights| Object-Specific Access Rights |
29 //
30 // Key
31 // GR = Generic Read
32 // GW = Generic Write
33 // GE = Generic Execute
34 // GA = Generic All
35 // AS = Right to access SACL
36 //
37 // TODO: what is the expected decompile behavior if a bit is found that is not explicitly enumerated
38 //
39 //-------------------------------------------------------------------------------------------------
40 // Generic Access Rights (per WinNT.h)
41 // ---------------------
42 // GENERIC_ALL (0x10000000L)
43 // GENERIC_EXECUTE (0x20000000L)
44 // GENERIC_WRITE (0x40000000L)
45 // GENERIC_READ (0x80000000L)
46 internal static readonly string[] GenericPermissions = { "GenericAll", "GenericExecute", "GenericWrite", "GenericRead" };
47
48 // Standard Access Rights (per WinNT.h)
49 // ----------------------
50 // DELETE (0x00010000L)
51 // READ_CONTROL (0x00020000L)
52 // WRITE_DAC (0x00040000L)
53 // WRITE_OWNER (0x00080000L)
54 // SYNCHRONIZE (0x00100000L)
55 internal static readonly string[] StandardPermissions = { "Delete", "ReadPermission", "ChangePermission", "TakeOwnership", "Synchronize" };
56
57 // Object-Specific Access Rights
58 // =============================
59 // Directory Access Rights (per WinNT.h)
60 // -----------------------
61 // FILE_LIST_DIRECTORY ( 0x0001 )
62 // FILE_ADD_FILE ( 0x0002 )
63 // FILE_ADD_SUBDIRECTORY ( 0x0004 )
64 // FILE_READ_EA ( 0x0008 )
65 // FILE_WRITE_EA ( 0x0010 )
66 // FILE_TRAVERSE ( 0x0020 )
67 // FILE_DELETE_CHILD ( 0x0040 )
68 // FILE_READ_ATTRIBUTES ( 0x0080 )
69 // FILE_WRITE_ATTRIBUTES ( 0x0100 )
70 internal static readonly string[] FolderPermissions = { "Read", "CreateFile", "CreateChild", "ReadExtendedAttributes", "WriteExtendedAttributes", "Traverse", "DeleteChild", "ReadAttributes", "WriteAttributes" };
71
72 // Registry Access Rights (per TODO)
73 // ----------------------
74 internal static readonly string[] RegistryPermissions = { "Read", "Write", "CreateSubkeys", "EnumerateSubkeys", "Notify", "CreateLink" };
75
76 // File Access Rights (per WinNT.h)
77 // ------------------
78 // FILE_READ_DATA ( 0x0001 )
79 // FILE_WRITE_DATA ( 0x0002 )
80 // FILE_APPEND_DATA ( 0x0004 )
81 // FILE_READ_EA ( 0x0008 )
82 // FILE_WRITE_EA ( 0x0010 )
83 // FILE_EXECUTE ( 0x0020 )
84 // via mask FILE_ALL_ACCESS ( 0x0040 )
85 // FILE_READ_ATTRIBUTES ( 0x0080 )
86 // FILE_WRITE_ATTRIBUTES ( 0x0100 )
87 //
88 // STANDARD_RIGHTS_REQUIRED (0x000F0000L)
89 // FILE_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x1FF)
90 internal static readonly string[] FilePermissions = { "Read", "Write", "Append", "ReadExtendedAttributes", "WriteExtendedAttributes", "Execute", "FileAllRights", "ReadAttributes", "WriteAttributes" };
91
92 internal static readonly string[] ReservedFileNames = { "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9" };
93
94 internal static readonly Regex WixVariableRegex = new Regex(@"(\!|\$)\((?<namespace>loc|wix|bind|bindpath)\.(?<fullname>(?<name>[_A-Za-z][0-9A-Za-z_]+)(\.(?<scope>[_A-Za-z][0-9A-Za-z_\.]*))?)(\=(?<value>.+?))?\)", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture);
95
96 internal const char CustomRowFieldSeparator = '\x85';
97
98 private static readonly Regex PropertySearch = new Regex(@"\[[#$!]?[a-zA-Z_][a-zA-Z0-9_\.]*]", RegexOptions.Singleline);
99 private static readonly Regex AddPrefix = new Regex(@"^[^a-zA-Z_]", RegexOptions.Compiled);
100 private static readonly Regex LegalIdentifierCharacters = new Regex(@"^[_A-Za-z][0-9A-Za-z_\.]*$", RegexOptions.Compiled);
101 private static readonly Regex IllegalIdentifierCharacters = new Regex(@"[^A-Za-z0-9_\.]|\.{2,}", RegexOptions.Compiled); // non 'words' and assorted valid characters
102
103 private const string LegalShortFilenameCharacters = @"[^\\\?|><:/\*""\+,;=\[\]\. ]"; // illegal: \ ? | > < : / * " + , ; = [ ] . (space)
104 private static readonly Regex LegalShortFilename = new Regex(String.Concat("^", LegalShortFilenameCharacters, @"{1,8}(\.", LegalShortFilenameCharacters, "{0,3})?$"), RegexOptions.Compiled);
105
106 private const string LegalWildcardShortFilenameCharacters = @"[^\\|><:/""\+,;=\[\]\. ]"; // illegal: \ | > < : / " + , ; = [ ] . (space)
107 private static readonly Regex LegalWildcardShortFilename = new Regex(String.Concat("^", LegalWildcardShortFilenameCharacters, @"{1,16}(\.", LegalWildcardShortFilenameCharacters, "{0,6})?$"));
108
109 /// <summary>
110 /// Cleans up the temp files.
111 /// </summary>
112 /// <param name="path">The temporary directory to delete.</param>
113 /// <param name="messageHandler">The message handler.</param>
114 /// <returns>True if all files were deleted, false otherwise.</returns>
115 internal static bool DeleteTempFiles(string path, IMessageHandler messageHandler)
116 {
117 // try three times and give up with a warning if the temp files aren't gone by then
118 int retryLimit = 3;
119 bool removedReadOnly = false;
120
121 for (int i = 0; i < retryLimit; i++)
122 {
123 try
124 {
125 Directory.Delete(path, true); // toast the whole temp directory
126 break; // no exception means we got success the first time
127 }
128 catch (UnauthorizedAccessException)
129 {
130 if (!removedReadOnly) // should only need to unmark readonly once - there's no point in doing it again and again
131 {
132 removedReadOnly = true;
133 RecursiveFileAttributes(path, FileAttributes.ReadOnly, false, messageHandler); // toasting will fail if any files are read-only. Try changing them to not be.
134 }
135 else
136 {
137 messageHandler.OnMessage(WixWarnings.AccessDeniedForDeletion(null, path));
138 return false;
139 }
140 }
141 catch (DirectoryNotFoundException)
142 {
143 // if the path doesn't exist, then there is nothing for us to worry about
144 break;
145 }
146 catch (IOException) // directory in use
147 {
148 if (i == (retryLimit - 1)) // last try failed still, give up
149 {
150 messageHandler.OnMessage(WixWarnings.DirectoryInUse(null, path));
151 return false;
152 }
153
154 System.Threading.Thread.Sleep(300); // sleep a bit before trying again
155 }
156 }
157
158 return true;
159 }
160
161 /// <summary>
162 /// Gets a valid code page from the given web name or integer value.
163 /// </summary>
164 /// <param name="value">A code page web name or integer value as a string.</param>
165 /// <param name="allowNoChange">Whether to allow -1 which does not change the database code pages. This may be the case with wxl files.</param>
166 /// <param name="onlyAnsi">Whether to allow Unicode (UCS) or UTF code pages.</param>
167 /// <param name="sourceLineNumbers">Source line information for the current authoring.</param>
168 /// <returns>A valid code page number.</returns>
169 /// <exception cref="ArgumentOutOfRangeException">The value is an integer less than 0 or greater than 65535.</exception>
170 /// <exception cref="ArgumentNullException"><paramref name="value"/> is null.</exception>
171 /// <exception cref="NotSupportedException">The value doesn't not represent a valid code page name or integer value.</exception>
172 /// <exception cref="WixException">The code page is invalid for summary information.</exception>
173 internal static int GetValidCodePage(string value, bool allowNoChange = false, bool onlyAnsi = false, SourceLineNumber sourceLineNumbers = null)
174 {
175 int codePage;
176 Encoding encoding;
177
178 try
179 {
180 // check if a integer as a string was passed
181 if (Int32.TryParse(value, out codePage))
182 {
183 if (0 == codePage)
184 {
185 // 0 represents a neutral database
186 return 0;
187 }
188 else if (allowNoChange && -1 == codePage)
189 {
190 // -1 means no change to the database code page
191 return -1;
192 }
193
194 encoding = Encoding.GetEncoding(codePage);
195 }
196 else
197 {
198 encoding = Encoding.GetEncoding(value);
199 }
200
201 // Windows Installer parses some code page references
202 // as unsigned shorts which fail to open the database.
203 if (onlyAnsi)
204 {
205 codePage = encoding.CodePage;
206 if (0 > codePage || Int16.MaxValue < codePage)
207 {
208 throw new WixException(WixErrors.InvalidSummaryInfoCodePage(sourceLineNumbers, codePage));
209 }
210 }
211
212 return encoding.CodePage;
213 }
214 catch (ArgumentException ex)
215 {
216 // rethrow as NotSupportedException since either can be thrown
217 // if the system does not support the specified code page
218 throw new NotSupportedException(ex.Message, ex);
219 }
220 }
221
222 /// <summary>
223 /// Verifies if a filename is a valid short filename.
224 /// </summary>
225 /// <param name="filename">Filename to verify.</param>
226 /// <param name="allowWildcards">true if wildcards are allowed in the filename.</param>
227 /// <returns>True if the filename is a valid short filename</returns>
228 internal static bool IsValidShortFilename(string filename, bool allowWildcards)
229 {
230 if (String.IsNullOrEmpty(filename))
231 {
232 return false;
233 }
234
235 if (allowWildcards)
236 {
237 if (Common.LegalWildcardShortFilename.IsMatch(filename))
238 {
239 bool foundPeriod = false;
240 int beforePeriod = 0;
241 int afterPeriod = 0;
242
243 // count the number of characters before and after the period
244 // '*' is not counted because it may represent zero characters
245 foreach (char character in filename)
246 {
247 if ('.' == character)
248 {
249 foundPeriod = true;
250 }
251 else if ('*' != character)
252 {
253 if (foundPeriod)
254 {
255 afterPeriod++;
256 }
257 else
258 {
259 beforePeriod++;
260 }
261 }
262 }
263
264 if (8 >= beforePeriod && 3 >= afterPeriod)
265 {
266 return true;
267 }
268 }
269
270 return false;
271 }
272 else
273 {
274 return Common.LegalShortFilename.IsMatch(filename);
275 }
276 }
277
278 /// <summary>
279 /// Verifies if an identifier is a valid binder variable name.
280 /// </summary>
281 /// <param name="name">Binder variable name to verify.</param>
282 /// <returns>True if the identifier is a valid binder variable name.</returns>
283 public static bool IsValidBinderVariable(string name)
284 {
285 if (String.IsNullOrEmpty(name))
286 {
287 return false;
288 }
289
290 Match match = Common.WixVariableRegex.Match(name);
291
292 return (match.Success && ("bind" == match.Groups["namespace"].Value || "wix" == match.Groups["namespace"].Value) && 0 == match.Index && name.Length == match.Length);
293 }
294
295 /// <summary>
296 /// Verifies if a string contains a valid binder variable name.
297 /// </summary>
298 /// <param name="name">String to verify.</param>
299 /// <returns>True if the string contains a valid binder variable name.</returns>
300 public static bool ContainsValidBinderVariable(string name)
301 {
302 if (String.IsNullOrEmpty(name))
303 {
304 return false;
305 }
306
307 Match match = Common.WixVariableRegex.Match(name);
308
309 return match.Success && ("bind" == match.Groups["namespace"].Value || "wix" == match.Groups["namespace"].Value);
310 }
311
312 /// <summary>
313 /// Get the value of an attribute with type YesNoType.
314 /// </summary>
315 /// <param name="sourceLineNumbers">Source information for the value.</param>
316 /// <param name="elementName">Name of the element for this attribute, used for a possible exception.</param>
317 /// <param name="attributeName">Name of the attribute.</param>
318 /// <param name="value">Value to process.</param>
319 /// <returns>Returns true for a value of 'yes' and false for a value of 'no'.</returns>
320 /// <exception cref="WixException">Thrown when the attribute's value is not 'yes' or 'no'.</exception>
321 internal static bool IsYes(SourceLineNumber sourceLineNumbers, string elementName, string attributeName, string value)
322 {
323 switch (value)
324 {
325 case "no":
326 case "false":
327 return false;
328 case "yes":
329 case "true":
330 return true;
331 default:
332 throw new WixException(WixErrors.IllegalAttributeValue(sourceLineNumbers, elementName, attributeName, value, "no", "yes"));
333 }
334 }
335
336 /// <summary>
337 /// Verifies the given string is a valid module or bundle version.
338 /// </summary>
339 /// <param name="version">The version to verify.</param>
340 /// <returns>True if version is a valid module or bundle version.</returns>
341 public static bool IsValidModuleOrBundleVersion(string version)
342 {
343 if (!Common.IsValidBinderVariable(version))
344 {
345 Version ver = null;
346
347 try
348 {
349 ver = new Version(version);
350 }
351 catch (ArgumentException)
352 {
353 return false;
354 }
355
356 if (65535 < ver.Major || 65535 < ver.Minor || 65535 < ver.Build || 65535 < ver.Revision)
357 {
358 return false;
359 }
360 }
361
362 return true;
363 }
364
365 /// <summary>
366 /// Generate a new Windows Installer-friendly guid.
367 /// </summary>
368 /// <returns>A new guid.</returns>
369 internal static string GenerateGuid()
370 {
371 return Guid.NewGuid().ToString("B").ToUpper(CultureInfo.InvariantCulture);
372 }
373
374 /// <summary>
375 /// Generate an identifier by hashing data from the row.
376 /// </summary>
377 /// <param name="prefix">Three letter or less prefix for generated row identifier.</param>
378 /// <param name="args">Information to hash.</param>
379 /// <returns>The generated identifier.</returns>
380 public static string GenerateIdentifier(string prefix, params string[] args)
381 {
382 string stringData = String.Join("|", args);
383 byte[] data = Encoding.UTF8.GetBytes(stringData);
384
385 // hash the data
386 byte[] hash;
387 using (SHA1 sha1 = new SHA1CryptoServiceProvider())
388 {
389 hash = sha1.ComputeHash(data);
390 }
391
392 // Build up the identifier.
393 StringBuilder identifier = new StringBuilder(35, 35);
394 identifier.Append(prefix);
395 identifier.Append(Convert.ToBase64String(hash).TrimEnd('='));
396 identifier.Replace('+', '.').Replace('/', '_');
397
398 return identifier.ToString();
399 }
400
401 /// <summary>
402 /// Return an identifier based on provided file or directory name
403 /// </summary>
404 /// <param name="name">File/directory name to generate identifer from</param>
405 /// <returns>A version of the name that is a legal identifier.</returns>
406 internal static string GetIdentifierFromName(string name)
407 {
408 string result = IllegalIdentifierCharacters.Replace(name, "_"); // replace illegal characters with "_".
409
410 // MSI identifiers must begin with an alphabetic character or an
411 // underscore. Prefix all other values with an underscore.
412 if (AddPrefix.IsMatch(name))
413 {
414 result = String.Concat("_", result);
415 }
416
417 return result;
418 }
419
420 /// <summary>
421 /// Checks if the string contains a property (i.e. "foo[Property]bar")
422 /// </summary>
423 /// <param name="possibleProperty">String to evaluate for properties.</param>
424 /// <returns>True if a property is found in the string.</returns>
425 internal static bool ContainsProperty(string possibleProperty)
426 {
427 return PropertySearch.IsMatch(possibleProperty);
428 }
429
430 /// <summary>
431 /// Recursively loops through a directory, changing an attribute on all of the underlying files.
432 /// An example is to add/remove the ReadOnly flag from each file.
433 /// </summary>
434 /// <param name="path">The directory path to start deleting from.</param>
435 /// <param name="fileAttribute">The FileAttribute to change on each file.</param>
436 /// <param name="messageHandler">The message handler.</param>
437 /// <param name="markAttribute">If true, add the attribute to each file. If false, remove it.</param>
438 private static void RecursiveFileAttributes(string path, FileAttributes fileAttribute, bool markAttribute, IMessageHandler messageHandler)
439 {
440 foreach (string subDirectory in Directory.GetDirectories(path))
441 {
442 RecursiveFileAttributes(subDirectory, fileAttribute, markAttribute, messageHandler);
443 }
444
445 foreach (string filePath in Directory.GetFiles(path))
446 {
447 FileAttributes attributes = File.GetAttributes(filePath);
448 if (markAttribute)
449 {
450 attributes = attributes | fileAttribute; // add to list of attributes
451 }
452 else if (fileAttribute == (attributes & fileAttribute)) // if attribute set
453 {
454 attributes = attributes ^ fileAttribute; // remove from list of attributes
455 }
456
457 try
458 {
459 File.SetAttributes(filePath, attributes);
460 }
461 catch (UnauthorizedAccessException)
462 {
463 messageHandler.OnMessage(WixWarnings.AccessDeniedForSettingAttributes(null, filePath));
464 }
465 }
466 }
467
468 internal static string GetFileHash(string path)
469 {
470 using (SHA1Managed managed = new SHA1Managed())
471 {
472 using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Delete | FileShare.Read))
473 {
474 byte[] hash = managed.ComputeHash(stream);
475 return BitConverter.ToString(hash).Replace("-", String.Empty);
476 }
477 }
478 }
479
480 /// <summary>
481 /// Get an attribute value.
482 /// </summary>
483 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
484 /// <param name="attribute">The attribute containing the value to get.</param>
485 /// <param name="emptyRule">A rule for the contents of the value. If the contents do not follow the rule, an error is thrown.</param>
486 /// <param name="messageHandler">A delegate that receives error messages.</param>
487 /// <returns>The attribute's value.</returns>
488 internal static string GetAttributeValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, EmptyRule emptyRule)
489 {
490 string value = attribute.Value;
491
492 if ((emptyRule == EmptyRule.MustHaveNonWhitespaceCharacters && String.IsNullOrEmpty(value.Trim())) ||
493 (emptyRule == EmptyRule.CanBeWhitespaceOnly && String.IsNullOrEmpty(value)))
494 {
495 Messaging.Instance.OnMessage(WixErrors.IllegalEmptyAttributeValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName));
496 return String.Empty;
497 }
498
499 return value;
500 }
501
502 /// <summary>
503 /// Verifies that a value is a legal identifier.
504 /// </summary>
505 /// <param name="value">The value to verify.</param>
506 /// <returns>true if the value is an identifier; false otherwise.</returns>
507 public static bool IsIdentifier(string value)
508 {
509 if (!String.IsNullOrEmpty(value))
510 {
511 if (LegalIdentifierCharacters.IsMatch(value))
512 {
513 return true;
514 }
515 }
516
517 return false;
518 }
519
520 /// <summary>
521 /// Get an identifier attribute value and displays an error for an illegal identifier value.
522 /// </summary>
523 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
524 /// <param name="attribute">The attribute containing the value to get.</param>
525 /// <param name="messageHandler">A delegate that receives error messages.</param>
526 /// <returns>The attribute's identifier value or a special value if an error occurred.</returns>
527 internal static string GetAttributeIdentifierValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
528 {
529 string value = Common.GetAttributeValue(sourceLineNumbers, attribute, EmptyRule.CanBeWhitespaceOnly);
530
531 if (Common.IsIdentifier(value))
532 {
533 if (72 < value.Length)
534 {
535 Messaging.Instance.OnMessage(WixWarnings.IdentifierTooLong(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
536 }
537
538 return value;
539 }
540 else
541 {
542 if (value.StartsWith("[", StringComparison.Ordinal) && value.EndsWith("]", StringComparison.Ordinal))
543 {
544 Messaging.Instance.OnMessage(WixErrors.IllegalIdentifierLooksLikeFormatted(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
545 }
546 else
547 {
548 Messaging.Instance.OnMessage(WixErrors.IllegalIdentifier(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
549 }
550
551 return String.Empty;
552 }
553 }
554
555 /// <summary>
556 /// Get an integer attribute value and displays an error for an illegal integer value.
557 /// </summary>
558 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
559 /// <param name="attribute">The attribute containing the value to get.</param>
560 /// <param name="minimum">The minimum legal value.</param>
561 /// <param name="maximum">The maximum legal value.</param>
562 /// <param name="messageHandler">A delegate that receives error messages.</param>
563 /// <returns>The attribute's integer value or a special value if an error occurred during conversion.</returns>
564 public static int GetAttributeIntegerValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, int minimum, int maximum)
565 {
566 Debug.Assert(minimum > CompilerConstants.IntegerNotSet && minimum > CompilerConstants.IllegalInteger, "The legal values for this attribute collide with at least one sentinel used during parsing.");
567
568 string value = Common.GetAttributeValue(sourceLineNumbers, attribute, EmptyRule.CanBeWhitespaceOnly);
569 int integer = CompilerConstants.IllegalInteger;
570
571 if (0 < value.Length)
572 {
573 if (Int32.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture.NumberFormat, out integer))
574 {
575 if (CompilerConstants.IntegerNotSet == integer || CompilerConstants.IllegalInteger == integer)
576 {
577 Messaging.Instance.OnMessage(WixErrors.IntegralValueSentinelCollision(sourceLineNumbers, integer));
578 }
579 else if (minimum > integer || maximum < integer)
580 {
581 Messaging.Instance.OnMessage(WixErrors.IntegralValueOutOfRange(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, integer, minimum, maximum));
582 integer = CompilerConstants.IllegalInteger;
583 }
584 }
585 else
586 {
587 Messaging.Instance.OnMessage(WixErrors.IllegalIntegerValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
588 }
589 }
590
591 return integer;
592 }
593
594 /// <summary>
595 /// Gets a yes/no value and displays an error for an illegal yes/no value.
596 /// </summary>
597 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
598 /// <param name="attribute">The attribute containing the value to get.</param>
599 /// <param name="messageHandler">A delegate that receives error messages.</param>
600 /// <returns>The attribute's YesNoType value.</returns>
601 internal static YesNoType GetAttributeYesNoValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
602 {
603 string value = Common.GetAttributeValue(sourceLineNumbers, attribute, EmptyRule.CanBeWhitespaceOnly);
604 YesNoType yesNo = YesNoType.IllegalValue;
605
606 if ("yes".Equals(value) || "true".Equals(value))
607 {
608 yesNo = YesNoType.Yes;
609 }
610 else if ("no".Equals(value) || "false".Equals(value))
611 {
612 yesNo = YesNoType.No;
613 }
614 else
615 {
616 Messaging.Instance.OnMessage(WixErrors.IllegalYesNoValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
617 }
618
619 return yesNo;
620 }
621
622 /// <summary>
623 /// Gets the text of an XElement.
624 /// </summary>
625 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
626 /// <param name="attribute">The attribute containing the value to get.</param>
627 /// <param name="messageHandler">A delegate that receives error messages.</param>
628 /// <returns>The attribute's YesNoType value.</returns>
629 internal static string GetInnerText(XElement node)
630 {
631 XText text = node.Nodes().Where(n => XmlNodeType.Text == n.NodeType || XmlNodeType.CDATA == n.NodeType).Cast<XText>().FirstOrDefault();
632 return (null == text) ? null : text.Value;
633 }
634
635 /// <summary>
636 /// Display an unexpected attribute error.
637 /// </summary>
638 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
639 /// <param name="attribute">The attribute.</param>
640 public static void UnexpectedAttribute(SourceLineNumber sourceLineNumbers, XAttribute attribute)
641 {
642 // Ignore elements defined by the W3C because we'll assume they are always right.
643 if (!((String.IsNullOrEmpty(attribute.Name.NamespaceName) && attribute.Name.LocalName.Equals("xmlns", StringComparison.Ordinal)) ||
644 attribute.Name.NamespaceName.StartsWith(CompilerCore.W3SchemaPrefix.NamespaceName, StringComparison.Ordinal)))
645 {
646 Messaging.Instance.OnMessage(WixErrors.UnexpectedAttribute(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName));
647 }
648 }
649
650 /// <summary>
651 /// Display an unsupported extension attribute error.
652 /// </summary>
653 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
654 /// <param name="extensionAttribute">The extension attribute.</param>
655 internal static void UnsupportedExtensionAttribute(SourceLineNumber sourceLineNumbers, XAttribute extensionAttribute)
656 {
657 // Ignore elements defined by the W3C because we'll assume they are always right.
658 if (!((String.IsNullOrEmpty(extensionAttribute.Name.NamespaceName) && extensionAttribute.Name.LocalName.Equals("xmlns", StringComparison.Ordinal)) ||
659 extensionAttribute.Name.NamespaceName.StartsWith(CompilerCore.W3SchemaPrefix.NamespaceName, StringComparison.Ordinal)))
660 {
661 Messaging.Instance.OnMessage(WixErrors.UnsupportedExtensionAttribute(sourceLineNumbers, extensionAttribute.Parent.Name.LocalName, extensionAttribute.Name.LocalName));
662 }
663 }
664 }
665}
diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs
new file mode 100644
index 00000000..00618152
--- /dev/null
+++ b/src/WixToolset.Core/Compiler.cs
@@ -0,0 +1,20667 @@
1// Copyright (c) .NET Foundation and 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.Collections;
7 using System.Collections.Generic;
8 using System.Diagnostics;
9 using System.Diagnostics.CodeAnalysis;
10 using System.Globalization;
11 using System.IO;
12 using System.Text.RegularExpressions;
13 using System.Xml.Linq;
14 using WixToolset.Data;
15 using WixToolset.Data.Rows;
16 using WixToolset.Extensibility;
17 using WixToolset.Msi;
18 using WixToolset.Core.Native;
19 using Wix = WixToolset.Data.Serialize;
20
21 /// <summary>
22 /// Compiler of the WiX toolset.
23 /// </summary>
24 [SuppressMessage("Microsoft.Naming", "CA1724:TypeNamesShouldNotMatchNamespaces")]
25 public sealed class Compiler
26 {
27 public const string UpgradeDetectedProperty = "WIX_UPGRADE_DETECTED";
28 public const string UpgradePreventedCondition = "NOT WIX_UPGRADE_DETECTED";
29 public const string DowngradeDetectedProperty = "WIX_DOWNGRADE_DETECTED";
30 public const string DowngradePreventedCondition = "NOT WIX_DOWNGRADE_DETECTED";
31 public const string DefaultComponentIdPlaceholderFormat = "WixComponentIdPlaceholder{0}";
32 public const string DefaultComponentIdPlaceholderWixVariableFormat = "!(wix.{0})";
33 public const string BurnUXContainerId = "WixUXContainer";
34 public const string BurnDefaultAttachedContainerId = "WixAttachedContainer";
35
36 // The following constants must stay in sync with src\burn\engine\core.h
37 private const string BURN_BUNDLE_NAME = "WixBundleName";
38 private const string BURN_BUNDLE_ORIGINAL_SOURCE = "WixBundleOriginalSource";
39 private const string BURN_BUNDLE_ORIGINAL_SOURCE_FOLDER = "WixBundleOriginalSourceFolder";
40 private const string BURN_BUNDLE_LAST_USED_SOURCE = "WixBundleLastUsedSource";
41
42 private TableDefinitionCollection tableDefinitions;
43 private Dictionary<XNamespace, ICompilerExtension> extensions;
44 private List<InspectorExtension> inspectorExtensions;
45 private CompilerCore core;
46 private bool showPedanticMessages;
47
48 // if these are true you know you are building a module or product
49 // but if they are false you cannot not be sure they will not end
50 // up a product or module. Use these flags carefully.
51 private bool compilingModule;
52 private bool compilingProduct;
53
54 private bool useShortFileNames;
55 private string activeName;
56 private string activeLanguage;
57
58 private WixVariableResolver componentIdPlaceholdersResolver;
59
60 /// <summary>
61 /// Creates a new compiler object with a default set of table definitions.
62 /// </summary>
63 public Compiler()
64 {
65 this.tableDefinitions = new TableDefinitionCollection(WindowsInstallerStandard.GetTableDefinitions());
66 this.extensions = new Dictionary<XNamespace, ICompilerExtension>();
67 this.inspectorExtensions = new List<InspectorExtension>();
68
69 this.CurrentPlatform = Platform.X86;
70 }
71
72 /// <summary>
73 /// Type of RadioButton element in a group.
74 /// </summary>
75 private enum RadioButtonType
76 {
77 /// <summary>Not set, yet.</summary>
78 NotSet,
79
80 /// <summary>Text</summary>
81 Text,
82
83 /// <summary>Bitmap</summary>
84 Bitmap,
85
86 /// <summary>Icon</summary>
87 Icon,
88 }
89
90 /// <summary>
91 /// Gets or sets the platform which the compiler will use when defaulting 64-bit attributes and elements.
92 /// </summary>
93 /// <value>The platform which the compiler will use when defaulting 64-bit attributes and elements.</value>
94 public Platform CurrentPlatform { get; set; }
95
96 /// <summary>
97 /// Gets or sets the option to show pedantic messages.
98 /// </summary>
99 /// <value>The option to show pedantic messages.</value>
100 public bool ShowPedanticMessages
101 {
102 get { return this.showPedanticMessages; }
103 set { this.showPedanticMessages = value; }
104 }
105
106 /// <summary>
107 /// Adds a compiler extension.
108 /// </summary>
109 /// <param name="extension">The extension to add.</param>
110 public void AddExtension(ICompilerExtension extension)
111 {
112 // Check if this extension is adding a schema namespace that already exists.
113 ICompilerExtension collidingExtension;
114 if (!this.extensions.TryGetValue(extension.Namespace, out collidingExtension))
115 {
116 this.extensions.Add(extension.Namespace, extension);
117 }
118 else
119 {
120 Messaging.Instance.OnMessage(WixErrors.DuplicateExtensionXmlSchemaNamespace(extension.GetType().ToString(), extension.Namespace.NamespaceName, collidingExtension.GetType().ToString()));
121 }
122
123 //if (null != extension.InspectorExtension)
124 //{
125 // this.inspectorExtensions.Add(extension.InspectorExtension);
126 //}
127 }
128
129 /// <summary>
130 /// Adds table definitions from an extension
131 /// </summary>
132 /// <param name="extension">Extension with table definitions.</param>
133 public void AddExtensionData(IExtensionData extension)
134 {
135 if (null != extension.TableDefinitions)
136 {
137 foreach (TableDefinition tableDefinition in extension.TableDefinitions)
138 {
139 if (!this.tableDefinitions.Contains(tableDefinition.Name))
140 {
141 this.tableDefinitions.Add(tableDefinition);
142 }
143 else
144 {
145 Messaging.Instance.OnMessage(WixErrors.DuplicateExtensionTable(extension.GetType().ToString(), tableDefinition.Name));
146 }
147 }
148 }
149 }
150
151 /// <summary>
152 /// Compiles the provided Xml document into an intermediate object
153 /// </summary>
154 /// <param name="source">Source xml document to compile. The BaseURI property
155 /// should be properly set to get messages containing source line information.</param>
156 /// <returns>Intermediate object representing compiled source document.</returns>
157 /// <remarks>This method is not thread-safe.</remarks>
158 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
159 public Intermediate Compile(XDocument source)
160 {
161 if (null == source)
162 {
163 throw new ArgumentNullException("source");
164 }
165
166 bool encounteredError = false;
167
168 // create the intermediate
169 Intermediate target = new Intermediate();
170
171 // try to compile it
172 try
173 {
174 this.core = new CompilerCore(target, this.tableDefinitions, this.extensions);
175 this.core.ShowPedanticMessages = this.showPedanticMessages;
176 this.core.CurrentPlatform = this.CurrentPlatform;
177 this.componentIdPlaceholdersResolver = new WixVariableResolver();
178
179 foreach (CompilerExtension extension in this.extensions.Values)
180 {
181 extension.Core = this.core;
182 extension.Initialize();
183 }
184
185 // parse the document
186 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(source.Root);
187 if ("Wix" == source.Root.Name.LocalName)
188 {
189 if (CompilerCore.WixNamespace == source.Root.Name.Namespace)
190 {
191 this.ParseWixElement(source.Root);
192 }
193 else // invalid or missing namespace
194 {
195 if (String.IsNullOrEmpty(source.Root.Name.NamespaceName))
196 {
197 this.core.OnMessage(WixErrors.InvalidWixXmlNamespace(sourceLineNumbers, "Wix", CompilerCore.WixNamespace.ToString()));
198 }
199 else
200 {
201 this.core.OnMessage(WixErrors.InvalidWixXmlNamespace(sourceLineNumbers, "Wix", source.Root.Name.NamespaceName, CompilerCore.WixNamespace.ToString()));
202 }
203 }
204 }
205 else
206 {
207 this.core.OnMessage(WixErrors.InvalidDocumentElement(sourceLineNumbers, source.Root.Name.LocalName, "source", "Wix"));
208 }
209
210 // Resolve any Component Id placeholders compiled into the intermediate.
211 if (0 < this.componentIdPlaceholdersResolver.VariableCount)
212 {
213 foreach (var section in target.Sections)
214 {
215 foreach (Table table in section.Tables)
216 {
217 foreach (Row row in table.Rows)
218 {
219 foreach (Field field in row.Fields)
220 {
221 if (field.Data is string)
222 {
223 bool isDefault = false;
224 bool delayedResolve = false;
225 field.Data = this.componentIdPlaceholdersResolver.ResolveVariables(row.SourceLineNumbers, (string)field.Data, false, false, ref isDefault, ref delayedResolve);
226 }
227 }
228 }
229 }
230 }
231 }
232
233 // inspect the document
234 InspectorCore inspectorCore = new InspectorCore();
235 foreach (InspectorExtension inspectorExtension in this.inspectorExtensions)
236 {
237 inspectorExtension.Core = inspectorCore;
238 inspectorExtension.InspectIntermediate(target);
239
240 // reset
241 inspectorExtension.Core = null;
242 }
243
244 if (inspectorCore.EncounteredError)
245 {
246 encounteredError = true;
247 }
248 }
249 finally
250 {
251 if (this.core.EncounteredError)
252 {
253 encounteredError = true;
254 }
255
256 foreach (CompilerExtension extension in this.extensions.Values)
257 {
258 extension.Finish();
259 extension.Core = null;
260 }
261 this.core = null;
262 }
263
264 // return the compiled intermediate only if it completed successfully
265 return (encounteredError ? null : target);
266 }
267
268 /// <summary>
269 /// Uppercases the first character of a string.
270 /// </summary>
271 /// <param name="s">String to uppercase first character of.</param>
272 /// <returns>String with first character uppercased.</returns>
273 private static string UppercaseFirstChar(string s)
274 {
275 if (0 == s.Length)
276 {
277 return s;
278 }
279
280 return String.Concat(s.Substring(0, 1).ToUpper(CultureInfo.InvariantCulture), s.Substring(1));
281 }
282
283 /// <summary>
284 /// Lowercases the string if present.
285 /// </summary>
286 /// <param name="s">String to lowercase.</param>
287 /// <returns>Null if the string is null, otherwise returns the lowercase.</returns>
288 private static string LowercaseOrNull(string s)
289 {
290 return (null == s) ? s : s.ToLowerInvariant();
291 }
292
293 /// <summary>
294 /// Given a possible short and long file name, create an msi filename value.
295 /// </summary>
296 /// <param name="shortName">The short file name.</param>
297 /// <param name="longName">Possibly the long file name.</param>
298 /// <returns>The value in the msi filename data type.</returns>
299 private string GetMsiFilenameValue(string shortName, string longName)
300 {
301 if (null != shortName && null != longName && !String.Equals(shortName, longName, StringComparison.OrdinalIgnoreCase))
302 {
303 return String.Format(CultureInfo.InvariantCulture, "{0}|{1}", shortName, longName);
304 }
305 else
306 {
307 if (this.core.IsValidShortFilename(longName, false))
308 {
309 return longName;
310 }
311 else
312 {
313 return shortName;
314 }
315 }
316 }
317
318 /// <summary>
319 /// Adds a search property to the active section.
320 /// </summary>
321 /// <param name="sourceLineNumbers">Current source/line number of processing.</param>
322 /// <param name="property">Property to add to search.</param>
323 /// <param name="signature">Signature for search.</param>
324 private void AddAppSearch(SourceLineNumber sourceLineNumbers, Identifier property, string signature)
325 {
326 if (!this.core.EncounteredError)
327 {
328 if (property.Id != property.Id.ToUpperInvariant())
329 {
330 this.core.OnMessage(WixErrors.SearchPropertyNotUppercase(sourceLineNumbers, "Property", "Id", property.Id));
331 }
332
333 Row row = this.core.CreateRow(sourceLineNumbers, "AppSearch", property);
334 row[1] = signature;
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="property">Name 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 property, 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 == property || String.IsNullOrEmpty(property.Id))
352 {
353 return;
354 }
355
356 if (!String.IsNullOrEmpty(value))
357 {
358 Regex regex = new Regex(@"\[(?<identifier>[a-zA-Z_][a-zA-Z0-9_\.]*)]", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture);
359 MatchCollection matches = regex.Matches(value);
360
361 foreach (Match match in matches)
362 {
363 Group group = match.Groups["identifier"];
364 if (group.Success)
365 {
366 this.core.OnMessage(WixWarnings.PropertyValueContainsPropertyReference(sourceLineNumbers, property.Id, group.Value));
367 }
368 }
369 }
370
371 if (!this.core.EncounteredError)
372 {
373 Section section = this.core.ActiveSection;
374
375 // Add the row to a separate section if requested.
376 if (fragment)
377 {
378 string id = String.Concat(this.core.ActiveSection.Id, ".", property.Id);
379
380 section = this.core.CreateSection(id, SectionType.Fragment, this.core.ActiveSection.Codepage);
381
382 // Reference the property in the active section.
383 this.core.CreateSimpleReference(sourceLineNumbers, "Property", property.Id);
384 }
385
386 Row row = this.core.CreateRow(sourceLineNumbers, "Property", section, property);
387
388 // Allow row to exist with no value so that PropertyRefs can be made for *Search elements
389 // the linker will remove these rows before the final output is created.
390 if (null != value)
391 {
392 row[1] = value;
393 }
394
395 if (admin || hidden || secure)
396 {
397 this.AddWixPropertyRow(sourceLineNumbers, property, admin, secure, hidden, section);
398 }
399 }
400 }
401
402 private WixPropertyRow AddWixPropertyRow(SourceLineNumber sourceLineNumbers, Identifier property, bool admin, bool secure, bool hidden, Section section = null)
403 {
404 if (secure && property.Id != property.Id.ToUpperInvariant())
405 {
406 this.core.OnMessage(WixErrors.SecurePropertyNotUppercase(sourceLineNumbers, "Property", "Id", property.Id));
407 }
408
409 if (null == section)
410 {
411 section = this.core.ActiveSection;
412
413 this.core.EnsureTable(sourceLineNumbers, "Property"); // Property table is always required when using WixProperty table.
414 }
415
416 WixPropertyRow row = (WixPropertyRow)this.core.CreateRow(sourceLineNumbers, "WixProperty", section, property);
417 row.Admin = admin;
418 row.Hidden = hidden;
419 row.Secure = secure;
420
421 return row;
422 }
423
424 /// <summary>
425 /// Adds a "implemented category" registry key to active section.
426 /// </summary>
427 /// <param name="sourceLineNumbers">Current source/line number of processing.</param>
428 /// <param name="categoryId">GUID for category.</param>
429 /// <param name="classId">ClassId for to mark "implemented".</param>
430 /// <param name="componentId">Identifier of parent component.</param>
431 private void RegisterImplementedCategories(SourceLineNumber sourceLineNumbers, string categoryId, string classId, string componentId)
432 {
433 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat("CLSID\\", classId, "\\Implemented Categories\\", categoryId), "*", null, componentId);
434 }
435
436 /// <summary>
437 /// Parses an application identifer element.
438 /// </summary>
439 /// <param name="node">Element to parse.</param>
440 /// <param name="componentId">Identifier of parent component.</param>
441 /// <param name="advertise">The required advertise state (set depending upon the parent).</param>
442 /// <param name="fileServer">Optional file identifier for CLSID when not advertised.</param>
443 /// <param name="typeLibId">Optional TypeLib GUID for CLSID.</param>
444 /// <param name="typeLibVersion">Optional TypeLib Version for CLSID Interfaces (if any).</param>
445 private void ParseAppIdElement(XElement node, string componentId, YesNoType advertise, string fileServer, string typeLibId, string typeLibVersion)
446 {
447 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
448 string appId = null;
449 string remoteServerName = null;
450 string localService = null;
451 string serviceParameters = null;
452 string dllSurrogate = null;
453 YesNoType activateAtStorage = YesNoType.NotSet;
454 YesNoType appIdAdvertise = YesNoType.NotSet;
455 YesNoType runAsInteractiveUser = YesNoType.NotSet;
456 string description = null;
457
458 foreach (XAttribute attrib in node.Attributes())
459 {
460 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
461 {
462 switch (attrib.Name.LocalName)
463 {
464 case "Id":
465 appId = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
466 break;
467 case "ActivateAtStorage":
468 activateAtStorage = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
469 break;
470 case "Advertise":
471 appIdAdvertise = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
472 break;
473 case "Description": description = this.core.GetAttributeValue(sourceLineNumbers, attrib);
474 break;
475 case "DllSurrogate":
476 dllSurrogate = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty);
477 break;
478 case "LocalService":
479 localService = this.core.GetAttributeValue(sourceLineNumbers, attrib);
480 break;
481 case "RemoteServerName":
482 remoteServerName = this.core.GetAttributeValue(sourceLineNumbers, attrib);
483 break;
484 case "RunAsInteractiveUser":
485 runAsInteractiveUser = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
486 break;
487 case "ServiceParameters":
488 serviceParameters = this.core.GetAttributeValue(sourceLineNumbers, attrib);
489 break;
490 default:
491 this.core.UnexpectedAttribute(node, attrib);
492 break;
493 }
494 }
495 else
496 {
497 this.core.ParseExtensionAttribute(node, attrib);
498 }
499 }
500
501 if (null == appId)
502 {
503 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
504 }
505
506 if ((YesNoType.No == advertise && YesNoType.Yes == appIdAdvertise) || (YesNoType.Yes == advertise && YesNoType.No == appIdAdvertise))
507 {
508 this.core.OnMessage(WixErrors.AppIdIncompatibleAdvertiseState(sourceLineNumbers, node.Name.LocalName, "Advertise", appIdAdvertise.ToString(), advertise.ToString()));
509 }
510 else
511 {
512 advertise = appIdAdvertise;
513 }
514
515 // if the advertise state has not been set, default to non-advertised
516 if (YesNoType.NotSet == advertise)
517 {
518 advertise = YesNoType.No;
519 }
520
521 foreach (XElement child in node.Elements())
522 {
523 if (CompilerCore.WixNamespace == child.Name.Namespace)
524 {
525 switch (child.Name.LocalName)
526 {
527 case "Class":
528 this.ParseClassElement(child, componentId, advertise, fileServer, typeLibId, typeLibVersion, appId);
529 break;
530 default:
531 this.core.UnexpectedElement(node, child);
532 break;
533 }
534 }
535 else
536 {
537 this.core.ParseExtensionElement(node, child);
538 }
539 }
540
541 if (YesNoType.Yes == advertise)
542 {
543 if (null != description)
544 {
545 this.core.OnMessage(WixErrors.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "Description"));
546 }
547
548 if (!this.core.EncounteredError)
549 {
550 Row row = this.core.CreateRow(sourceLineNumbers, "AppId");
551 row[0] = appId;
552 row[1] = remoteServerName;
553 row[2] = localService;
554 row[3] = serviceParameters;
555 row[4] = dllSurrogate;
556 if (YesNoType.Yes == activateAtStorage)
557 {
558 row[5] = 1;
559 }
560
561 if (YesNoType.Yes == runAsInteractiveUser)
562 {
563 row[6] = 1;
564 }
565 }
566 }
567 else if (YesNoType.No == advertise)
568 {
569 if (null != description)
570 {
571 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat("AppID\\", appId), null, description, componentId);
572 }
573 else
574 {
575 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat("AppID\\", appId), "+", null, componentId);
576 }
577
578 if (null != remoteServerName)
579 {
580 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat("AppID\\", appId), "RemoteServerName", remoteServerName, componentId);
581 }
582
583 if (null != localService)
584 {
585 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat("AppID\\", appId), "LocalService", localService, componentId);
586 }
587
588 if (null != serviceParameters)
589 {
590 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat("AppID\\", appId), "ServiceParameters", serviceParameters, componentId);
591 }
592
593 if (null != dllSurrogate)
594 {
595 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat("AppID\\", appId), "DllSurrogate", dllSurrogate, componentId);
596 }
597
598 if (YesNoType.Yes == activateAtStorage)
599 {
600 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat("AppID\\", appId), "ActivateAtStorage", "Y", componentId);
601 }
602
603 if (YesNoType.Yes == runAsInteractiveUser)
604 {
605 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat("AppID\\", appId), "RunAs", "Interactive User", componentId);
606 }
607 }
608 }
609
610 /// <summary>
611 /// Parses an AssemblyName element.
612 /// </summary>
613 /// <param name="node">File element to parse.</param>
614 /// <param name="componentId">Parent's component id.</param>
615 private void ParseAssemblyName(XElement node, string componentId)
616 {
617 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
618 string id = null;
619 string value = null;
620
621 foreach (XAttribute attrib in node.Attributes())
622 {
623 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
624 {
625 switch (attrib.Name.LocalName)
626 {
627 case "Id":
628 id = this.core.GetAttributeValue(sourceLineNumbers, attrib);
629 break;
630 case "Value":
631 value = this.core.GetAttributeValue(sourceLineNumbers, attrib);
632 break;
633 default:
634 this.core.UnexpectedAttribute(node, attrib);
635 break;
636 }
637 }
638 else
639 {
640 this.core.ParseExtensionAttribute(node, attrib);
641 }
642 }
643
644 if (null == id)
645 {
646 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
647 }
648
649 this.core.ParseForExtensionElements(node);
650
651 if (!this.core.EncounteredError)
652 {
653 Row row = this.core.CreateRow(sourceLineNumbers, "MsiAssemblyName");
654 row[0] = componentId;
655 row[1] = id;
656 row[2] = value;
657 }
658 }
659
660 /// <summary>
661 /// Parses a binary element.
662 /// </summary>
663 /// <param name="node">Element to parse.</param>
664 /// <returns>Identifier for the new row.</returns>
665 [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
666 private Identifier ParseBinaryElement(XElement node)
667 {
668 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
669 Identifier id = null;
670 string sourceFile = null;
671 YesNoType suppressModularization = YesNoType.NotSet;
672
673 foreach (XAttribute attrib in node.Attributes())
674 {
675 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
676 {
677 switch (attrib.Name.LocalName)
678 {
679 case "Id":
680 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
681 break;
682 case "SourceFile":
683 case "src":
684 if (null != sourceFile)
685 {
686 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile", "src"));
687 }
688
689 if ("src" == attrib.Name.LocalName)
690 {
691 this.core.OnMessage(WixWarnings.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "SourceFile"));
692 }
693 sourceFile = this.core.GetAttributeValue(sourceLineNumbers, attrib);
694 break;
695 case "SuppressModularization":
696 suppressModularization = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
697 break;
698 default:
699 this.core.UnexpectedAttribute(node, attrib);
700 break;
701 }
702 }
703 else
704 {
705 this.core.ParseExtensionAttribute(node, attrib);
706 }
707 }
708
709 if (null == id)
710 {
711 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
712 id = Identifier.Invalid;
713 }
714 else if (!String.IsNullOrEmpty(id.Id)) // only check legal values
715 {
716 if (55 < id.Id.Length)
717 {
718 this.core.OnMessage(WixErrors.StreamNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, id.Id.Length, 55));
719 }
720 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
721 {
722 if (18 < id.Id.Length)
723 {
724 this.core.OnMessage(WixWarnings.IdentifierCannotBeModularized(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, id.Id.Length, 18));
725 }
726 }
727 }
728
729 if (null == sourceFile)
730 {
731 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile"));
732 }
733
734 this.core.ParseForExtensionElements(node);
735
736 if (!this.core.EncounteredError)
737 {
738 Row row = this.core.CreateRow(sourceLineNumbers, "Binary", id);
739 row[1] = sourceFile;
740
741 if (YesNoType.Yes == suppressModularization)
742 {
743 Row wixSuppressModularizationRow = this.core.CreateRow(sourceLineNumbers, "WixSuppressModularization");
744 wixSuppressModularizationRow[0] = id;
745 }
746 }
747
748 return id;
749 }
750
751 /// <summary>
752 /// Parses an icon element.
753 /// </summary>
754 /// <param name="node">Element to parse.</param>
755 /// <returns>Identifier for the new row.</returns>
756 private string ParseIconElement(XElement node)
757 {
758 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
759 Identifier id = null;
760 string sourceFile = null;
761
762 foreach (XAttribute attrib in node.Attributes())
763 {
764 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
765 {
766 switch (attrib.Name.LocalName)
767 {
768 case "Id":
769 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
770 break;
771 case "SourceFile":
772 sourceFile = this.core.GetAttributeValue(sourceLineNumbers, attrib);
773 break;
774 default:
775 this.core.UnexpectedAttribute(node, attrib);
776 break;
777 }
778 }
779 else
780 {
781 this.core.ParseExtensionAttribute(node, attrib);
782 }
783 }
784
785 if (null == id)
786 {
787 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
788 id = Identifier.Invalid;
789 }
790 else if (!String.IsNullOrEmpty(id.Id)) // only check legal values
791 {
792 if (57 < id.Id.Length)
793 {
794 this.core.OnMessage(WixErrors.StreamNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, id.Id.Length, 57));
795 }
796 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
797 {
798 if (20 < id.Id.Length)
799 {
800 this.core.OnMessage(WixWarnings.IdentifierCannotBeModularized(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, id.Id.Length, 20));
801 }
802 }
803 }
804
805 if (null == sourceFile)
806 {
807 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile"));
808 }
809
810 this.core.ParseForExtensionElements(node);
811
812 if (!this.core.EncounteredError)
813 {
814 Row row = this.core.CreateRow(sourceLineNumbers, "Icon", id);
815 row[1] = sourceFile;
816 }
817
818 return id.Id;
819 }
820
821 /// <summary>
822 /// Parses an InstanceTransforms element.
823 /// </summary>
824 /// <param name="node">Element to parse.</param>
825 private void ParseInstanceTransformsElement(XElement node)
826 {
827 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
828 string property = null;
829
830 foreach (XAttribute attrib in node.Attributes())
831 {
832 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
833 {
834 switch (attrib.Name.LocalName)
835 {
836 case "Property":
837 property = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
838 this.core.CreateSimpleReference(sourceLineNumbers, "Property", property);
839 break;
840 default:
841 this.core.UnexpectedAttribute(node, attrib);
842 break;
843 }
844 }
845 else
846 {
847 this.core.ParseExtensionAttribute(node, attrib);
848 }
849 }
850
851 if (null == property)
852 {
853 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property"));
854 }
855
856 // find unexpected child elements
857 foreach (XElement child in node.Elements())
858 {
859 if (CompilerCore.WixNamespace == child.Name.Namespace)
860 {
861 switch (child.Name.LocalName)
862 {
863 case "Instance":
864 ParseInstanceElement(child, property);
865 break;
866 default:
867 this.core.UnexpectedElement(node, child);
868 break;
869 }
870 }
871 else
872 {
873 this.core.ParseExtensionElement(node, child);
874 }
875 }
876 }
877
878 /// <summary>
879 /// Parses an instance element.
880 /// </summary>
881 /// <param name="node">Element to parse.</param>
882 /// <param name="componentId">Identifier of instance property.</param>
883 private void ParseInstanceElement(XElement node, string propertyId)
884 {
885 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
886 string id = null;
887 string productCode = null;
888 string productName = null;
889 string upgradeCode = null;
890
891 foreach (XAttribute attrib in node.Attributes())
892 {
893 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
894 {
895 switch (attrib.Name.LocalName)
896 {
897 case "Id":
898 id = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
899 break;
900 case "ProductCode":
901 productCode = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, true);
902 break;
903 case "ProductName":
904 productName = this.core.GetAttributeValue(sourceLineNumbers, attrib);
905 break;
906 case "UpgradeCode":
907 upgradeCode = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
908 break;
909 default:
910 this.core.UnexpectedAttribute(node, attrib);
911 break;
912 }
913 }
914 else
915 {
916 this.core.ParseExtensionAttribute(node, attrib);
917 }
918 }
919
920 if (null == id)
921 {
922 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
923 }
924
925 if (null == productCode)
926 {
927 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ProductCode"));
928 }
929
930 this.core.ParseForExtensionElements(node);
931
932 if (!this.core.EncounteredError)
933 {
934 Row row = this.core.CreateRow(sourceLineNumbers, "WixInstanceTransforms");
935 row[0] = id;
936 row[1] = propertyId;
937 row[2] = productCode;
938 if (null != productName)
939 {
940 row[3] = productName;
941 }
942 if (null != upgradeCode)
943 {
944 row[4] = upgradeCode;
945 }
946 }
947 }
948
949 /// <summary>
950 /// Parses a category element.
951 /// </summary>
952 /// <param name="node">Element to parse.</param>
953 /// <param name="componentId">Identifier of parent component.</param>
954 private void ParseCategoryElement(XElement node, string componentId)
955 {
956 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
957 string id = null;
958 string appData = null;
959 string feature = null;
960 string qualifier = null;
961
962 foreach (XAttribute attrib in node.Attributes())
963 {
964 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
965 {
966 switch (attrib.Name.LocalName)
967 {
968 case "Id":
969 id = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
970 break;
971 case "AppData":
972 appData = this.core.GetAttributeValue(sourceLineNumbers, attrib);
973 break;
974 case "Feature":
975 feature = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
976 this.core.CreateSimpleReference(sourceLineNumbers, "Feature", feature);
977 break;
978 case "Qualifier":
979 qualifier = this.core.GetAttributeValue(sourceLineNumbers, attrib);
980 break;
981 default:
982 this.core.UnexpectedAttribute(node, attrib);
983 break;
984 }
985 }
986 else
987 {
988 this.core.ParseExtensionAttribute(node, attrib);
989 }
990 }
991
992 if (null == id)
993 {
994 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
995 }
996
997 if (null == qualifier)
998 {
999 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Qualifier"));
1000 }
1001
1002 this.core.ParseForExtensionElements(node);
1003
1004 if (!this.core.EncounteredError)
1005 {
1006 Row row = this.core.CreateRow(sourceLineNumbers, "PublishComponent");
1007 row[0] = id;
1008 row[1] = qualifier;
1009 row[2] = componentId;
1010 row[3] = appData;
1011 if (null == feature)
1012 {
1013 row[4] = Guid.Empty.ToString("B");
1014 }
1015 else
1016 {
1017 row[4] = feature;
1018 }
1019 }
1020 }
1021
1022 /// <summary>
1023 /// Parses a class element.
1024 /// </summary>
1025 /// <param name="node">Element to parse.</param>
1026 /// <param name="componentId">Identifier of parent component.</param>
1027 /// <param name="advertise">Optional Advertise State for the parent AppId element (if any).</param>
1028 /// <param name="fileServer">Optional file identifier for CLSID when not advertised.</param>
1029 /// <param name="typeLibId">Optional TypeLib GUID for CLSID.</param>
1030 /// <param name="typeLibVersion">Optional TypeLib Version for CLSID Interfaces (if any).</param>
1031 /// <param name="parentAppId">Optional parent AppId.</param>
1032 private void ParseClassElement(XElement node, string componentId, YesNoType advertise, string fileServer, string typeLibId, string typeLibVersion, string parentAppId)
1033 {
1034 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1035
1036 string appId = null;
1037 string argument = null;
1038 bool class16bit = false;
1039 bool class32bit = false;
1040 string classId = null;
1041 YesNoType classAdvertise = YesNoType.NotSet;
1042 string[] contexts = null;
1043 string formattedContextString = null;
1044 bool control = false;
1045 string defaultInprocHandler = null;
1046 string defaultProgId = null;
1047 string description = null;
1048 string fileTypeMask = null;
1049 string foreignServer = null;
1050 string icon = null;
1051 int iconIndex = CompilerConstants.IntegerNotSet;
1052 string insertable = null;
1053 string localFileServer = null;
1054 bool programmable = false;
1055 YesNoType relativePath = YesNoType.NotSet;
1056 bool safeForInit = false;
1057 bool safeForScripting = false;
1058 bool shortServerPath = false;
1059 string threadingModel = null;
1060 string version = null;
1061
1062 foreach (XAttribute attrib in node.Attributes())
1063 {
1064 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
1065 {
1066 switch (attrib.Name.LocalName)
1067 {
1068 case "Id":
1069 classId = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
1070 break;
1071 case "Advertise":
1072 classAdvertise = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1073 break;
1074 case "AppId":
1075 appId = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
1076 break;
1077 case "Argument":
1078 argument = this.core.GetAttributeValue(sourceLineNumbers, attrib);
1079 break;
1080 case "Context":
1081 contexts = this.core.GetAttributeValue(sourceLineNumbers, attrib).Split("\r\n\t ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
1082 break;
1083 case "Control":
1084 control = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1085 break;
1086 case "Description":
1087 description = this.core.GetAttributeValue(sourceLineNumbers, attrib);
1088 break;
1089 case "Handler":
1090 defaultInprocHandler = this.core.GetAttributeValue(sourceLineNumbers, attrib);
1091 break;
1092 case "Icon":
1093 icon = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
1094 break;
1095 case "IconIndex":
1096 iconIndex = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, short.MinValue + 1, short.MaxValue);
1097 break;
1098 case "RelativePath":
1099 relativePath = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1100 break;
1101
1102 // The following attributes result in rows always added to the Registry table rather than the Class table
1103 case "Insertable":
1104 insertable = (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? "Insertable" : "NotInsertable";
1105 break;
1106 case "Programmable":
1107 programmable = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1108 break;
1109 case "SafeForInitializing":
1110 safeForInit = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1111 break;
1112 case "SafeForScripting":
1113 safeForScripting = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1114 break;
1115 case "ForeignServer":
1116 foreignServer = this.core.GetAttributeValue(sourceLineNumbers, attrib);
1117 break;
1118 case "Server":
1119 localFileServer = this.core.GetAttributeValue(sourceLineNumbers, attrib);
1120 break;
1121 case "ShortPath":
1122 shortServerPath = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1123 break;
1124 case "ThreadingModel":
1125 threadingModel = this.core.GetAttributeValue(sourceLineNumbers, attrib);
1126 break;
1127 case "Version":
1128 version = this.core.GetAttributeValue(sourceLineNumbers, attrib);
1129 break;
1130 default:
1131 this.core.UnexpectedAttribute(node, attrib);
1132 break;
1133 }
1134 }
1135 else
1136 {
1137 this.core.ParseExtensionAttribute(node, attrib);
1138 }
1139 }
1140
1141 if (null == classId)
1142 {
1143 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
1144 }
1145
1146 HashSet<string> uniqueContexts = new HashSet<string>();
1147 foreach (string context in contexts)
1148 {
1149 if (uniqueContexts.Contains(context))
1150 {
1151 this.core.OnMessage(WixErrors.DuplicateContextValue(sourceLineNumbers, context));
1152 }
1153 else
1154 {
1155 uniqueContexts.Add(context);
1156 }
1157
1158 if (context.EndsWith("32", StringComparison.Ordinal))
1159 {
1160 class32bit = true;
1161 }
1162 else
1163 {
1164 class16bit = true;
1165 }
1166 }
1167
1168 if ((YesNoType.No == advertise && YesNoType.Yes == classAdvertise) || (YesNoType.Yes == advertise && YesNoType.No == classAdvertise))
1169 {
1170 this.core.OnMessage(WixErrors.AdvertiseStateMustMatch(sourceLineNumbers, classAdvertise.ToString(), advertise.ToString()));
1171 }
1172 else
1173 {
1174 advertise = classAdvertise;
1175 }
1176
1177 // If the advertise state has not been set, default to non-advertised.
1178 if (YesNoType.NotSet == advertise)
1179 {
1180 advertise = YesNoType.No;
1181 }
1182
1183 if (YesNoType.Yes == advertise && 0 == contexts.Length)
1184 {
1185 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Context", "Advertise", "yes"));
1186 }
1187
1188 if (!String.IsNullOrEmpty(parentAppId) && !String.IsNullOrEmpty(appId))
1189 {
1190 this.core.OnMessage(WixErrors.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "AppId", node.Parent.Name.LocalName));
1191 }
1192
1193 if (!String.IsNullOrEmpty(localFileServer))
1194 {
1195 this.core.CreateSimpleReference(sourceLineNumbers, "File", localFileServer);
1196 }
1197
1198 // Local variables used strictly for child node processing.
1199 int fileTypeMaskIndex = 0;
1200 YesNoType firstProgIdForClass = YesNoType.Yes;
1201
1202 foreach (XElement child in node.Elements())
1203 {
1204 if (CompilerCore.WixNamespace == child.Name.Namespace)
1205 {
1206 switch (child.Name.LocalName)
1207 {
1208 case "FileTypeMask":
1209 if (YesNoType.Yes == advertise)
1210 {
1211 fileTypeMask = String.Concat(fileTypeMask, null == fileTypeMask ? String.Empty : ";", this.ParseFileTypeMaskElement(child));
1212 }
1213 else if (YesNoType.No == advertise)
1214 {
1215 SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
1216 this.core.CreateRegistryRow(childSourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat("FileType\\", classId, "\\", fileTypeMaskIndex.ToString()), String.Empty, this.ParseFileTypeMaskElement(child), componentId);
1217 fileTypeMaskIndex++;
1218 }
1219 break;
1220 case "Interface":
1221 this.ParseInterfaceElement(child, componentId, class16bit ? classId : null, class32bit ? classId : null, typeLibId, typeLibVersion);
1222 break;
1223 case "ProgId":
1224 {
1225 bool foundExtension = false;
1226 string progId = this.ParseProgIdElement(child, componentId, advertise, classId, description, null, ref foundExtension, firstProgIdForClass);
1227 if (null == defaultProgId)
1228 {
1229 defaultProgId = progId;
1230 }
1231 firstProgIdForClass = YesNoType.No;
1232 }
1233 break;
1234 default:
1235 this.core.UnexpectedElement(node, child);
1236 break;
1237 }
1238 }
1239 else
1240 {
1241 this.core.ParseExtensionElement(node, child);
1242 }
1243 }
1244
1245 // If this Class is being advertised.
1246 if (YesNoType.Yes == advertise)
1247 {
1248 if (null != fileServer || null != localFileServer)
1249 {
1250 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Server", "Advertise", "yes"));
1251 }
1252
1253 if (null != foreignServer)
1254 {
1255 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "ForeignServer", "Advertise", "yes"));
1256 }
1257
1258 if (null == appId && null != parentAppId)
1259 {
1260 appId = parentAppId;
1261 }
1262
1263 // add a Class row for each context
1264 if (!this.core.EncounteredError)
1265 {
1266 foreach (string context in contexts)
1267 {
1268 Row row = this.core.CreateRow(sourceLineNumbers, "Class");
1269 row[0] = classId;
1270 row[1] = context;
1271 row[2] = componentId;
1272 row[3] = defaultProgId;
1273 row[4] = description;
1274 if (null != appId)
1275 {
1276 row[5] = appId;
1277 this.core.CreateSimpleReference(sourceLineNumbers, "AppId", appId);
1278 }
1279 row[6] = fileTypeMask;
1280 if (null != icon)
1281 {
1282 row[7] = icon;
1283 this.core.CreateSimpleReference(sourceLineNumbers, "Icon", icon);
1284 }
1285 if (CompilerConstants.IntegerNotSet != iconIndex)
1286 {
1287 row[8] = iconIndex;
1288 }
1289 row[9] = defaultInprocHandler;
1290 row[10] = argument;
1291 row[11] = Guid.Empty.ToString("B");
1292 if (YesNoType.Yes == relativePath)
1293 {
1294 row[12] = MsiInterop.MsidbClassAttributesRelativePath;
1295 }
1296 }
1297 }
1298 }
1299 else if (YesNoType.No == advertise)
1300 {
1301 if (null == fileServer && null == localFileServer && null == foreignServer)
1302 {
1303 this.core.OnMessage(WixErrors.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "ForeignServer", "Server"));
1304 }
1305
1306 if (null != fileServer && null != foreignServer)
1307 {
1308 this.core.OnMessage(WixErrors.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "ForeignServer", "File"));
1309 }
1310 else if (null != localFileServer && null != foreignServer)
1311 {
1312 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "ForeignServer", "Server"));
1313 }
1314 else if (null == fileServer)
1315 {
1316 fileServer = localFileServer;
1317 }
1318
1319 if (null != appId) // need to use nesting (not a reference) for the unadvertised Class elements
1320 {
1321 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "AppId", "Advertise", "no"));
1322 }
1323
1324 // add the core registry keys for each context in the class
1325 foreach (string context in contexts)
1326 {
1327 if (context.StartsWith("InprocServer", StringComparison.Ordinal)) // dll server
1328 {
1329 if (null != argument)
1330 {
1331 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Arguments", "Context", context));
1332 }
1333
1334 if (null != fileServer)
1335 {
1336 formattedContextString = String.Concat("[", shortServerPath ? "!" : "#", fileServer, "]");
1337 }
1338 else if (null != foreignServer)
1339 {
1340 formattedContextString = foreignServer;
1341 }
1342 }
1343 else if (context.StartsWith("LocalServer", StringComparison.Ordinal)) // exe server (quote the long path)
1344 {
1345 if (null != fileServer)
1346 {
1347 if (shortServerPath)
1348 {
1349 formattedContextString = String.Concat("[!", fileServer, "]");
1350 }
1351 else
1352 {
1353 formattedContextString = String.Concat("\"[#", fileServer, "]\"");
1354 }
1355 }
1356 else if (null != foreignServer)
1357 {
1358 formattedContextString = foreignServer;
1359 }
1360
1361 if (null != argument)
1362 {
1363 formattedContextString = String.Concat(formattedContextString, " ", argument);
1364 }
1365 }
1366 else
1367 {
1368 this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Context", context, "InprocServer", "InprocServer32", "LocalServer", "LocalServer32"));
1369 }
1370
1371 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat("CLSID\\", classId, "\\", context), String.Empty, formattedContextString, componentId); // ClassId context
1372
1373 if (null != icon) // ClassId default icon
1374 {
1375 this.core.CreateSimpleReference(sourceLineNumbers, "File", icon);
1376
1377 icon = String.Format(CultureInfo.InvariantCulture, "\"[#{0}]\"", icon);
1378
1379 if (CompilerConstants.IntegerNotSet != iconIndex)
1380 {
1381 icon = String.Concat(icon, ",", iconIndex);
1382 }
1383 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat("CLSID\\", classId, "\\", context, "\\DefaultIcon"), String.Empty, icon, componentId);
1384 }
1385 }
1386
1387 if (null != parentAppId) // ClassId AppId (must be specified via nesting, not with the AppId attribute)
1388 {
1389 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat("CLSID\\", classId), "AppID", parentAppId, componentId);
1390 }
1391
1392 if (null != description) // ClassId description
1393 {
1394 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat("CLSID\\", classId), String.Empty, description, componentId);
1395 }
1396
1397 if (null != defaultInprocHandler)
1398 {
1399 switch (defaultInprocHandler) // ClassId Default Inproc Handler
1400 {
1401 case "1":
1402 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat("CLSID\\", classId, "\\InprocHandler"), String.Empty, "ole.dll", componentId);
1403 break;
1404 case "2":
1405 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat("CLSID\\", classId, "\\InprocHandler32"), String.Empty, "ole32.dll", componentId);
1406 break;
1407 case "3":
1408 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat("CLSID\\", classId, "\\InprocHandler"), String.Empty, "ole.dll", componentId);
1409 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat("CLSID\\", classId, "\\InprocHandler32"), String.Empty, "ole32.dll", componentId);
1410 break;
1411 default:
1412 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat("CLSID\\", classId, "\\InprocHandler32"), String.Empty, defaultInprocHandler, componentId);
1413 break;
1414 }
1415 }
1416
1417 if (YesNoType.NotSet != relativePath) // ClassId's RelativePath
1418 {
1419 this.core.OnMessage(WixErrors.RelativePathForRegistryElement(sourceLineNumbers));
1420 }
1421 }
1422
1423 if (null != threadingModel)
1424 {
1425 threadingModel = Compiler.UppercaseFirstChar(threadingModel);
1426
1427 // add a threading model for each context in the class
1428 foreach (string context in contexts)
1429 {
1430 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat("CLSID\\", classId, "\\", context), "ThreadingModel", threadingModel, componentId);
1431 }
1432 }
1433
1434 if (null != typeLibId)
1435 {
1436 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat("CLSID\\", classId, "\\TypeLib"), null, typeLibId, componentId);
1437 }
1438
1439 if (null != version)
1440 {
1441 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat("CLSID\\", classId, "\\Version"), null, version, componentId);
1442 }
1443
1444 if (null != insertable)
1445 {
1446 // Add "*" for name so that any subkeys (shouldn't be any) are removed on uninstall.
1447 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat("CLSID\\", classId, "\\", insertable), "*", null, componentId);
1448 }
1449
1450 if (control)
1451 {
1452 // Add "*" for name so that any subkeys (shouldn't be any) are removed on uninstall.
1453 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat("CLSID\\", classId, "\\Control"), "*", null, componentId);
1454 }
1455
1456 if (programmable)
1457 {
1458 // Add "*" for name so that any subkeys (shouldn't be any) are removed on uninstall.
1459 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat("CLSID\\", classId, "\\Programmable"), "*", null, componentId);
1460 }
1461
1462 if (safeForInit)
1463 {
1464 this.RegisterImplementedCategories(sourceLineNumbers, "{7DD95802-9882-11CF-9FA9-00AA006C42C4}", classId, componentId);
1465 }
1466
1467 if (safeForScripting)
1468 {
1469 this.RegisterImplementedCategories(sourceLineNumbers, "{7DD95801-9882-11CF-9FA9-00AA006C42C4}", classId, componentId);
1470 }
1471 }
1472
1473 /// <summary>
1474 /// Parses an Interface element.
1475 /// </summary>
1476 /// <param name="node">Element to parse.</param>
1477 /// <param name="componentId">Identifier of parent component.</param>
1478 /// <param name="proxyId">16-bit proxy for interface.</param>
1479 /// <param name="proxyId32">32-bit proxy for interface.</param>
1480 /// <param name="typeLibId">Optional TypeLib GUID for CLSID.</param>
1481 /// <param name="typelibVersion">Version of the TypeLib to which this interface belongs. Required if typeLibId is specified</param>
1482 private void ParseInterfaceElement(XElement node, string componentId, string proxyId, string proxyId32, string typeLibId, string typelibVersion)
1483 {
1484 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1485 string baseInterface = null;
1486 string interfaceId = null;
1487 string name = null;
1488 int numMethods = CompilerConstants.IntegerNotSet;
1489 bool versioned = true;
1490
1491 foreach (XAttribute attrib in node.Attributes())
1492 {
1493 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
1494 {
1495 switch (attrib.Name.LocalName)
1496 {
1497 case "Id":
1498 interfaceId = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
1499 break;
1500 case "BaseInterface":
1501 baseInterface = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
1502 break;
1503 case "Name":
1504 name = this.core.GetAttributeValue(sourceLineNumbers, attrib);
1505 break;
1506 case "NumMethods":
1507 numMethods = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, int.MaxValue);
1508 break;
1509 case "ProxyStubClassId":
1510 proxyId = this.core.GetAttributeValue(sourceLineNumbers, attrib);
1511 break;
1512 case "ProxyStubClassId32":
1513 proxyId32 = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
1514 break;
1515 case "Versioned":
1516 versioned = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
1517 break;
1518 default:
1519 this.core.UnexpectedAttribute(node, attrib);
1520 break;
1521 }
1522 }
1523 else
1524 {
1525 this.core.ParseExtensionAttribute(node, attrib);
1526 }
1527 }
1528
1529 if (null == interfaceId)
1530 {
1531 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
1532 }
1533
1534 if (null == name)
1535 {
1536 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
1537 }
1538
1539 this.core.ParseForExtensionElements(node);
1540
1541 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat("Interface\\", interfaceId), null, name, componentId);
1542 if (null != typeLibId)
1543 {
1544 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat("Interface\\", interfaceId, "\\TypeLib"), null, typeLibId, componentId);
1545 if (versioned)
1546 {
1547 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat("Interface\\", interfaceId, "\\TypeLib"), "Version", typelibVersion, componentId);
1548 }
1549 }
1550
1551 if (null != baseInterface)
1552 {
1553 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat("Interface\\", interfaceId, "\\BaseInterface"), null, baseInterface, componentId);
1554 }
1555
1556 if (CompilerConstants.IntegerNotSet != numMethods)
1557 {
1558 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat("Interface\\", interfaceId, "\\NumMethods"), null, numMethods.ToString(), componentId);
1559 }
1560
1561 if (null != proxyId)
1562 {
1563 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat("Interface\\", interfaceId, "\\ProxyStubClsid"), null, proxyId, componentId);
1564 }
1565
1566 if (null != proxyId32)
1567 {
1568 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat("Interface\\", interfaceId, "\\ProxyStubClsid32"), null, proxyId32, componentId);
1569 }
1570 }
1571
1572 /// <summary>
1573 /// Parses a CLSID's file type mask element.
1574 /// </summary>
1575 /// <param name="node">Element to parse.</param>
1576 /// <returns>String representing the file type mask elements.</returns>
1577 private string ParseFileTypeMaskElement(XElement node)
1578 {
1579 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1580 int cb = 0;
1581 int offset = CompilerConstants.IntegerNotSet;
1582 string mask = null;
1583 string value = null;
1584
1585 foreach (XAttribute 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 "Mask":
1592 mask = this.core.GetAttributeValue(sourceLineNumbers, attrib);
1593 break;
1594 case "Offset":
1595 offset = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, int.MaxValue);
1596 break;
1597 case "Value":
1598 value = this.core.GetAttributeValue(sourceLineNumbers, attrib);
1599 break;
1600 default:
1601 this.core.UnexpectedAttribute(node, attrib);
1602 break;
1603 }
1604 }
1605 else
1606 {
1607 this.core.ParseExtensionAttribute(node, attrib);
1608 }
1609 }
1610
1611
1612 if (null == mask)
1613 {
1614 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Mask"));
1615 }
1616
1617 if (CompilerConstants.IntegerNotSet == offset)
1618 {
1619 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Offset"));
1620 }
1621
1622 if (null == value)
1623 {
1624 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value"));
1625 }
1626
1627 this.core.ParseForExtensionElements(node);
1628
1629 if (!this.core.EncounteredError)
1630 {
1631 if (mask.Length != value.Length)
1632 {
1633 this.core.OnMessage(WixErrors.ValueAndMaskMustBeSameLength(sourceLineNumbers));
1634 }
1635 cb = mask.Length / 2;
1636 }
1637
1638 return String.Concat(offset.ToString(CultureInfo.InvariantCulture.NumberFormat), ",", cb.ToString(CultureInfo.InvariantCulture.NumberFormat), ",", mask, ",", value);
1639 }
1640
1641 /// <summary>
1642 /// Parses a product search element.
1643 /// </summary>
1644 /// <param name="node">Element to parse.</param>
1645 /// <returns>Signature for search element.</returns>
1646 private void ParseProductSearchElement(XElement node, string propertyId)
1647 {
1648 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1649
1650 string upgradeCode = null;
1651 string language = null;
1652 string maximum = null;
1653 string minimum = null;
1654 int options = MsiInterop.MsidbUpgradeAttributesVersionMinInclusive | MsiInterop.MsidbUpgradeAttributesOnlyDetect;
1655
1656 foreach (XAttribute attrib in node.Attributes())
1657 {
1658 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
1659 {
1660 switch (attrib.Name.LocalName)
1661 {
1662 case "ExcludeLanguages":
1663 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
1664 {
1665 options |= MsiInterop.MsidbUpgradeAttributesLanguagesExclusive;
1666 }
1667 break;
1668 case "IncludeMaximum":
1669 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
1670 {
1671 options |= MsiInterop.MsidbUpgradeAttributesVersionMaxInclusive;
1672 }
1673 break;
1674 case "IncludeMinimum": // this is "yes" by default
1675 if (YesNoType.No == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
1676 {
1677 options &= ~MsiInterop.MsidbUpgradeAttributesVersionMinInclusive;
1678 }
1679 break;
1680 case "Language":
1681 language = this.core.GetAttributeValue(sourceLineNumbers, attrib);
1682 break;
1683 case "Minimum":
1684 minimum = this.core.GetAttributeValue(sourceLineNumbers, attrib);
1685 break;
1686 case "Maximum":
1687 maximum = this.core.GetAttributeValue(sourceLineNumbers, attrib);
1688 break;
1689 case "UpgradeCode":
1690 upgradeCode = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
1691 break;
1692 default:
1693 this.core.UnexpectedAttribute(node, attrib);
1694 break;
1695 }
1696 }
1697 else
1698 {
1699 this.core.ParseExtensionAttribute(node, attrib);
1700 }
1701 }
1702
1703 if (null == minimum && null == maximum)
1704 {
1705 this.core.OnMessage(WixErrors.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "Minimum", "Maximum"));
1706 }
1707
1708 this.core.ParseForExtensionElements(node);
1709
1710 if (!this.core.EncounteredError)
1711 {
1712 Row row = this.core.CreateRow(sourceLineNumbers, "Upgrade");
1713 row[0] = upgradeCode;
1714 row[1] = minimum;
1715 row[2] = maximum;
1716 row[3] = language;
1717 row[4] = options;
1718 row[6] = propertyId;
1719 }
1720 }
1721
1722 /// <summary>
1723 /// Parses a registry search element.
1724 /// </summary>
1725 /// <param name="node">Element to parse.</param>
1726 /// <returns>Signature for search element.</returns>
1727 private string ParseRegistrySearchElement(XElement node)
1728 {
1729 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1730 bool explicitWin64 = false;
1731 Identifier id = null;
1732 string key = null;
1733 string name = null;
1734 string signature = null;
1735 int root = CompilerConstants.IntegerNotSet;
1736 int type = CompilerConstants.IntegerNotSet;
1737 bool search64bit = false;
1738
1739 foreach (XAttribute attrib in node.Attributes())
1740 {
1741 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
1742 {
1743 switch (attrib.Name.LocalName)
1744 {
1745 case "Id":
1746 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
1747 break;
1748 case "Key":
1749 key = this.core.GetAttributeValue(sourceLineNumbers, attrib);
1750 break;
1751 case "Name":
1752 name = this.core.GetAttributeValue(sourceLineNumbers, attrib);
1753 break;
1754 case "Root":
1755 root = this.core.GetAttributeMsidbRegistryRootValue(sourceLineNumbers, attrib, false);
1756 break;
1757 case "Type":
1758 string typeValue = this.core.GetAttributeValue(sourceLineNumbers, attrib);
1759 if (0 < typeValue.Length)
1760 {
1761 Wix.RegistrySearch.TypeType typeType = Wix.RegistrySearch.ParseTypeType(typeValue);
1762 switch (typeType)
1763 {
1764 case Wix.RegistrySearch.TypeType.directory:
1765 type = 0;
1766 break;
1767 case Wix.RegistrySearch.TypeType.file:
1768 type = 1;
1769 break;
1770 case Wix.RegistrySearch.TypeType.raw:
1771 type = 2;
1772 break;
1773 default:
1774 this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Type", typeValue, "directory", "file", "raw"));
1775 break;
1776 }
1777 }
1778 break;
1779 case "Win64":
1780 explicitWin64 = true;
1781 search64bit = YesNoType.Yes == this.core.GetAttributeYesNoValue(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 if (!explicitWin64 && (Platform.IA64 == this.CurrentPlatform || Platform.X64 == this.CurrentPlatform))
1795 {
1796 search64bit = true;
1797 }
1798
1799 if (null == id)
1800 {
1801 id = this.core.CreateIdentifier("reg", root.ToString(), key, name, type.ToString(), search64bit.ToString());
1802 }
1803
1804 if (null == key)
1805 {
1806 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key"));
1807 }
1808
1809 if (CompilerConstants.IntegerNotSet == root)
1810 {
1811 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root"));
1812 }
1813
1814 if (CompilerConstants.IntegerNotSet == type)
1815 {
1816 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Type"));
1817 }
1818
1819 signature = id.Id;
1820 bool oneChild = false;
1821 foreach (XElement child in node.Elements())
1822 {
1823 if (CompilerCore.WixNamespace == child.Name.Namespace)
1824 {
1825 switch (child.Name.LocalName)
1826 {
1827 case "DirectorySearch":
1828 if (oneChild)
1829 {
1830 this.core.OnMessage(WixErrors.TooManySearchElements(sourceLineNumbers, node.Name.LocalName));
1831 }
1832 oneChild = true;
1833
1834 // directorysearch parentage should work like directory element, not the rest of the signature type because of the DrLocator.Parent column
1835 signature = this.ParseDirectorySearchElement(child, id.Id);
1836 break;
1837 case "DirectorySearchRef":
1838 if (oneChild)
1839 {
1840 this.core.OnMessage(WixErrors.TooManySearchElements(sourceLineNumbers, node.Name.LocalName));
1841 }
1842 oneChild = true;
1843 signature = this.ParseDirectorySearchRefElement(child, id.Id);
1844 break;
1845 case "FileSearch":
1846 if (oneChild)
1847 {
1848 this.core.OnMessage(WixErrors.TooManySearchElements(sourceLineNumbers, node.Name.LocalName));
1849 }
1850 oneChild = true;
1851 signature = this.ParseFileSearchElement(child, id.Id, false, CompilerConstants.IntegerNotSet);
1852 id = new Identifier(signature, AccessModifier.Private); // FileSearch signatures override parent signatures
1853 break;
1854 case "FileSearchRef":
1855 if (oneChild)
1856 {
1857 this.core.OnMessage(WixErrors.TooManySearchElements(sourceLineNumbers, node.Name.LocalName));
1858 }
1859 oneChild = true;
1860 string newId = this.ParseSimpleRefElement(child, "Signature"); // FileSearch signatures override parent signatures
1861 id = new Identifier(newId, AccessModifier.Private);
1862 signature = null;
1863 break;
1864 default:
1865 this.core.UnexpectedElement(node, child);
1866 break;
1867 }
1868 }
1869 else
1870 {
1871 this.core.ParseExtensionElement(node, child);
1872 }
1873 }
1874
1875
1876 if (!this.core.EncounteredError)
1877 {
1878 Row row = this.core.CreateRow(sourceLineNumbers, "RegLocator", id);
1879 row[1] = root;
1880 row[2] = key;
1881 row[3] = name;
1882 row[4] = search64bit ? (type | 16) : type;
1883 }
1884
1885 return signature;
1886 }
1887
1888 /// <summary>
1889 /// Parses a registry search reference element.
1890 /// </summary>
1891 /// <param name="node">Element to parse.</param>
1892 /// <returns>Signature of referenced search element.</returns>
1893 private string ParseRegistrySearchRefElement(XElement node)
1894 {
1895 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1896 string id = null;
1897
1898 foreach (XAttribute attrib in node.Attributes())
1899 {
1900 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
1901 {
1902 switch (attrib.Name.LocalName)
1903 {
1904 case "Id":
1905 id = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
1906 this.core.CreateSimpleReference(sourceLineNumbers, "RegLocator", id);
1907 break;
1908 default:
1909 this.core.UnexpectedAttribute(node, attrib);
1910 break;
1911 }
1912 }
1913 else
1914 {
1915 this.core.ParseExtensionAttribute(node, attrib);
1916 }
1917 }
1918
1919 if (null == id)
1920 {
1921 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
1922 }
1923
1924 this.core.ParseForExtensionElements(node);
1925
1926 return id; // the id of the RegistrySearchRef element is its signature
1927 }
1928
1929 /// <summary>
1930 /// Parses child elements for search signatures.
1931 /// </summary>
1932 /// <param name="node">Node whose children we are parsing.</param>
1933 /// <returns>Returns list of string signatures.</returns>
1934 private List<string> ParseSearchSignatures(XElement node)
1935 {
1936 List<string> signatures = new List<string>();
1937
1938 foreach (XElement child in node.Elements())
1939 {
1940 string signature = null;
1941 if (CompilerCore.WixNamespace == child.Name.Namespace)
1942 {
1943 switch (child.Name.LocalName)
1944 {
1945 case "ComplianceDrive":
1946 signature = this.ParseComplianceDriveElement(child);
1947 break;
1948 case "ComponentSearch":
1949 signature = this.ParseComponentSearchElement(child);
1950 break;
1951 case "DirectorySearch":
1952 signature = this.ParseDirectorySearchElement(child, String.Empty);
1953 break;
1954 case "DirectorySearchRef":
1955 signature = this.ParseDirectorySearchRefElement(child, String.Empty);
1956 break;
1957 case "IniFileSearch":
1958 signature = this.ParseIniFileSearchElement(child);
1959 break;
1960 case "ProductSearch":
1961 // handled in ParsePropertyElement
1962 break;
1963 case "RegistrySearch":
1964 signature = this.ParseRegistrySearchElement(child);
1965 break;
1966 case "RegistrySearchRef":
1967 signature = this.ParseRegistrySearchRefElement(child);
1968 break;
1969 default:
1970 this.core.UnexpectedElement(node, child);
1971 break;
1972 }
1973 }
1974 else
1975 {
1976 this.core.ParseExtensionElement(node, child);
1977 }
1978
1979
1980 if (!String.IsNullOrEmpty(signature))
1981 {
1982 signatures.Add(signature);
1983 }
1984 }
1985
1986 return signatures;
1987 }
1988
1989 /// <summary>
1990 /// Parses a compliance drive element.
1991 /// </summary>
1992 /// <param name="node">Element to parse.</param>
1993 /// <returns>Signature of nested search elements.</returns>
1994 private string ParseComplianceDriveElement(XElement node)
1995 {
1996 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1997 string signature = null;
1998
1999 bool oneChild = false;
2000 foreach (XElement child in node.Elements())
2001 {
2002 if (CompilerCore.WixNamespace == child.Name.Namespace)
2003 {
2004 SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2005 switch (child.Name.LocalName)
2006 {
2007 case "DirectorySearch":
2008 if (oneChild)
2009 {
2010 this.core.OnMessage(WixErrors.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName));
2011 }
2012 oneChild = true;
2013 signature = this.ParseDirectorySearchElement(child, "CCP_DRIVE");
2014 break;
2015 case "DirectorySearchRef":
2016 if (oneChild)
2017 {
2018 this.core.OnMessage(WixErrors.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName));
2019 }
2020 oneChild = true;
2021 signature = this.ParseDirectorySearchRefElement(child, "CCP_DRIVE");
2022 break;
2023 default:
2024 this.core.UnexpectedElement(node, child);
2025 break;
2026 }
2027 }
2028 else
2029 {
2030 this.core.ParseExtensionElement(node, child);
2031 }
2032 }
2033
2034 if (null == signature)
2035 {
2036 this.core.OnMessage(WixErrors.SearchElementRequired(sourceLineNumbers, node.Name.LocalName));
2037 }
2038
2039 return signature;
2040 }
2041
2042 /// <summary>
2043 /// Parses a compilance check element.
2044 /// </summary>
2045 /// <param name="node">Element to parse.</param>
2046 private void ParseComplianceCheckElement(XElement node)
2047 {
2048 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2049
2050 foreach (XAttribute attrib in node.Attributes())
2051 {
2052 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
2053 {
2054 switch (attrib.Name.LocalName)
2055 {
2056 default:
2057 this.core.UnexpectedAttribute(node, attrib);
2058 break;
2059 }
2060 }
2061 else
2062 {
2063 this.core.ParseExtensionAttribute(node, attrib);
2064 }
2065 }
2066
2067 string signature = null;
2068
2069 // see if this property is used for appSearch
2070 List<string> signatures = this.ParseSearchSignatures(node);
2071 foreach (string sig in signatures)
2072 {
2073 // if we haven't picked a signature for this ComplianceCheck pick
2074 // this one
2075 if (null == signature)
2076 {
2077 signature = sig;
2078 }
2079 else if (signature != sig)
2080 {
2081 // all signatures under a ComplianceCheck must be the same
2082 this.core.OnMessage(WixErrors.MultipleIdentifiersFound(sourceLineNumbers, node.Name.LocalName, sig, signature));
2083 }
2084 }
2085
2086 if (null == signature)
2087 {
2088 this.core.OnMessage(WixErrors.SearchElementRequired(sourceLineNumbers, node.Name.LocalName));
2089 }
2090
2091 if (!this.core.EncounteredError)
2092 {
2093 Row row = this.core.CreateRow(sourceLineNumbers, "CCPSearch");
2094 row[0] = signature;
2095 }
2096 }
2097
2098 /// <summary>
2099 /// Parses a component element.
2100 /// </summary>
2101 /// <param name="node">Element to parse.</param>
2102 /// <param name="parentType">Type of component's complex reference parent. Will be Uknown if there is no parent.</param>
2103 /// <param name="parentId">Optional identifier for component's primary parent.</param>
2104 /// <param name="parentLanguage">Optional string for component's parent's language.</param>
2105 /// <param name="diskId">Optional disk id inherited from parent directory.</param>
2106 /// <param name="directoryId">Optional identifier for component's directory.</param>
2107 /// <param name="srcPath">Optional source path for files up to this point.</param>
2108 [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
2109 private void ParseComponentElement(XElement node, ComplexReferenceParentType parentType, string parentId, string parentLanguage, int diskId, string directoryId, string srcPath)
2110 {
2111 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2112
2113 int bits = 0;
2114 int comPlusBits = CompilerConstants.IntegerNotSet;
2115 string condition = null;
2116 bool encounteredODBCDataSource = false;
2117 bool explicitWin64 = false;
2118 int files = 0;
2119 string guid = "*";
2120 string componentIdPlaceholder = String.Format(Compiler.DefaultComponentIdPlaceholderFormat, this.componentIdPlaceholdersResolver.VariableCount); // placeholder id for defaulting Component/@Id to keypath id.
2121 string componentIdPlaceholderWixVariable = String.Format(Compiler.DefaultComponentIdPlaceholderWixVariableFormat, componentIdPlaceholder);
2122 Identifier id = new Identifier(componentIdPlaceholderWixVariable, AccessModifier.Private);
2123 int keyBits = 0;
2124 bool keyFound = false;
2125 string keyPath = null;
2126 bool shouldAddCreateFolder = false;
2127 bool win64 = false;
2128 bool multiInstance = false;
2129 List<string> symbols = new List<string>();
2130 string feature = null;
2131
2132 foreach (XAttribute attrib in node.Attributes())
2133 {
2134 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
2135 {
2136 switch (attrib.Name.LocalName)
2137 {
2138 case "Id":
2139 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
2140 break;
2141 case "ComPlusFlags":
2142 comPlusBits = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue);
2143 break;
2144 case "DisableRegistryReflection":
2145 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
2146 {
2147 bits |= MsiInterop.MsidbComponentAttributesDisableRegistryReflection;
2148 }
2149 break;
2150 case "Directory":
2151 directoryId = this.core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, directoryId);
2152 break;
2153 case "DiskId":
2154 diskId = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, short.MaxValue);
2155 break;
2156 case "Feature":
2157 feature = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
2158 break;
2159 case "Guid":
2160 guid = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, true, true);
2161 break;
2162 case "KeyPath":
2163 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
2164 {
2165 keyFound = true;
2166 keyPath = null;
2167 keyBits = 0;
2168 shouldAddCreateFolder = true;
2169 }
2170 break;
2171 case "Location":
2172 string location = this.core.GetAttributeValue(sourceLineNumbers, attrib);
2173 if (0 < location.Length)
2174 {
2175 Wix.Component.LocationType locationType = Wix.Component.ParseLocationType(location);
2176 switch (locationType)
2177 {
2178 case Wix.Component.LocationType.either:
2179 bits |= MsiInterop.MsidbComponentAttributesOptional;
2180 break;
2181 case Wix.Component.LocationType.local: // this is the default
2182 break;
2183 case Wix.Component.LocationType.source:
2184 bits |= MsiInterop.MsidbComponentAttributesSourceOnly;
2185 break;
2186 default:
2187 this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "either", "local", "source"));
2188 break;
2189 }
2190 }
2191 break;
2192 case "MultiInstance":
2193 multiInstance = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
2194 break;
2195 case "NeverOverwrite":
2196 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
2197 {
2198 bits |= MsiInterop.MsidbComponentAttributesNeverOverwrite;
2199 }
2200 break;
2201 case "Permanent":
2202 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
2203 {
2204 bits |= MsiInterop.MsidbComponentAttributesPermanent;
2205 }
2206 break;
2207 case "Shared":
2208 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
2209 {
2210 bits |= MsiInterop.MsidbComponentAttributesShared;
2211 }
2212 break;
2213 case "SharedDllRefCount":
2214 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
2215 {
2216 bits |= MsiInterop.MsidbComponentAttributesSharedDllRefCount;
2217 }
2218 break;
2219 case "Transitive":
2220 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
2221 {
2222 bits |= MsiInterop.MsidbComponentAttributesTransitive;
2223 }
2224 break;
2225 case "UninstallWhenSuperseded":
2226 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
2227 {
2228 bits |= MsiInterop.MsidbComponentAttributesUninstallOnSupersedence;
2229 }
2230 break;
2231 case "Win64":
2232 explicitWin64 = true;
2233 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
2234 {
2235 bits |= MsiInterop.MsidbComponentAttributes64bit;
2236 win64 = true;
2237 }
2238 break;
2239 default:
2240 this.core.UnexpectedAttribute(node, attrib);
2241 break;
2242 }
2243 }
2244 else
2245 {
2246 this.core.ParseExtensionAttribute(node, attrib);
2247 }
2248 }
2249
2250 if (!explicitWin64 && (Platform.IA64 == this.CurrentPlatform || Platform.X64 == this.CurrentPlatform))
2251 {
2252 bits |= MsiInterop.MsidbComponentAttributes64bit;
2253 win64 = true;
2254 }
2255
2256 if (null == directoryId)
2257 {
2258 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Directory"));
2259 }
2260
2261 if (String.IsNullOrEmpty(guid) && MsiInterop.MsidbComponentAttributesShared == (bits & MsiInterop.MsidbComponentAttributesShared))
2262 {
2263 this.core.OnMessage(WixErrors.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Shared", "yes", "Guid", ""));
2264 }
2265
2266 if (String.IsNullOrEmpty(guid) && MsiInterop.MsidbComponentAttributesPermanent == (bits & MsiInterop.MsidbComponentAttributesPermanent))
2267 {
2268 this.core.OnMessage(WixErrors.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Permanent", "yes", "Guid", ""));
2269 }
2270
2271 if (null != feature)
2272 {
2273 if (this.compilingModule)
2274 {
2275 this.core.OnMessage(WixErrors.IllegalAttributeInMergeModule(sourceLineNumbers, node.Name.LocalName, "Feature"));
2276 }
2277 else
2278 {
2279 if (ComplexReferenceParentType.Feature == parentType || ComplexReferenceParentType.FeatureGroup == parentType)
2280 {
2281 this.core.OnMessage(WixErrors.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "Feature", node.Parent.Name.LocalName));
2282 }
2283 else
2284 {
2285 this.core.CreateComplexReference(sourceLineNumbers, ComplexReferenceParentType.Feature, feature, null, ComplexReferenceChildType.Component, id.Id, true);
2286 }
2287 }
2288 }
2289
2290 foreach (XElement child in node.Elements())
2291 {
2292 YesNoType keyPathSet = YesNoType.NotSet;
2293 string keyPossible = null;
2294 int keyBit = 0;
2295
2296 if (CompilerCore.WixNamespace == child.Name.Namespace)
2297 {
2298 switch (child.Name.LocalName)
2299 {
2300 case "AppId":
2301 this.ParseAppIdElement(child, id.Id, YesNoType.NotSet, null, null, null);
2302 break;
2303 case "Category":
2304 this.ParseCategoryElement(child, id.Id);
2305 break;
2306 case "Class":
2307 this.ParseClassElement(child, id.Id, YesNoType.NotSet, null, null, null, null);
2308 break;
2309 case "Condition":
2310 if (null != condition)
2311 {
2312 SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2313 this.core.OnMessage(WixErrors.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, child.Name.LocalName));
2314 }
2315 condition = this.ParseConditionElement(child, node.Name.LocalName, null, null);
2316 break;
2317 case "CopyFile":
2318 this.ParseCopyFileElement(child, id.Id, null);
2319 break;
2320 case "CreateFolder":
2321 string createdFolder = this.ParseCreateFolderElement(child, id.Id, directoryId, win64);
2322 if (directoryId == createdFolder)
2323 {
2324 shouldAddCreateFolder = false;
2325 }
2326 break;
2327 case "Environment":
2328 this.ParseEnvironmentElement(child, id.Id);
2329 break;
2330 case "Extension":
2331 this.ParseExtensionElement(child, id.Id, YesNoType.NotSet, null);
2332 break;
2333 case "File":
2334 keyPathSet = this.ParseFileElement(child, id.Id, directoryId, diskId, srcPath, out keyPossible, win64, guid);
2335 if (null != keyPossible)
2336 {
2337 keyBit = 0;
2338 }
2339 files++;
2340 break;
2341 case "IniFile":
2342 this.ParseIniFileElement(child, id.Id);
2343 break;
2344 case "Interface":
2345 this.ParseInterfaceElement(child, id.Id, null, null, null, null);
2346 break;
2347 case "IsolateComponent":
2348 this.ParseIsolateComponentElement(child, id.Id);
2349 break;
2350 case "ODBCDataSource":
2351 keyPathSet = this.ParseODBCDataSource(child, id.Id, null, out keyPossible);
2352 keyBit = MsiInterop.MsidbComponentAttributesODBCDataSource;
2353 encounteredODBCDataSource = true;
2354 break;
2355 case "ODBCDriver":
2356 this.ParseODBCDriverOrTranslator(child, id.Id, null, this.tableDefinitions["ODBCDriver"]);
2357 break;
2358 case "ODBCTranslator":
2359 this.ParseODBCDriverOrTranslator(child, id.Id, null, this.tableDefinitions["ODBCTranslator"]);
2360 break;
2361 case "ProgId":
2362 bool foundExtension = false;
2363 this.ParseProgIdElement(child, id.Id, YesNoType.NotSet, null, null, null, ref foundExtension, YesNoType.NotSet);
2364 break;
2365 case "RegistryKey":
2366 keyPathSet = this.ParseRegistryKeyElement(child, id.Id, CompilerConstants.IntegerNotSet, null, win64, out keyPossible);
2367 keyBit = MsiInterop.MsidbComponentAttributesRegistryKeyPath;
2368 break;
2369 case "RegistryValue":
2370 keyPathSet = this.ParseRegistryValueElement(child, id.Id, CompilerConstants.IntegerNotSet, null, win64, out keyPossible);
2371 keyBit = MsiInterop.MsidbComponentAttributesRegistryKeyPath;
2372 break;
2373 case "RemoveFile":
2374 this.ParseRemoveFileElement(child, id.Id, directoryId);
2375 break;
2376 case "RemoveFolder":
2377 this.ParseRemoveFolderElement(child, id.Id, directoryId);
2378 break;
2379 case "RemoveRegistryKey":
2380 this.ParseRemoveRegistryKeyElement(child, id.Id);
2381 break;
2382 case "RemoveRegistryValue":
2383 this.ParseRemoveRegistryValueElement(child, id.Id);
2384 break;
2385 case "ReserveCost":
2386 this.ParseReserveCostElement(child, id.Id, directoryId);
2387 break;
2388 case "ServiceConfig":
2389 this.ParseServiceConfigElement(child, id.Id, null);
2390 break;
2391 case "ServiceConfigFailureActions":
2392 this.ParseServiceConfigFailureActionsElement(child, id.Id, null);
2393 break;
2394 case "ServiceControl":
2395 this.ParseServiceControlElement(child, id.Id);
2396 break;
2397 case "ServiceInstall":
2398 this.ParseServiceInstallElement(child, id.Id, win64);
2399 break;
2400 case "Shortcut":
2401 this.ParseShortcutElement(child, id.Id, node.Name.LocalName, directoryId, YesNoType.No);
2402 break;
2403 case "SymbolPath":
2404 symbols.Add(this.ParseSymbolPathElement(child));
2405 break;
2406 case "TypeLib":
2407 this.ParseTypeLibElement(child, id.Id, null, win64);
2408 break;
2409 default:
2410 this.core.UnexpectedElement(node, child);
2411 break;
2412 }
2413 }
2414 else
2415 {
2416 Dictionary<string, string> context = new Dictionary<string, string>() { { "ComponentId", id.Id }, { "DirectoryId", directoryId }, { "Win64", win64.ToString() }, };
2417 ComponentKeyPath possibleKeyPath = this.core.ParsePossibleKeyPathExtensionElement(node, child, context);
2418 if (null != possibleKeyPath)
2419 {
2420 if (ComponentKeyPathType.None == possibleKeyPath.Type)
2421 {
2422 keyPathSet = YesNoType.No;
2423 }
2424 else
2425 {
2426 keyPathSet = possibleKeyPath.Explicit ? YesNoType.Yes : YesNoType.NotSet;
2427
2428 if (!String.IsNullOrEmpty(possibleKeyPath.Id))
2429 {
2430 keyPossible = possibleKeyPath.Id;
2431 }
2432
2433 if (ComponentKeyPathType.Registry == possibleKeyPath.Type || ComponentKeyPathType.RegistryFormatted == possibleKeyPath.Type)
2434 {
2435 keyBit = MsiInterop.MsidbComponentAttributesRegistryKeyPath;
2436 }
2437 }
2438 }
2439 }
2440
2441 // Verify that either the key path is not set, or it is set along with a key path ID.
2442 Debug.Assert(YesNoType.Yes != keyPathSet || (YesNoType.Yes == keyPathSet && null != keyPossible));
2443
2444 if (keyFound && YesNoType.Yes == keyPathSet)
2445 {
2446 this.core.OnMessage(WixErrors.ComponentMultipleKeyPaths(sourceLineNumbers, node.Name.LocalName, "KeyPath", "yes", "File", "RegistryValue", "ODBCDataSource"));
2447 }
2448
2449 // if a possible KeyPath has been found and that value was explicitly set as
2450 // the KeyPath of the component, set it now. Alternatively, if a possible
2451 // KeyPath has been found and no KeyPath has been previously set, use this
2452 // value as the default KeyPath of the component
2453 if (!String.IsNullOrEmpty(keyPossible) && (YesNoType.Yes == keyPathSet || (YesNoType.NotSet == keyPathSet && String.IsNullOrEmpty(keyPath) && !keyFound)))
2454 {
2455 keyFound = YesNoType.Yes == keyPathSet;
2456 keyPath = keyPossible;
2457 keyBits = keyBit;
2458 }
2459 }
2460
2461
2462 if (shouldAddCreateFolder)
2463 {
2464 Row row = this.core.CreateRow(sourceLineNumbers, "CreateFolder");
2465 row[0] = directoryId;
2466 row[1] = id.Id;
2467 }
2468
2469 // check for conditions that exclude this component from using generated guids
2470 bool isGeneratableGuidOk = "*" == guid;
2471 if (isGeneratableGuidOk)
2472 {
2473 if (encounteredODBCDataSource)
2474 {
2475 this.core.OnMessage(WixErrors.IllegalComponentWithAutoGeneratedGuid(sourceLineNumbers));
2476 isGeneratableGuidOk = false;
2477 }
2478
2479 if (0 != files && MsiInterop.MsidbComponentAttributesRegistryKeyPath == keyBits)
2480 {
2481 this.core.OnMessage(WixErrors.IllegalComponentWithAutoGeneratedGuid(sourceLineNumbers, true));
2482 isGeneratableGuidOk = false;
2483 }
2484 }
2485
2486 // check for implicit KeyPath which can easily be accidentally changed
2487 if (this.showPedanticMessages && !keyFound && !isGeneratableGuidOk)
2488 {
2489 this.core.OnMessage(WixErrors.ImplicitComponentKeyPath(sourceLineNumbers, id.Id));
2490 }
2491
2492 // if there isn't an @Id attribute value, replace the placeholder with the id of the keypath.
2493 // either an explicit KeyPath="yes" attribute must be specified or requirements for
2494 // generatable guid must be met.
2495 if (componentIdPlaceholderWixVariable == id.Id)
2496 {
2497 if (isGeneratableGuidOk || keyFound && !String.IsNullOrEmpty(keyPath))
2498 {
2499 this.componentIdPlaceholdersResolver.AddVariable(componentIdPlaceholder, keyPath);
2500
2501 id = new Identifier(keyPath, AccessModifier.Private);
2502 }
2503 else
2504 {
2505 this.core.OnMessage(WixErrors.CannotDefaultComponentId(sourceLineNumbers));
2506 }
2507 }
2508
2509 // If an id was not determined by now, we have to error.
2510 if (null == id)
2511 {
2512 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
2513 }
2514
2515 // finally add the Component table row
2516 if (!this.core.EncounteredError)
2517 {
2518 Row row = this.core.CreateRow(sourceLineNumbers, "Component", id);
2519 row[1] = guid;
2520 row[2] = directoryId;
2521 row[3] = bits | keyBits;
2522 row[4] = condition;
2523 row[5] = keyPath;
2524
2525 if (multiInstance)
2526 {
2527 Row instanceComponentRow = this.core.CreateRow(sourceLineNumbers, "WixInstanceComponent");
2528 instanceComponentRow[0] = id;
2529 }
2530
2531 if (0 < symbols.Count)
2532 {
2533 WixDeltaPatchSymbolPathsRow symbolRow = (WixDeltaPatchSymbolPathsRow)this.core.CreateRow(sourceLineNumbers, "WixDeltaPatchSymbolPaths", id);
2534 symbolRow.Type = SymbolPathType.Component;
2535 symbolRow.SymbolPaths = String.Join(";", symbols);
2536 }
2537
2538 // Complus
2539 if (CompilerConstants.IntegerNotSet != comPlusBits)
2540 {
2541 row = this.core.CreateRow(sourceLineNumbers, "Complus");
2542 row[0] = id;
2543 row[1] = comPlusBits;
2544 }
2545
2546 // if this is a module, automatically add this component to the references to ensure it gets in the ModuleComponents table
2547 if (this.compilingModule)
2548 {
2549 this.core.CreateComplexReference(sourceLineNumbers, ComplexReferenceParentType.Module, this.activeName, this.activeLanguage, ComplexReferenceChildType.Component, id.Id, false);
2550 }
2551 else if (ComplexReferenceParentType.Unknown != parentType && null != parentId) // if parent was provided, add a complex reference to that.
2552 {
2553 // If the Component is defined directly under a feature, then mark the complex reference primary.
2554 this.core.CreateComplexReference(sourceLineNumbers, parentType, parentId, parentLanguage, ComplexReferenceChildType.Component, id.Id, ComplexReferenceParentType.Feature == parentType);
2555 }
2556 }
2557 }
2558
2559 /// <summary>
2560 /// Parses a component group element.
2561 /// </summary>
2562 /// <param name="node">Element to parse.</param>
2563 [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
2564 private void ParseComponentGroupElement(XElement node, ComplexReferenceParentType parentType, string parentId)
2565 {
2566 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2567 Identifier id = null;
2568 string directoryId = null;
2569 string source = null;
2570
2571 foreach (XAttribute attrib in node.Attributes())
2572 {
2573 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
2574 {
2575 switch (attrib.Name.LocalName)
2576 {
2577 case "Id":
2578 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
2579 break;
2580 case "Directory":
2581 // If the inline syntax is invalid it returns null. Use a static error identifier so the null
2582 // directory identifier here doesn't trickle down false errors into child elements.
2583 directoryId = this.core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, null) ?? "ErrorParsingInlineSyntax";
2584 break;
2585 case "Source":
2586 source = this.core.GetAttributeValue(sourceLineNumbers, attrib);
2587 break;
2588 default:
2589 this.core.UnexpectedAttribute(node, attrib);
2590 break;
2591 }
2592 }
2593 else
2594 {
2595 this.core.ParseExtensionAttribute(node, attrib);
2596 }
2597 }
2598
2599 if (null == id)
2600 {
2601 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
2602 id = Identifier.Invalid;
2603 }
2604
2605 if (!String.IsNullOrEmpty(source) && !source.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal))
2606 {
2607 source = String.Concat(source, Path.DirectorySeparatorChar);
2608 }
2609
2610 foreach (XElement child in node.Elements())
2611 {
2612 if (CompilerCore.WixNamespace == child.Name.Namespace)
2613 {
2614 switch (child.Name.LocalName)
2615 {
2616 case "ComponentGroupRef":
2617 this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.ComponentGroup, id.Id, null);
2618 break;
2619 case "ComponentRef":
2620 this.ParseComponentRefElement(child, ComplexReferenceParentType.ComponentGroup, id.Id, null);
2621 break;
2622 case "Component":
2623 this.ParseComponentElement(child, ComplexReferenceParentType.ComponentGroup, id.Id, null, CompilerConstants.IntegerNotSet, directoryId, source);
2624 break;
2625 default:
2626 this.core.UnexpectedElement(node, child);
2627 break;
2628 }
2629 }
2630 else
2631 {
2632 this.core.ParseExtensionElement(node, child);
2633 }
2634 }
2635
2636 if (!this.core.EncounteredError)
2637 {
2638 Row row = this.core.CreateRow(sourceLineNumbers, "WixComponentGroup", id);
2639
2640 // Add this componentGroup and its parent in WixGroup.
2641 this.core.CreateWixGroupRow(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.ComponentGroup, id.Id);
2642 }
2643 }
2644
2645 /// <summary>
2646 /// Parses a component group reference element.
2647 /// </summary>
2648 /// <param name="node">Element to parse.</param>
2649 /// <param name="parentType">ComplexReferenceParentType of parent element.</param>
2650 /// <param name="parentId">Identifier of parent element (usually a Feature or Module).</param>
2651 /// <param name="parentLanguage">Optional language of parent (only useful for Modules).</param>
2652 private void ParseComponentGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId, string parentLanguage)
2653 {
2654 Debug.Assert(ComplexReferenceParentType.ComponentGroup == parentType || ComplexReferenceParentType.FeatureGroup == parentType || ComplexReferenceParentType.Feature == parentType || ComplexReferenceParentType.Module == parentType);
2655
2656 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2657 string id = null;
2658 YesNoType primary = YesNoType.NotSet;
2659
2660 foreach (XAttribute attrib in node.Attributes())
2661 {
2662 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
2663 {
2664 switch (attrib.Name.LocalName)
2665 {
2666 case "Id":
2667 id = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
2668 this.core.CreateSimpleReference(sourceLineNumbers, "WixComponentGroup", id);
2669 break;
2670 case "Primary":
2671 primary = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
2672 break;
2673 default:
2674 this.core.UnexpectedAttribute(node, attrib);
2675 break;
2676 }
2677 }
2678 else
2679 {
2680 this.core.ParseExtensionAttribute(node, attrib);
2681 }
2682 }
2683
2684 if (null == id)
2685 {
2686 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
2687 }
2688
2689 this.core.ParseForExtensionElements(node);
2690
2691 this.core.CreateComplexReference(sourceLineNumbers, parentType, parentId, parentLanguage, ComplexReferenceChildType.ComponentGroup, id, (YesNoType.Yes == primary));
2692 }
2693
2694 /// <summary>
2695 /// Parses a component reference element.
2696 /// </summary>
2697 /// <param name="node">Element to parse.</param>
2698 /// <param name="parentType">ComplexReferenceParentType of parent element.</param>
2699 /// <param name="parentId">Identifier of parent element (usually a Feature or Module).</param>
2700 /// <param name="parentLanguage">Optional language of parent (only useful for Modules).</param>
2701 private void ParseComponentRefElement(XElement node, ComplexReferenceParentType parentType, string parentId, string parentLanguage)
2702 {
2703 Debug.Assert(ComplexReferenceParentType.FeatureGroup == parentType || ComplexReferenceParentType.ComponentGroup == parentType || ComplexReferenceParentType.Feature == parentType || ComplexReferenceParentType.Module == parentType);
2704
2705 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2706 string id = null;
2707 YesNoType primary = YesNoType.NotSet;
2708
2709 foreach (XAttribute attrib in node.Attributes())
2710 {
2711 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
2712 {
2713 switch (attrib.Name.LocalName)
2714 {
2715 case "Id":
2716 id = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
2717 this.core.CreateSimpleReference(sourceLineNumbers, "Component", id);
2718 break;
2719 case "Primary":
2720 primary = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
2721 break;
2722 default:
2723 this.core.UnexpectedAttribute(node, attrib);
2724 break;
2725 }
2726 }
2727 else
2728 {
2729 this.core.ParseExtensionAttribute(node, attrib);
2730 }
2731 }
2732
2733 if (null == id)
2734 {
2735 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
2736 }
2737
2738 this.core.ParseForExtensionElements(node);
2739
2740 this.core.CreateComplexReference(sourceLineNumbers, parentType, parentId, parentLanguage, ComplexReferenceChildType.Component, id, (YesNoType.Yes == primary));
2741 }
2742
2743 /// <summary>
2744 /// Parses a component search element.
2745 /// </summary>
2746 /// <param name="node">Element to parse.</param>
2747 /// <returns>Signature for search element.</returns>
2748 private string ParseComponentSearchElement(XElement node)
2749 {
2750 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2751 Identifier id = null;
2752 string componentId = null;
2753 int type = MsiInterop.MsidbLocatorTypeFileName;
2754 string signature = null;
2755
2756 foreach (XAttribute attrib in node.Attributes())
2757 {
2758 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
2759 {
2760 switch (attrib.Name.LocalName)
2761 {
2762 case "Id":
2763 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
2764 break;
2765 case "Guid":
2766 componentId = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
2767 break;
2768 case "Type":
2769 string typeValue = this.core.GetAttributeValue(sourceLineNumbers, attrib);
2770 if (0 < typeValue.Length)
2771 {
2772 Wix.ComponentSearch.TypeType typeType = Wix.ComponentSearch.ParseTypeType(typeValue);
2773 switch (typeType)
2774 {
2775 case Wix.ComponentSearch.TypeType.directory:
2776 type = MsiInterop.MsidbLocatorTypeDirectory;
2777 break;
2778 case Wix.ComponentSearch.TypeType.file:
2779 type = MsiInterop.MsidbLocatorTypeFileName;
2780 break;
2781 default:
2782 this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, typeValue, "directory", "file"));
2783 break;
2784 }
2785 }
2786 break;
2787 default:
2788 this.core.UnexpectedAttribute(node, attrib);
2789 break;
2790 }
2791 }
2792 else
2793 {
2794 this.core.ParseExtensionAttribute(node, attrib);
2795 }
2796 }
2797
2798 if (null == id)
2799 {
2800 id = this.core.CreateIdentifier("cmp", componentId, type.ToString());
2801 }
2802
2803 signature = id.Id;
2804 bool oneChild = false;
2805 foreach (XElement child in node.Elements())
2806 {
2807 if (CompilerCore.WixNamespace == child.Name.Namespace)
2808 {
2809 switch (child.Name.LocalName)
2810 {
2811 case "DirectorySearch":
2812 if (oneChild)
2813 {
2814 this.core.OnMessage(WixErrors.TooManySearchElements(sourceLineNumbers, node.Name.LocalName));
2815 }
2816 oneChild = true;
2817
2818 // directorysearch parentage should work like directory element, not the rest of the signature type because of the DrLocator.Parent column
2819 signature = this.ParseDirectorySearchElement(child, id.Id);
2820 break;
2821 case "DirectorySearchRef":
2822 if (oneChild)
2823 {
2824 this.core.OnMessage(WixErrors.TooManySearchElements(sourceLineNumbers, node.Name.LocalName));
2825 }
2826 oneChild = true;
2827 signature = this.ParseDirectorySearchRefElement(child, id.Id);
2828 break;
2829 case "FileSearch":
2830 if (oneChild)
2831 {
2832 this.core.OnMessage(WixErrors.TooManySearchElements(sourceLineNumbers, node.Name.LocalName));
2833 }
2834 oneChild = true;
2835 signature = this.ParseFileSearchElement(child, id.Id, false, CompilerConstants.IntegerNotSet);
2836 id = new Identifier(signature, AccessModifier.Private); // FileSearch signatures override parent signatures
2837 break;
2838 case "FileSearchRef":
2839 if (oneChild)
2840 {
2841 this.core.OnMessage(WixErrors.TooManySearchElements(sourceLineNumbers, node.Name.LocalName));
2842 }
2843 oneChild = true;
2844 string newId = this.ParseSimpleRefElement(child, "Signature"); // FileSearch signatures override parent signatures
2845 id = new Identifier(newId, AccessModifier.Private);
2846 signature = null;
2847 break;
2848 default:
2849 this.core.UnexpectedElement(node, child);
2850 break;
2851 }
2852 }
2853 else
2854 {
2855 this.core.ParseExtensionElement(node, child);
2856 }
2857 }
2858
2859 if (!this.core.EncounteredError)
2860 {
2861 Row row = this.core.CreateRow(sourceLineNumbers, "CompLocator", id);
2862 row[1] = componentId;
2863 row[2] = type;
2864 }
2865
2866 return signature;
2867 }
2868
2869 /// <summary>
2870 /// Parses a create folder element.
2871 /// </summary>
2872 /// <param name="node">Element to parse.</param>
2873 /// <param name="componentId">Identifier for parent component.</param>
2874 /// <param name="directoryId">Default identifier for directory to create.</param>
2875 /// <param name="win64Component">true if the component is 64-bit.</param>
2876 /// <returns>Identifier for the directory that will be created</returns>
2877 private string ParseCreateFolderElement(XElement node, string componentId, string directoryId, bool win64Component)
2878 {
2879 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2880 foreach (XAttribute attrib in node.Attributes())
2881 {
2882 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
2883 {
2884 switch (attrib.Name.LocalName)
2885 {
2886 case "Directory":
2887 directoryId = this.core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, directoryId);
2888 break;
2889 default:
2890 this.core.UnexpectedAttribute(node, attrib);
2891 break;
2892 }
2893 }
2894 else
2895 {
2896 this.core.ParseExtensionAttribute(node, attrib);
2897 }
2898 }
2899
2900 foreach (XElement child in node.Elements())
2901 {
2902 if (CompilerCore.WixNamespace == child.Name.Namespace)
2903 {
2904 switch (child.Name.LocalName)
2905 {
2906 case "Shortcut":
2907 this.ParseShortcutElement(child, componentId, node.Name.LocalName, directoryId, YesNoType.No);
2908 break;
2909 case "Permission":
2910 this.ParsePermissionElement(child, directoryId, "CreateFolder");
2911 break;
2912 case "PermissionEx":
2913 this.ParsePermissionExElement(child, directoryId, "CreateFolder");
2914 break;
2915 default:
2916 this.core.UnexpectedElement(node, child);
2917 break;
2918 }
2919 }
2920 else
2921 {
2922 Dictionary<string, string> context = new Dictionary<string, string>() { { "DirectoryId", directoryId }, { "ComponentId", componentId }, { "Win64", win64Component.ToString() } };
2923 this.core.ParseExtensionElement(node, child, context);
2924 }
2925 }
2926
2927 if (!this.core.EncounteredError)
2928 {
2929 Row row = this.core.CreateRow(sourceLineNumbers, "CreateFolder");
2930 row[0] = directoryId;
2931 row[1] = componentId;
2932 }
2933
2934 return directoryId;
2935 }
2936
2937 /// <summary>
2938 /// Parses a copy file element.
2939 /// </summary>
2940 /// <param name="node">Element to parse.</param>
2941 /// <param name="componentId">Identifier of parent component.</param>
2942 /// <param name="fileId">Identifier of file to copy (null if moving the file).</param>
2943 private void ParseCopyFileElement(XElement node, string componentId, string fileId)
2944 {
2945 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
2946 Identifier id = null;
2947 bool delete = false;
2948 string destinationDirectory = null;
2949 string destinationName = null;
2950 string destinationShortName = null;
2951 string destinationProperty = null;
2952 string sourceDirectory = null;
2953 string sourceFolder = null;
2954 string sourceName = null;
2955 string sourceProperty = null;
2956
2957 foreach (XAttribute attrib in node.Attributes())
2958 {
2959 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
2960 {
2961 switch (attrib.Name.LocalName)
2962 {
2963 case "Id":
2964 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
2965 break;
2966 case "Delete":
2967 delete = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
2968 break;
2969 case "DestinationDirectory":
2970 destinationDirectory = this.core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, null);
2971 break;
2972 case "DestinationName":
2973 destinationName = this.core.GetAttributeLongFilename(sourceLineNumbers, attrib, false);
2974 break;
2975 case "DestinationProperty":
2976 destinationProperty = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
2977 break;
2978 case "DestinationShortName":
2979 destinationShortName = this.core.GetAttributeShortFilename(sourceLineNumbers, attrib, false);
2980 break;
2981 case "FileId":
2982 if (null != fileId)
2983 {
2984 this.core.OnMessage(WixErrors.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, node.Parent.Name.LocalName));
2985 }
2986 fileId = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
2987 this.core.CreateSimpleReference(sourceLineNumbers, "File", fileId);
2988 break;
2989 case "SourceDirectory":
2990 sourceDirectory = this.core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, null);
2991 break;
2992 case "SourceName":
2993 sourceName = this.core.GetAttributeValue(sourceLineNumbers, attrib);
2994 break;
2995 case "SourceProperty":
2996 sourceProperty = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
2997 break;
2998 default:
2999 this.core.UnexpectedAttribute(node, attrib);
3000 break;
3001 }
3002 }
3003 else
3004 {
3005 this.core.ParseExtensionAttribute(node, attrib);
3006 }
3007 }
3008
3009 if (null != sourceFolder && null != sourceDirectory) // SourceFolder and SourceDirectory cannot coexist
3010 {
3011 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFolder", "SourceDirectory"));
3012 }
3013
3014 if (null != sourceFolder && null != sourceProperty) // SourceFolder and SourceProperty cannot coexist
3015 {
3016 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFolder", "SourceProperty"));
3017 }
3018
3019 if (null != sourceDirectory && null != sourceProperty) // SourceDirectory and SourceProperty cannot coexist
3020 {
3021 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceProperty", "SourceDirectory"));
3022 }
3023
3024 if (null != destinationDirectory && null != destinationProperty) // DestinationDirectory and DestinationProperty cannot coexist
3025 {
3026 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DestinationProperty", "DestinationDirectory"));
3027 }
3028
3029 // generate a short file name
3030 if (null == destinationShortName && (null != destinationName && !this.core.IsValidShortFilename(destinationName, false)))
3031 {
3032 destinationShortName = this.core.CreateShortName(destinationName, true, false, node.Name.LocalName, componentId);
3033 }
3034
3035 if (null == id)
3036 {
3037 id = this.core.CreateIdentifier("cf", sourceFolder, sourceDirectory, sourceProperty, destinationDirectory, destinationProperty, destinationName);
3038 }
3039
3040 this.core.ParseForExtensionElements(node);
3041
3042 if (null == fileId)
3043 {
3044 // DestinationDirectory or DestinationProperty must be specified
3045 if (null == destinationDirectory && null == destinationProperty)
3046 {
3047 this.core.OnMessage(WixErrors.ExpectedAttributesWithoutOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DestinationDirectory", "DestinationProperty", "FileId"));
3048 }
3049
3050 if (!this.core.EncounteredError)
3051 {
3052 Row row = this.core.CreateRow(sourceLineNumbers, "MoveFile", id);
3053 row[1] = componentId;
3054 row[2] = sourceName;
3055 row[3] = String.IsNullOrEmpty(destinationShortName) && String.IsNullOrEmpty(destinationName) ? null : GetMsiFilenameValue(destinationShortName, destinationName);
3056 if (null != sourceDirectory)
3057 {
3058 row[4] = sourceDirectory;
3059 }
3060 else if (null != sourceProperty)
3061 {
3062 row[4] = sourceProperty;
3063 }
3064 else
3065 {
3066 row[4] = sourceFolder;
3067 }
3068
3069 if (null != destinationDirectory)
3070 {
3071 row[5] = destinationDirectory;
3072 }
3073 else
3074 {
3075 row[5] = destinationProperty;
3076 }
3077 row[6] = delete ? 1 : 0;
3078 }
3079 }
3080 else // copy the file
3081 {
3082 if (null != sourceDirectory)
3083 {
3084 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceDirectory", "FileId"));
3085 }
3086
3087 if (null != sourceFolder)
3088 {
3089 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFolder", "FileId"));
3090 }
3091
3092 if (null != sourceName)
3093 {
3094 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceName", "FileId"));
3095 }
3096
3097 if (null != sourceProperty)
3098 {
3099 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "SourceProperty", "FileId"));
3100 }
3101
3102 if (delete)
3103 {
3104 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Delete", "FileId"));
3105 }
3106
3107 if (null == destinationName && null == destinationDirectory && null == destinationProperty)
3108 {
3109 this.core.OnMessage(WixWarnings.CopyFileFileIdUseless(sourceLineNumbers));
3110 }
3111
3112 if (!this.core.EncounteredError)
3113 {
3114 Row row = this.core.CreateRow(sourceLineNumbers, "DuplicateFile", id);
3115 row[1] = componentId;
3116 row[2] = fileId;
3117 row[3] = String.IsNullOrEmpty(destinationShortName) && String.IsNullOrEmpty(destinationName) ? null : GetMsiFilenameValue(destinationShortName, destinationName);
3118 if (null != destinationDirectory)
3119 {
3120 row[4] = destinationDirectory;
3121 }
3122 else
3123 {
3124 row[4] = destinationProperty;
3125 }
3126 }
3127 }
3128 }
3129
3130 /// <summary>
3131 /// Parses a CustomAction element.
3132 /// </summary>
3133 /// <param name="node">Element to parse.</param>
3134 private void ParseCustomActionElement(XElement node)
3135 {
3136 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
3137 Identifier id = null;
3138 int bits = 0;
3139 int extendedBits = 0;
3140 bool inlineScript = false;
3141 string innerText = null;
3142 string source = null;
3143 int sourceBits = 0;
3144 YesNoType suppressModularization = YesNoType.NotSet;
3145 string target = null;
3146 int targetBits = 0;
3147 bool explicitWin64 = false;
3148
3149 foreach (XAttribute attrib in node.Attributes())
3150 {
3151 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
3152 {
3153 switch (attrib.Name.LocalName)
3154 {
3155 case "Id":
3156 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
3157 break;
3158 case "BinaryKey":
3159 if (null != source)
3160 {
3161 this.core.OnMessage(WixErrors.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryKey", "Directory", "FileKey", "Property", "Script"));
3162 }
3163 source = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3164 sourceBits = MsiInterop.MsidbCustomActionTypeBinaryData;
3165 this.core.CreateSimpleReference(sourceLineNumbers, "Binary", source); // add a reference to the appropriate Binary
3166 break;
3167 case "Directory":
3168 if (null != source)
3169 {
3170 this.core.OnMessage(WixErrors.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryKey", "Directory", "FileKey", "Property", "Script"));
3171 }
3172 source = this.core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, null);
3173 sourceBits = MsiInterop.MsidbCustomActionTypeDirectory;
3174 break;
3175 case "DllEntry":
3176 if (null != target)
3177 {
3178 this.core.OnMessage(WixErrors.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall"));
3179 }
3180 target = this.core.GetAttributeValue(sourceLineNumbers, attrib);
3181 targetBits = MsiInterop.MsidbCustomActionTypeDll;
3182 break;
3183 case "Error":
3184 if (null != target)
3185 {
3186 this.core.OnMessage(WixErrors.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall"));
3187 }
3188 target = this.core.GetAttributeValue(sourceLineNumbers, attrib);
3189 targetBits = MsiInterop.MsidbCustomActionTypeTextData | MsiInterop.MsidbCustomActionTypeSourceFile;
3190
3191 bool errorReference = true;
3192
3193 try
3194 {
3195 // The target can be either a formatted error string or a literal
3196 // error number. Try to convert to error number to determine whether
3197 // to add a reference. No need to look at the value.
3198 Convert.ToInt32(target, CultureInfo.InvariantCulture.NumberFormat);
3199 }
3200 catch (FormatException)
3201 {
3202 errorReference = false;
3203 }
3204 catch (OverflowException)
3205 {
3206 errorReference = false;
3207 }
3208
3209 if (errorReference)
3210 {
3211 this.core.CreateSimpleReference(sourceLineNumbers, "Error", target);
3212 }
3213 break;
3214 case "ExeCommand":
3215 if (null != target)
3216 {
3217 this.core.OnMessage(WixErrors.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall"));
3218 }
3219 target = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); // one of the few cases where an empty string value is valid
3220 targetBits = MsiInterop.MsidbCustomActionTypeExe;
3221 break;
3222 case "Execute":
3223 string execute = this.core.GetAttributeValue(sourceLineNumbers, attrib);
3224 if (0 < execute.Length)
3225 {
3226 Wix.CustomAction.ExecuteType executeType = Wix.CustomAction.ParseExecuteType(execute);
3227 switch (executeType)
3228 {
3229 case Wix.CustomAction.ExecuteType.commit:
3230 bits |= MsiInterop.MsidbCustomActionTypeInScript | MsiInterop.MsidbCustomActionTypeCommit;
3231 break;
3232 case Wix.CustomAction.ExecuteType.deferred:
3233 bits |= MsiInterop.MsidbCustomActionTypeInScript;
3234 break;
3235 case Wix.CustomAction.ExecuteType.firstSequence:
3236 bits |= MsiInterop.MsidbCustomActionTypeFirstSequence;
3237 break;
3238 case Wix.CustomAction.ExecuteType.immediate:
3239 break;
3240 case Wix.CustomAction.ExecuteType.oncePerProcess:
3241 bits |= MsiInterop.MsidbCustomActionTypeOncePerProcess;
3242 break;
3243 case Wix.CustomAction.ExecuteType.rollback:
3244 bits |= MsiInterop.MsidbCustomActionTypeInScript | MsiInterop.MsidbCustomActionTypeRollback;
3245 break;
3246 case Wix.CustomAction.ExecuteType.secondSequence:
3247 bits |= MsiInterop.MsidbCustomActionTypeClientRepeat;
3248 break;
3249 default:
3250 this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, execute, "commit", "deferred", "firstSequence", "immediate", "oncePerProcess", "rollback", "secondSequence"));
3251 break;
3252 }
3253 }
3254 break;
3255 case "FileKey":
3256 if (null != source)
3257 {
3258 this.core.OnMessage(WixErrors.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryKey", "Directory", "FileKey", "Property", "Script"));
3259 }
3260 source = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3261 sourceBits = MsiInterop.MsidbCustomActionTypeSourceFile;
3262 this.core.CreateSimpleReference(sourceLineNumbers, "File", source); // add a reference to the appropriate File
3263 break;
3264 case "HideTarget":
3265 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
3266 {
3267 bits |= MsiInterop.MsidbCustomActionTypeHideTarget;
3268 }
3269 break;
3270 case "Impersonate":
3271 if (YesNoType.No == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
3272 {
3273 bits |= MsiInterop.MsidbCustomActionTypeNoImpersonate;
3274 }
3275 break;
3276 case "JScriptCall":
3277 if (null != target)
3278 {
3279 this.core.OnMessage(WixErrors.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall"));
3280 }
3281 target = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); // one of the few cases where an empty string value is valid
3282 targetBits = MsiInterop.MsidbCustomActionTypeJScript;
3283 break;
3284 case "PatchUninstall":
3285 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
3286 {
3287 extendedBits |= MsiInterop.MsidbCustomActionTypePatchUninstall;
3288 }
3289 break;
3290 case "Property":
3291 if (null != source)
3292 {
3293 this.core.OnMessage(WixErrors.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryKey", "Directory", "FileKey", "Property", "Script"));
3294 }
3295 source = this.core.GetAttributeValue(sourceLineNumbers, attrib);
3296 sourceBits = MsiInterop.MsidbCustomActionTypeProperty;
3297 break;
3298 case "Return":
3299 string returnValue = this.core.GetAttributeValue(sourceLineNumbers, attrib);
3300 if (0 < returnValue.Length)
3301 {
3302 Wix.CustomAction.ReturnType returnType = Wix.CustomAction.ParseReturnType(returnValue);
3303 switch (returnType)
3304 {
3305 case Wix.CustomAction.ReturnType.asyncNoWait:
3306 bits |= MsiInterop.MsidbCustomActionTypeAsync | MsiInterop.MsidbCustomActionTypeContinue;
3307 break;
3308 case Wix.CustomAction.ReturnType.asyncWait:
3309 bits |= MsiInterop.MsidbCustomActionTypeAsync;
3310 break;
3311 case Wix.CustomAction.ReturnType.check:
3312 break;
3313 case Wix.CustomAction.ReturnType.ignore:
3314 bits |= MsiInterop.MsidbCustomActionTypeContinue;
3315 break;
3316 default:
3317 this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, returnValue, "asyncNoWait", "asyncWait", "check", "ignore"));
3318 break;
3319 }
3320 }
3321 break;
3322 case "Script":
3323 if (null != source)
3324 {
3325 this.core.OnMessage(WixErrors.CustomActionMultipleSources(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinaryKey", "Directory", "FileKey", "Property", "Script"));
3326 }
3327
3328 if (null != target)
3329 {
3330 this.core.OnMessage(WixErrors.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall"));
3331 }
3332
3333 // set the source and target to empty string for error messages when the user sets multiple sources or targets
3334 source = string.Empty;
3335 target = string.Empty;
3336
3337 inlineScript = true;
3338
3339 string script = this.core.GetAttributeValue(sourceLineNumbers, attrib);
3340 if (0 < script.Length)
3341 {
3342 Wix.CustomAction.ScriptType scriptType = Wix.CustomAction.ParseScriptType(script);
3343 switch (scriptType)
3344 {
3345 case Wix.CustomAction.ScriptType.jscript:
3346 sourceBits = MsiInterop.MsidbCustomActionTypeDirectory;
3347 targetBits = MsiInterop.MsidbCustomActionTypeJScript;
3348 break;
3349 case Wix.CustomAction.ScriptType.vbscript:
3350 sourceBits = MsiInterop.MsidbCustomActionTypeDirectory;
3351 targetBits = MsiInterop.MsidbCustomActionTypeVBScript;
3352 break;
3353 default:
3354 this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, script, "jscript", "vbscript"));
3355 break;
3356 }
3357 }
3358 break;
3359 case "SuppressModularization":
3360 suppressModularization = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
3361 break;
3362 case "TerminalServerAware":
3363 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
3364 {
3365 bits |= MsiInterop.MsidbCustomActionTypeTSAware;
3366 }
3367 break;
3368 case "Value":
3369 if (null != target)
3370 {
3371 this.core.OnMessage(WixErrors.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall"));
3372 }
3373 target = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); // one of the few cases where an empty string value is valid
3374 targetBits = MsiInterop.MsidbCustomActionTypeTextData;
3375 break;
3376 case "VBScriptCall":
3377 if (null != target)
3378 {
3379 this.core.OnMessage(WixErrors.CustomActionMultipleTargets(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall"));
3380 }
3381 target = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); // one of the few cases where an empty string value is valid
3382 targetBits = MsiInterop.MsidbCustomActionTypeVBScript;
3383 break;
3384 case "Win64":
3385 explicitWin64 = true;
3386 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
3387 {
3388 bits |= MsiInterop.MsidbCustomActionType64BitScript;
3389 }
3390 break;
3391 default:
3392 this.core.UnexpectedAttribute(node, attrib);
3393 break;
3394 }
3395 }
3396 else
3397 {
3398 this.core.ParseExtensionAttribute(node, attrib);
3399 }
3400 }
3401
3402 if (null == id)
3403 {
3404 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
3405 id = Identifier.Invalid;
3406 }
3407
3408 if (!explicitWin64 && (MsiInterop.MsidbCustomActionTypeVBScript == targetBits || MsiInterop.MsidbCustomActionTypeJScript == targetBits) && (Platform.IA64 == this.CurrentPlatform || Platform.X64 == this.CurrentPlatform))
3409 {
3410 bits |= MsiInterop.MsidbCustomActionType64BitScript;
3411 }
3412
3413 // get the inner text if any exists
3414 innerText = this.core.GetTrimmedInnerText(node);
3415
3416 // if we have an in-lined Script CustomAction ensure no source or target attributes were provided
3417 if (inlineScript)
3418 {
3419 target = innerText;
3420 }
3421 else if (MsiInterop.MsidbCustomActionTypeVBScript == targetBits) // non-inline vbscript
3422 {
3423 if (null == source)
3424 {
3425 this.core.OnMessage(WixErrors.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "VBScriptCall", "BinaryKey", "FileKey", "Property"));
3426 }
3427 else if (MsiInterop.MsidbCustomActionTypeDirectory == sourceBits)
3428 {
3429 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "VBScriptCall", "Directory"));
3430 }
3431 }
3432 else if (MsiInterop.MsidbCustomActionTypeJScript == targetBits) // non-inline jscript
3433 {
3434 if (null == source)
3435 {
3436 this.core.OnMessage(WixErrors.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "JScriptCall", "BinaryKey", "FileKey", "Property"));
3437 }
3438 else if (MsiInterop.MsidbCustomActionTypeDirectory == sourceBits)
3439 {
3440 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "JScriptCall", "Directory"));
3441 }
3442 }
3443 else if (MsiInterop.MsidbCustomActionTypeExe == targetBits) // exe-command
3444 {
3445 if (null == source)
3446 {
3447 this.core.OnMessage(WixErrors.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "ExeCommand", "BinaryKey", "Directory", "FileKey", "Property"));
3448 }
3449 }
3450 else if (MsiInterop.MsidbCustomActionTypeTextData == (bits | sourceBits | targetBits))
3451 {
3452 this.core.OnMessage(WixErrors.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "Value", "Directory", "Property"));
3453 }
3454 else if (!String.IsNullOrEmpty(innerText)) // inner text cannot be specified with non-script CAs
3455 {
3456 this.core.OnMessage(WixErrors.CustomActionIllegalInnerText(sourceLineNumbers, node.Name.LocalName, innerText, "Script"));
3457 }
3458
3459 if (MsiInterop.MsidbCustomActionType64BitScript == (bits & MsiInterop.MsidbCustomActionType64BitScript) && MsiInterop.MsidbCustomActionTypeVBScript != targetBits && MsiInterop.MsidbCustomActionTypeJScript != targetBits)
3460 {
3461 this.core.OnMessage(WixErrors.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "Win64", "Script", "VBScriptCall", "JScriptCall"));
3462 }
3463
3464 if ((MsiInterop.MsidbCustomActionTypeAsync | MsiInterop.MsidbCustomActionTypeContinue) == (bits & (MsiInterop.MsidbCustomActionTypeAsync | MsiInterop.MsidbCustomActionTypeContinue)) && MsiInterop.MsidbCustomActionTypeExe != targetBits)
3465 {
3466 this.core.OnMessage(WixErrors.IllegalAttributeValueWithoutOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Return", "asyncNoWait", "ExeCommand"));
3467 }
3468
3469 if (MsiInterop.MsidbCustomActionTypeTSAware == (bits & MsiInterop.MsidbCustomActionTypeTSAware))
3470 {
3471 // TS-aware CAs are valid only when deferred so require the in-script Type bit...
3472 if (0 == (bits & MsiInterop.MsidbCustomActionTypeInScript))
3473 {
3474 this.core.OnMessage(WixErrors.IllegalTerminalServerCustomActionAttributes(sourceLineNumbers));
3475 }
3476 }
3477
3478 // MSI doesn't support in-script property setting, so disallow it
3479 if (MsiInterop.MsidbCustomActionTypeProperty == sourceBits &&
3480 MsiInterop.MsidbCustomActionTypeTextData == targetBits &&
3481 0 != (bits & MsiInterop.MsidbCustomActionTypeInScript))
3482 {
3483 this.core.OnMessage(WixErrors.IllegalPropertyCustomActionAttributes(sourceLineNumbers));
3484 }
3485
3486 if (0 == targetBits)
3487 {
3488 this.core.OnMessage(WixErrors.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "DllEntry", "Error", "ExeCommand", "JScriptCall", "Script", "Value", "VBScriptCall"));
3489 }
3490
3491 this.core.ParseForExtensionElements(node);
3492
3493 if (!this.core.EncounteredError)
3494 {
3495 Row row = this.core.CreateRow(sourceLineNumbers, "CustomAction", id);
3496 row[1] = bits | sourceBits | targetBits;
3497 row[2] = source;
3498 row[3] = target;
3499 if (0 != extendedBits)
3500 {
3501 row[4] = extendedBits;
3502 }
3503
3504 if (YesNoType.Yes == suppressModularization)
3505 {
3506 this.core.CreateRow(sourceLineNumbers, "WixSuppressModularization", id);
3507 }
3508
3509 // For deferred CAs that specify HideTarget we should also hide the CA data property for the action.
3510 if (MsiInterop.MsidbCustomActionTypeHideTarget == (bits & MsiInterop.MsidbCustomActionTypeHideTarget) &&
3511 MsiInterop.MsidbCustomActionTypeInScript == (bits & MsiInterop.MsidbCustomActionTypeInScript))
3512 {
3513 this.AddWixPropertyRow(sourceLineNumbers, id, false, false, true);
3514 }
3515 }
3516 }
3517
3518 /// <summary>
3519 /// Parses a simple reference element.
3520 /// </summary>
3521 /// <param name="node">Element to parse.</param>
3522 /// <param name="table">Table which contains the target of the simple reference.</param>
3523 /// <returns>Id of the referenced element.</returns>
3524 private string ParseSimpleRefElement(XElement node, string table)
3525 {
3526 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
3527 string id = null;
3528
3529 foreach (XAttribute attrib in node.Attributes())
3530 {
3531 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
3532 {
3533 switch (attrib.Name.LocalName)
3534 {
3535 case "Id":
3536 id = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3537 this.core.CreateSimpleReference(sourceLineNumbers, table, id);
3538 break;
3539 default:
3540 this.core.UnexpectedAttribute(node, attrib);
3541 break;
3542 }
3543 }
3544 else
3545 {
3546 this.core.ParseExtensionAttribute(node, attrib);
3547 }
3548 }
3549
3550 if (null == id)
3551 {
3552 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
3553 }
3554
3555 this.core.ParseForExtensionElements(node);
3556
3557 return id;
3558 }
3559
3560 /// <summary>
3561 /// Parses a PatchFamilyRef element.
3562 /// </summary>
3563 /// <param name="node">Element to parse.</param>
3564 /// <param name="parentType">The parent type.</param>
3565 /// <param name="parentId">The ID of the parent.</param>
3566 /// <returns>Id of the referenced element.</returns>
3567 private void ParsePatchFamilyRefElement(XElement node, ComplexReferenceParentType parentType, string parentId)
3568 {
3569 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
3570 string[] primaryKeys = new string[2];
3571
3572 foreach (XAttribute attrib in node.Attributes())
3573 {
3574 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
3575 {
3576 switch (attrib.Name.LocalName)
3577 {
3578 case "Id":
3579 primaryKeys[0] = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3580 break;
3581 case "ProductCode":
3582 primaryKeys[1] = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3583 break;
3584 default:
3585 this.core.UnexpectedAttribute(node, attrib);
3586 break;
3587 }
3588 }
3589 else
3590 {
3591 this.core.ParseExtensionAttribute(node, attrib);
3592 }
3593 }
3594
3595 if (null == primaryKeys[0])
3596 {
3597 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
3598 }
3599
3600 this.core.CreateSimpleReference(sourceLineNumbers, "MsiPatchSequence", primaryKeys);
3601
3602 this.core.ParseForExtensionElements(node);
3603
3604 if (!this.core.EncounteredError)
3605 {
3606 this.core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.PatchFamily, primaryKeys[0], true);
3607 }
3608 }
3609
3610 /// <summary>
3611 /// Parses a PatchFamilyGroup element.
3612 /// </summary>
3613 /// <param name="node">Element to parse.</param>
3614 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
3615 private void ParsePatchFamilyGroupElement(XElement node, ComplexReferenceParentType parentType, string parentId)
3616 {
3617 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
3618 Identifier id = null;
3619
3620 foreach (XAttribute attrib in node.Attributes())
3621 {
3622 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
3623 {
3624 switch (attrib.Name.LocalName)
3625 {
3626 case "Id":
3627 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
3628 break;
3629 default:
3630 this.core.UnexpectedAttribute(node, attrib);
3631 break;
3632 }
3633 }
3634 else
3635 {
3636 this.core.ParseExtensionAttribute(node, attrib);
3637 }
3638 }
3639
3640 if (null == id)
3641 {
3642 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
3643 id = Identifier.Invalid;
3644 }
3645
3646 foreach (XElement child in node.Elements())
3647 {
3648 if (CompilerCore.WixNamespace == child.Name.Namespace)
3649 {
3650 switch (child.Name.LocalName)
3651 {
3652 case "PatchFamily":
3653 this.ParsePatchFamilyElement(child, ComplexReferenceParentType.PatchFamilyGroup, id.Id);
3654 break;
3655 case "PatchFamilyRef":
3656 this.ParsePatchFamilyRefElement(child, ComplexReferenceParentType.PatchFamilyGroup, id.Id);
3657 break;
3658 case "PatchFamilyGroupRef":
3659 this.ParsePatchFamilyGroupRefElement(child, ComplexReferenceParentType.PatchFamilyGroup, id.Id);
3660 break;
3661 default:
3662 this.core.UnexpectedElement(node, child);
3663 break;
3664 }
3665 }
3666 else
3667 {
3668 this.core.ParseExtensionElement(node, child);
3669 }
3670 }
3671
3672 if (!this.core.EncounteredError)
3673 {
3674 Row row = this.core.CreateRow(sourceLineNumbers, "WixPatchFamilyGroup", id);
3675
3676 //Add this PatchFamilyGroup and its parent in WixGroup.
3677 this.core.CreateWixGroupRow(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.PatchFamilyGroup, id.Id);
3678 }
3679 }
3680
3681 /// <summary>
3682 /// Parses a PatchFamilyGroup reference element.
3683 /// </summary>
3684 /// <param name="node">Element to parse.</param>
3685 /// <param name="parentType">The type of parent.</param>
3686 /// <param name="parentId">Identifier of parent element.</param>
3687 private void ParsePatchFamilyGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId)
3688 {
3689 Debug.Assert(ComplexReferenceParentType.PatchFamilyGroup == parentType || ComplexReferenceParentType.Patch == parentType);
3690
3691 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
3692 string id = null;
3693
3694 foreach (XAttribute attrib in node.Attributes())
3695 {
3696 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
3697 {
3698 switch (attrib.Name.LocalName)
3699 {
3700 case "Id":
3701 id = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3702 this.core.CreateSimpleReference(sourceLineNumbers, "WixPatchFamilyGroup", id);
3703 break;
3704 default:
3705 this.core.UnexpectedAttribute(node, attrib);
3706 break;
3707 }
3708 }
3709 else
3710 {
3711 this.core.ParseExtensionAttribute(node, attrib);
3712 }
3713 }
3714
3715 if (null == id)
3716 {
3717 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
3718 }
3719
3720 this.core.ParseForExtensionElements(node);
3721
3722 if (!this.core.EncounteredError)
3723 {
3724 this.core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.PatchFamilyGroup, id, true);
3725 }
3726 }
3727
3728 /// <summary>
3729 /// Parses an ensure table element.
3730 /// </summary>
3731 /// <param name="node">Element to parse.</param>
3732 private void ParseEnsureTableElement(XElement node)
3733 {
3734 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
3735 string id = null;
3736
3737 foreach (XAttribute attrib in node.Attributes())
3738 {
3739 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
3740 {
3741 switch (attrib.Name.LocalName)
3742 {
3743 case "Id":
3744 id = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3745 break;
3746 default:
3747 this.core.UnexpectedAttribute(node, attrib);
3748 break;
3749 }
3750 }
3751 else
3752 {
3753 this.core.ParseExtensionAttribute(node, attrib);
3754 }
3755 }
3756
3757 if (null == id)
3758 {
3759 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
3760 }
3761 else if (31 < id.Length)
3762 {
3763 this.core.OnMessage(WixErrors.TableNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", id));
3764 }
3765
3766 this.core.ParseForExtensionElements(node);
3767
3768 this.core.EnsureTable(sourceLineNumbers, id);
3769 }
3770
3771 /// <summary>
3772 /// Parses a custom table element.
3773 /// </summary>
3774 /// <param name="node">Element to parse.</param>
3775 /// <remarks>not cleaned</remarks>
3776 [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "Changing the way this string normalizes would result " +
3777 "in a change to the way the WixCustomTable table is generated. Furthermore, there is no security hole here, as the strings won't need to " +
3778 "make a round trip")]
3779 private void ParseCustomTableElement(XElement node)
3780 {
3781 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
3782 string tableId = null;
3783
3784 string categories = null;
3785 int columnCount = 0;
3786 string columnNames = null;
3787 string columnTypes = null;
3788 string descriptions = null;
3789 string keyColumns = null;
3790 string keyTables = null;
3791 string maxValues = null;
3792 string minValues = null;
3793 string modularizations = null;
3794 string primaryKeys = null;
3795 string sets = null;
3796 bool bootstrapperApplicationData = false;
3797
3798 foreach (XAttribute attrib in node.Attributes())
3799 {
3800 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
3801 {
3802 switch (attrib.Name.LocalName)
3803 {
3804 case "Id":
3805 tableId = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
3806 break;
3807 case "BootstrapperApplicationData":
3808 bootstrapperApplicationData = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
3809 break;
3810 default:
3811 this.core.UnexpectedAttribute(node, attrib);
3812 break;
3813 }
3814 }
3815 else
3816 {
3817 this.core.ParseExtensionAttribute(node, attrib);
3818 }
3819 }
3820
3821 if (null == tableId)
3822 {
3823 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
3824 }
3825 else if (31 < tableId.Length)
3826 {
3827 this.core.OnMessage(WixErrors.CustomTableNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", tableId));
3828 }
3829
3830 foreach (XElement child in node.Elements())
3831 {
3832 if (CompilerCore.WixNamespace == child.Name.Namespace)
3833 {
3834 SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
3835 switch (child.Name.LocalName)
3836 {
3837 case "Column":
3838 ++columnCount;
3839
3840 string category = String.Empty;
3841 string columnName = null;
3842 string columnType = null;
3843 string description = String.Empty;
3844 int keyColumn = CompilerConstants.IntegerNotSet;
3845 string keyTable = String.Empty;
3846 bool localizable = false;
3847 long maxValue = CompilerConstants.LongNotSet;
3848 long minValue = CompilerConstants.LongNotSet;
3849 string modularization = "None";
3850 bool nullable = false;
3851 bool primaryKey = false;
3852 string setValues = String.Empty;
3853 string typeName = null;
3854 int width = 0;
3855
3856 foreach (XAttribute childAttrib in child.Attributes())
3857 {
3858 switch (childAttrib.Name.LocalName)
3859 {
3860 case "Id":
3861 columnName = this.core.GetAttributeIdentifierValue(childSourceLineNumbers, childAttrib);
3862 break;
3863 case "Category":
3864 category = this.core.GetAttributeValue(childSourceLineNumbers, childAttrib);
3865 break;
3866 case "Description":
3867 description = this.core.GetAttributeValue(childSourceLineNumbers, childAttrib);
3868 break;
3869 case "KeyColumn":
3870 keyColumn = this.core.GetAttributeIntegerValue(childSourceLineNumbers, childAttrib, 1, 32);
3871 break;
3872 case "KeyTable":
3873 keyTable = this.core.GetAttributeValue(childSourceLineNumbers, childAttrib);
3874 break;
3875 case "Localizable":
3876 localizable = YesNoType.Yes == this.core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib);
3877 break;
3878 case "MaxValue":
3879 maxValue = this.core.GetAttributeLongValue(childSourceLineNumbers, childAttrib, int.MinValue + 1, int.MaxValue);
3880 break;
3881 case "MinValue":
3882 minValue = this.core.GetAttributeLongValue(childSourceLineNumbers, childAttrib, int.MinValue + 1, int.MaxValue);
3883 break;
3884 case "Modularize":
3885 modularization = this.core.GetAttributeValue(childSourceLineNumbers, childAttrib);
3886 break;
3887 case "Nullable":
3888 nullable = YesNoType.Yes == this.core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib);
3889 break;
3890 case "PrimaryKey":
3891 primaryKey = YesNoType.Yes == this.core.GetAttributeYesNoValue(childSourceLineNumbers, childAttrib);
3892 break;
3893 case "Set":
3894 setValues = this.core.GetAttributeValue(childSourceLineNumbers, childAttrib);
3895 break;
3896 case "Type":
3897 string typeValue = this.core.GetAttributeValue(childSourceLineNumbers, childAttrib);
3898 if (0 < typeValue.Length)
3899 {
3900 Wix.Column.TypeType typeType = Wix.Column.ParseTypeType(typeValue);
3901 switch (typeType)
3902 {
3903 case Wix.Column.TypeType.binary:
3904 typeName = "OBJECT";
3905 break;
3906 case Wix.Column.TypeType.@int:
3907 typeName = "SHORT";
3908 break;
3909 case Wix.Column.TypeType.@string:
3910 typeName = "CHAR";
3911 break;
3912 default:
3913 this.core.OnMessage(WixErrors.IllegalAttributeValue(childSourceLineNumbers, child.Name.LocalName, "Type", typeValue, "binary", "int", "string"));
3914 break;
3915 }
3916 }
3917 break;
3918 case "Width":
3919 width = this.core.GetAttributeIntegerValue(childSourceLineNumbers, childAttrib, 0, int.MaxValue);
3920 break;
3921 default:
3922 this.core.UnexpectedAttribute(child, childAttrib);
3923 break;
3924 }
3925 }
3926
3927 if (null == columnName)
3928 {
3929 this.core.OnMessage(WixErrors.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Id"));
3930 }
3931
3932 if (null == typeName)
3933 {
3934 this.core.OnMessage(WixErrors.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Type"));
3935 }
3936 else if ("SHORT" == typeName)
3937 {
3938 if (2 != width && 4 != width)
3939 {
3940 this.core.OnMessage(WixErrors.CustomTableIllegalColumnWidth(childSourceLineNumbers, child.Name.LocalName, "Width", width));
3941 }
3942 columnType = String.Concat(nullable ? "I" : "i", width);
3943 }
3944 else if ("CHAR" == typeName)
3945 {
3946 string typeChar = localizable ? "l" : "s";
3947 columnType = String.Concat(nullable ? typeChar.ToUpper(CultureInfo.InvariantCulture) : typeChar.ToLower(CultureInfo.InvariantCulture), width);
3948 }
3949 else if ("OBJECT" == typeName)
3950 {
3951 if ("Binary" != category)
3952 {
3953 this.core.OnMessage(WixErrors.ExpectedBinaryCategory(childSourceLineNumbers));
3954 }
3955 columnType = String.Concat(nullable ? "V" : "v", width);
3956 }
3957
3958 this.core.ParseForExtensionElements(child);
3959
3960 columnNames = String.Concat(columnNames, null == columnNames ? String.Empty : "\t", columnName);
3961 columnTypes = String.Concat(columnTypes, null == columnTypes ? String.Empty : "\t", columnType);
3962 if (primaryKey)
3963 {
3964 primaryKeys = String.Concat(primaryKeys, null == primaryKeys ? String.Empty : "\t", columnName);
3965 }
3966
3967 minValues = String.Concat(minValues, null == minValues ? String.Empty : "\t", CompilerConstants.LongNotSet != minValue ? minValue.ToString(CultureInfo.InvariantCulture) : String.Empty);
3968 maxValues = String.Concat(maxValues, null == maxValues ? String.Empty : "\t", CompilerConstants.LongNotSet != maxValue ? maxValue.ToString(CultureInfo.InvariantCulture) : String.Empty);
3969 keyTables = String.Concat(keyTables, null == keyTables ? String.Empty : "\t", keyTable);
3970 keyColumns = String.Concat(keyColumns, null == keyColumns ? String.Empty : "\t", CompilerConstants.IntegerNotSet != keyColumn ? keyColumn.ToString(CultureInfo.InvariantCulture) : String.Empty);
3971 categories = String.Concat(categories, null == categories ? String.Empty : "\t", category);
3972 sets = String.Concat(sets, null == sets ? String.Empty : "\t", setValues);
3973 descriptions = String.Concat(descriptions, null == descriptions ? String.Empty : "\t", description);
3974 modularizations = String.Concat(modularizations, null == modularizations ? String.Empty : "\t", modularization);
3975
3976 break;
3977 case "Row":
3978 string dataValue = null;
3979
3980 foreach (XAttribute childAttrib in child.Attributes())
3981 {
3982 this.core.ParseExtensionAttribute(child, childAttrib);
3983 }
3984
3985 foreach (XElement data in child.Elements())
3986 {
3987 SourceLineNumber dataSourceLineNumbers = Preprocessor.GetSourceLineNumbers(data);
3988 switch (data.Name.LocalName)
3989 {
3990 case "Data":
3991 columnName = null;
3992 foreach (XAttribute dataAttrib in data.Attributes())
3993 {
3994 switch (dataAttrib.Name.LocalName)
3995 {
3996 case "Column":
3997 columnName = this.core.GetAttributeValue(dataSourceLineNumbers, dataAttrib);
3998 break;
3999 default:
4000 this.core.UnexpectedAttribute(data, dataAttrib);
4001 break;
4002 }
4003 }
4004
4005 if (null == columnName)
4006 {
4007 this.core.OnMessage(WixErrors.ExpectedAttribute(dataSourceLineNumbers, data.Name.LocalName, "Column"));
4008 }
4009
4010 dataValue = String.Concat(dataValue, null == dataValue ? String.Empty : Common.CustomRowFieldSeparator.ToString(), columnName, ":", Common.GetInnerText(data));
4011 break;
4012 }
4013 }
4014
4015 this.core.CreateSimpleReference(sourceLineNumbers, "WixCustomTable", tableId);
4016
4017 if (!this.core.EncounteredError)
4018 {
4019 Row rowRow = this.core.CreateRow(childSourceLineNumbers, "WixCustomRow");
4020 rowRow[0] = tableId;
4021 rowRow[1] = dataValue;
4022 }
4023 break;
4024 default:
4025 this.core.UnexpectedElement(node, child);
4026 break;
4027 }
4028 }
4029 else
4030 {
4031 this.core.ParseExtensionElement(node, child);
4032 }
4033 }
4034
4035 if (0 < columnCount)
4036 {
4037 if (null == primaryKeys || 0 == primaryKeys.Length)
4038 {
4039 this.core.OnMessage(WixErrors.CustomTableMissingPrimaryKey(sourceLineNumbers));
4040 }
4041
4042 if (!this.core.EncounteredError)
4043 {
4044 Row row = this.core.CreateRow(sourceLineNumbers, "WixCustomTable");
4045 row[0] = tableId;
4046 row[1] = columnCount;
4047 row[2] = columnNames;
4048 row[3] = columnTypes;
4049 row[4] = primaryKeys;
4050 row[5] = minValues;
4051 row[6] = maxValues;
4052 row[7] = keyTables;
4053 row[8] = keyColumns;
4054 row[9] = categories;
4055 row[10] = sets;
4056 row[11] = descriptions;
4057 row[12] = modularizations;
4058 row[13] = bootstrapperApplicationData ? 1 : 0;
4059 }
4060 }
4061 }
4062
4063 /// <summary>
4064 /// Parses a directory element.
4065 /// </summary>
4066 /// <param name="node">Element to parse.</param>
4067 /// <param name="parentId">Optional identifier of parent directory.</param>
4068 /// <param name="diskId">Disk id inherited from parent directory.</param>
4069 /// <param name="fileSource">Path to source file as of yet.</param>
4070 [SuppressMessage("Microsoft.Performance", "CA1820:TestForEmptyStringsUsingStringLength")]
4071 private void ParseDirectoryElement(XElement node, string parentId, int diskId, string fileSource)
4072 {
4073 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
4074 Identifier id = null;
4075 string componentGuidGenerationSeed = null;
4076 bool fileSourceAttribSet = false;
4077 bool nameHasValue = false;
4078 string name = "."; // default to parent directory.
4079 string[] inlineSyntax = null;
4080 string shortName = null;
4081 string sourceName = null;
4082 string shortSourceName = null;
4083 string defaultDir = null;
4084 string symbols = null;
4085
4086 foreach (XAttribute attrib in node.Attributes())
4087 {
4088 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
4089 {
4090 switch (attrib.Name.LocalName)
4091 {
4092 case "Id":
4093 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
4094 break;
4095 case "ComponentGuidGenerationSeed":
4096 componentGuidGenerationSeed = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
4097 break;
4098 case "DiskId":
4099 diskId = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, short.MaxValue);
4100 break;
4101 case "FileSource":
4102 fileSource = this.core.GetAttributeValue(sourceLineNumbers, attrib);
4103 fileSourceAttribSet = true;
4104 break;
4105 case "Name":
4106 nameHasValue = true;
4107 if (attrib.Value.Equals("."))
4108 {
4109 name = attrib.Value;
4110 }
4111 else
4112 {
4113 inlineSyntax = this.core.GetAttributeInlineDirectorySyntax(sourceLineNumbers, attrib);
4114 }
4115 break;
4116 case "ShortName":
4117 shortName = this.core.GetAttributeShortFilename(sourceLineNumbers, attrib, false);
4118 break;
4119 case "ShortSourceName":
4120 shortSourceName = this.core.GetAttributeShortFilename(sourceLineNumbers, attrib, false);
4121 break;
4122 case "SourceName":
4123 if ("." == attrib.Value)
4124 {
4125 sourceName = attrib.Value;
4126 }
4127 else
4128 {
4129 sourceName = this.core.GetAttributeLongFilename(sourceLineNumbers, attrib, false);
4130 }
4131 break;
4132 default:
4133 this.core.UnexpectedAttribute(node, attrib);
4134 break;
4135 }
4136 }
4137 else
4138 {
4139 this.core.ParseExtensionAttribute(node, attrib);
4140 }
4141 }
4142
4143 // Create the directory rows for the inline.
4144 if (null != inlineSyntax)
4145 {
4146 // Special case the single entry in the inline syntax since it is the most common case
4147 // and needs no extra processing. It's just the name of the directory.
4148 if (1 == inlineSyntax.Length)
4149 {
4150 name = inlineSyntax[0];
4151 }
4152 else
4153 {
4154 int pathStartsAt = 0;
4155 if (inlineSyntax[0].EndsWith(":"))
4156 {
4157 parentId = inlineSyntax[0].TrimEnd(':');
4158 this.core.CreateSimpleReference(sourceLineNumbers, "Directory", parentId);
4159
4160 pathStartsAt = 1;
4161 }
4162
4163 for (int i = pathStartsAt; i < inlineSyntax.Length - 1; ++i)
4164 {
4165 Identifier inlineId = this.core.CreateDirectoryRow(sourceLineNumbers, null, parentId, inlineSyntax[i]);
4166 parentId = inlineId.Id;
4167 }
4168
4169 name = inlineSyntax[inlineSyntax.Length - 1];
4170 }
4171 }
4172
4173 if (!nameHasValue)
4174 {
4175 if (!String.IsNullOrEmpty(shortName))
4176 {
4177 this.core.OnMessage(WixErrors.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "ShortName", "Name"));
4178 }
4179
4180 if (null == parentId)
4181 {
4182 this.core.OnMessage(WixErrors.DirectoryRootWithoutName(sourceLineNumbers, node.Name.LocalName, "Name"));
4183 }
4184 }
4185 else if (!String.IsNullOrEmpty(name))
4186 {
4187 if (String.IsNullOrEmpty(shortName))
4188 {
4189 if (!name.Equals(".") && !name.Equals("SourceDir") && !this.core.IsValidShortFilename(name, false))
4190 {
4191 shortName = this.core.CreateShortName(name, false, false, "Directory", parentId);
4192 }
4193 }
4194 else if (name.Equals("."))
4195 {
4196 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "ShortName", "Name", name));
4197 }
4198 else if (name.Equals(shortName))
4199 {
4200 this.core.OnMessage(WixWarnings.DirectoryRedundantNames(sourceLineNumbers, node.Name.LocalName, "Name", "ShortName", name));
4201 }
4202 }
4203
4204 if (String.IsNullOrEmpty(sourceName))
4205 {
4206 if (!String.IsNullOrEmpty(shortSourceName))
4207 {
4208 this.core.OnMessage(WixErrors.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "ShortSourceName", "SourceName"));
4209 }
4210 }
4211 else
4212 {
4213 if (String.IsNullOrEmpty(shortSourceName))
4214 {
4215 if (!sourceName.Equals(".") && !this.core.IsValidShortFilename(sourceName, false))
4216 {
4217 shortSourceName = this.core.CreateShortName(sourceName, false, false, "Directory", parentId);
4218 }
4219 }
4220 else if (sourceName.Equals("."))
4221 {
4222 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "ShortSourceName", "SourceName", sourceName));
4223 }
4224 else if (sourceName.Equals(shortSourceName))
4225 {
4226 this.core.OnMessage(WixWarnings.DirectoryRedundantNames(sourceLineNumbers, node.Name.LocalName, "SourceName", "ShortSourceName", sourceName));
4227 }
4228 }
4229
4230 // Update the file source path appropriately.
4231 if (fileSourceAttribSet)
4232 {
4233 if (!fileSource.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal))
4234 {
4235 fileSource = String.Concat(fileSource, Path.DirectorySeparatorChar);
4236 }
4237 }
4238 else // add the appropriate part of this directory element to the file source.
4239 {
4240 string append = null;
4241 if (this.useShortFileNames)
4242 {
4243 append = !String.IsNullOrEmpty(shortSourceName) ? shortSourceName : shortName;
4244 }
4245
4246 if (String.IsNullOrEmpty(append))
4247 {
4248 append = !String.IsNullOrEmpty(sourceName) ? sourceName : name;
4249 }
4250
4251 if (!String.IsNullOrEmpty(append))
4252 {
4253 fileSource = String.Concat(fileSource, append, Path.DirectorySeparatorChar);
4254 }
4255 }
4256
4257 if (null == id)
4258 {
4259 id = this.core.CreateIdentifier("dir", parentId, name, shortName, sourceName, shortSourceName);
4260 }
4261
4262 // Calculate the DefaultDir for the directory row.
4263 defaultDir = String.IsNullOrEmpty(shortName) ? name : String.Concat(shortName, "|", name);
4264 if (!String.IsNullOrEmpty(sourceName))
4265 {
4266 defaultDir = String.Concat(defaultDir, ":", String.IsNullOrEmpty(shortSourceName) ? sourceName : String.Concat(shortSourceName, "|", sourceName));
4267 }
4268
4269 if ("TARGETDIR".Equals(id.Id) && !"SourceDir".Equals(defaultDir))
4270 {
4271 this.core.OnMessage(WixErrors.IllegalTargetDirDefaultDir(sourceLineNumbers, defaultDir));
4272 }
4273
4274 foreach (XElement child in node.Elements())
4275 {
4276 if (CompilerCore.WixNamespace == child.Name.Namespace)
4277 {
4278 switch (child.Name.LocalName)
4279 {
4280 case "Component":
4281 this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, diskId, id.Id, fileSource);
4282 break;
4283 case "Directory":
4284 this.ParseDirectoryElement(child, id.Id, diskId, fileSource);
4285 break;
4286 case "Merge":
4287 this.ParseMergeElement(child, id.Id, diskId);
4288 break;
4289 case "SymbolPath":
4290 if (null != symbols)
4291 {
4292 symbols += ";" + this.ParseSymbolPathElement(child);
4293 }
4294 else
4295 {
4296 symbols = this.ParseSymbolPathElement(child);
4297 }
4298 break;
4299 default:
4300 this.core.UnexpectedElement(node, child);
4301 break;
4302 }
4303 }
4304 else
4305 {
4306 this.core.ParseExtensionElement(node, child);
4307 }
4308 }
4309
4310 if (!this.core.EncounteredError)
4311 {
4312 Row row = this.core.CreateRow(sourceLineNumbers, "Directory", id);
4313 row[1] = parentId;
4314 row[2] = defaultDir;
4315
4316 if (null != componentGuidGenerationSeed)
4317 {
4318 Row wixRow = this.core.CreateRow(sourceLineNumbers, "WixDirectory");
4319 wixRow[0] = id.Id;
4320 wixRow[1] = componentGuidGenerationSeed;
4321 }
4322
4323 if (null != symbols)
4324 {
4325 WixDeltaPatchSymbolPathsRow symbolRow = (WixDeltaPatchSymbolPathsRow)this.core.CreateRow(sourceLineNumbers, "WixDeltaPatchSymbolPaths", id);
4326 symbolRow.Type = SymbolPathType.Directory;
4327 symbolRow.SymbolPaths = symbols;
4328 }
4329 }
4330 }
4331
4332 /// <summary>
4333 /// Parses a directory reference element.
4334 /// </summary>
4335 /// <param name="node">Element to parse.</param>
4336 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
4337 private void ParseDirectoryRefElement(XElement node)
4338 {
4339 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
4340 string id = null;
4341 int diskId = CompilerConstants.IntegerNotSet;
4342 string fileSource = String.Empty;
4343
4344 foreach (XAttribute attrib in node.Attributes())
4345 {
4346 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
4347 {
4348 switch (attrib.Name.LocalName)
4349 {
4350 case "Id":
4351 id = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
4352 this.core.CreateSimpleReference(sourceLineNumbers, "Directory", id);
4353 break;
4354 case "DiskId":
4355 diskId = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, short.MaxValue);
4356 break;
4357 case "FileSource":
4358 fileSource = this.core.GetAttributeValue(sourceLineNumbers, attrib);
4359 break;
4360 default:
4361 this.core.UnexpectedAttribute(node, attrib);
4362 break;
4363 }
4364 }
4365 else
4366 {
4367 this.core.ParseExtensionAttribute(node, attrib);
4368 }
4369 }
4370
4371 if (null == id)
4372 {
4373 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
4374 }
4375
4376 if (!String.IsNullOrEmpty(fileSource) && !fileSource.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal))
4377 {
4378 fileSource = String.Concat(fileSource, Path.DirectorySeparatorChar);
4379 }
4380
4381 foreach (XElement child in node.Elements())
4382 {
4383 if (CompilerCore.WixNamespace == child.Name.Namespace)
4384 {
4385 switch (child.Name.LocalName)
4386 {
4387 case "Component":
4388 this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, diskId, id, fileSource);
4389 break;
4390 case "Directory":
4391 this.ParseDirectoryElement(child, id, diskId, fileSource);
4392 break;
4393 case "Merge":
4394 this.ParseMergeElement(child, id, diskId);
4395 break;
4396 default:
4397 this.core.UnexpectedElement(node, child);
4398 break;
4399 }
4400 }
4401 else
4402 {
4403 this.core.ParseExtensionElement(node, child);
4404 }
4405 }
4406 }
4407
4408 /// <summary>
4409 /// Parses a directory search element.
4410 /// </summary>
4411 /// <param name="node">Element to parse.</param>
4412 /// <param name="parentSignature">Signature of parent search element.</param>
4413 /// <returns>Signature of search element.</returns>
4414 private string ParseDirectorySearchElement(XElement node, string parentSignature)
4415 {
4416 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
4417 Identifier id = null;
4418 int depth = CompilerConstants.IntegerNotSet;
4419 string path = null;
4420 bool assignToProperty = false;
4421 string signature = null;
4422
4423 foreach (XAttribute attrib in node.Attributes())
4424 {
4425 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
4426 {
4427 switch (attrib.Name.LocalName)
4428 {
4429 case "Id":
4430 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
4431 break;
4432 case "Depth":
4433 depth = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue);
4434 break;
4435 case "Path":
4436 path = this.core.GetAttributeValue(sourceLineNumbers, attrib);
4437 break;
4438 case "AssignToProperty":
4439 assignToProperty = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
4440 break;
4441 default:
4442 this.core.UnexpectedAttribute(node, attrib);
4443 break;
4444 }
4445 }
4446 else
4447 {
4448 this.core.ParseExtensionAttribute(node, attrib);
4449 }
4450 }
4451
4452 if (null == id)
4453 {
4454 id = this.core.CreateIdentifier("dir", path, depth.ToString());
4455 }
4456
4457 signature = id.Id;
4458
4459 bool oneChild = false;
4460 bool hasFileSearch = false;
4461 foreach (XElement child in node.Elements())
4462 {
4463 if (CompilerCore.WixNamespace == child.Name.Namespace)
4464 {
4465 SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
4466 switch (child.Name.LocalName)
4467 {
4468 case "DirectorySearch":
4469 if (oneChild)
4470 {
4471 this.core.OnMessage(WixErrors.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName));
4472 }
4473 oneChild = true;
4474 signature = this.ParseDirectorySearchElement(child, id.Id);
4475 break;
4476 case "DirectorySearchRef":
4477 if (oneChild)
4478 {
4479 this.core.OnMessage(WixErrors.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName));
4480 }
4481 oneChild = true;
4482 signature = this.ParseDirectorySearchRefElement(child, id.Id);
4483 break;
4484 case "FileSearch":
4485 if (oneChild)
4486 {
4487 this.core.OnMessage(WixErrors.TooManySearchElements(sourceLineNumbers, node.Name.LocalName));
4488 }
4489 oneChild = true;
4490 hasFileSearch = true;
4491 signature = this.ParseFileSearchElement(child, id.Id, assignToProperty, depth);
4492 break;
4493 case "FileSearchRef":
4494 if (oneChild)
4495 {
4496 this.core.OnMessage(WixErrors.TooManySearchElements(sourceLineNumbers, node.Name.LocalName));
4497 }
4498 oneChild = true;
4499 signature = this.ParseSimpleRefElement(child, "Signature");
4500 break;
4501 default:
4502 this.core.UnexpectedElement(node, child);
4503 break;
4504 }
4505
4506 // If AssignToProperty is set, only a FileSearch
4507 // or no child element can be nested.
4508 if (assignToProperty)
4509 {
4510 if (!hasFileSearch)
4511 {
4512 this.core.OnMessage(WixErrors.IllegalParentAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "AssignToProperty", child.Name.LocalName));
4513 }
4514 else if (!oneChild)
4515 {
4516 // This a normal directory search.
4517 assignToProperty = false;
4518 }
4519 }
4520 }
4521 else
4522 {
4523 this.core.ParseExtensionElement(node, child);
4524 }
4525 }
4526
4527 if (!this.core.EncounteredError)
4528 {
4529 Identifier rowId = id;
4530
4531 // If AssignToProperty is set, the DrLocator row created by
4532 // ParseFileSearchElement creates the directory entry to return
4533 // and the row created here is for the file search.
4534 if (assignToProperty)
4535 {
4536 rowId = new Identifier(signature, AccessModifier.Private);
4537
4538 // The property should be set to the directory search Id.
4539 signature = id.Id;
4540 }
4541
4542 Row row = this.core.CreateRow(sourceLineNumbers, "DrLocator", rowId);
4543 row[1] = parentSignature;
4544 row[2] = path;
4545 if (CompilerConstants.IntegerNotSet != depth)
4546 {
4547 row[3] = depth;
4548 }
4549 }
4550
4551 return signature;
4552 }
4553
4554 /// <summary>
4555 /// Parses a directory search reference element.
4556 /// </summary>
4557 /// <param name="node">Element to parse.</param>
4558 /// <param name="parentSignature">Signature of parent search element.</param>
4559 /// <returns>Signature of search element.</returns>
4560 private string ParseDirectorySearchRefElement(XElement node, string parentSignature)
4561 {
4562 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
4563 Identifier id = null;
4564 Identifier parent = null;
4565 string path = null;
4566 string signature = null;
4567
4568 foreach (XAttribute attrib in node.Attributes())
4569 {
4570 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
4571 {
4572 switch (attrib.Name.LocalName)
4573 {
4574 case "Id":
4575 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
4576 break;
4577 case "Parent":
4578 parent = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
4579 break;
4580 case "Path":
4581 path = this.core.GetAttributeValue(sourceLineNumbers, attrib);
4582 break;
4583 default:
4584 this.core.UnexpectedAttribute(node, attrib);
4585 break;
4586 }
4587 }
4588 else
4589 {
4590 this.core.ParseExtensionAttribute(node, attrib);
4591 }
4592 }
4593
4594 if (null != parent)
4595 {
4596 if (!String.IsNullOrEmpty(parentSignature))
4597 {
4598 this.core.OnMessage(WixErrors.CanNotHaveTwoParents(sourceLineNumbers, id.Id, parent.Id, parentSignature));
4599 }
4600 else
4601 {
4602 parentSignature = parent.Id;
4603 }
4604 }
4605
4606 if (null == id)
4607 {
4608 id = this.core.CreateIdentifier("dsr", parentSignature, path);
4609 }
4610
4611 signature = id.Id;
4612
4613 bool oneChild = false;
4614 foreach (XElement child in node.Elements())
4615 {
4616 if (CompilerCore.WixNamespace == child.Name.Namespace)
4617 {
4618 SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
4619 switch (child.Name.LocalName)
4620 {
4621 case "DirectorySearch":
4622 if (oneChild)
4623 {
4624 this.core.OnMessage(WixErrors.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName));
4625 }
4626 oneChild = true;
4627 signature = this.ParseDirectorySearchElement(child, id.Id);
4628 break;
4629 case "DirectorySearchRef":
4630 if (oneChild)
4631 {
4632 this.core.OnMessage(WixErrors.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName));
4633 }
4634 oneChild = true;
4635 signature = this.ParseDirectorySearchRefElement(child, id.Id);
4636 break;
4637 case "FileSearch":
4638 if (oneChild)
4639 {
4640 this.core.OnMessage(WixErrors.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName));
4641 }
4642 oneChild = true;
4643 signature = this.ParseFileSearchElement(child, id.Id, false, CompilerConstants.IntegerNotSet);
4644 break;
4645 case "FileSearchRef":
4646 if (oneChild)
4647 {
4648 this.core.OnMessage(WixErrors.TooManySearchElements(sourceLineNumbers, node.Name.LocalName));
4649 }
4650 oneChild = true;
4651 signature = this.ParseSimpleRefElement(child, "Signature");
4652 break;
4653 default:
4654 this.core.UnexpectedElement(node, child);
4655 break;
4656 }
4657 }
4658 else
4659 {
4660 this.core.ParseExtensionElement(node, child);
4661 }
4662 }
4663
4664
4665 this.core.CreateSimpleReference(sourceLineNumbers, "DrLocator", id.Id, parentSignature, path);
4666
4667 return signature;
4668 }
4669
4670 /// <summary>
4671 /// Parses a feature element.
4672 /// </summary>
4673 /// <param name="node">Element to parse.</param>
4674 /// <param name="parentType">The type of parent.</param>
4675 /// <param name="parentId">Optional identifer for parent feature.</param>
4676 /// <param name="lastDisplay">Display value for last feature used to get the features to display in the same order as specified
4677 /// in the source code.</param>
4678 [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
4679 private void ParseFeatureElement(XElement node, ComplexReferenceParentType parentType, string parentId, ref int lastDisplay)
4680 {
4681 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
4682 Identifier id = null;
4683 string allowAdvertise = null;
4684 int bits = 0;
4685 string configurableDirectory = null;
4686 string description = null;
4687 string display = "collapse";
4688 YesNoType followParent = YesNoType.NotSet;
4689 string installDefault = null;
4690 int level = 1;
4691 string title = null;
4692 string typicalDefault = null;
4693
4694 foreach (XAttribute attrib in node.Attributes())
4695 {
4696 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
4697 {
4698 switch (attrib.Name.LocalName)
4699 {
4700 case "Id":
4701 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
4702 break;
4703 case "Absent":
4704 string absent = this.core.GetAttributeValue(sourceLineNumbers, attrib);
4705 if (0 < absent.Length)
4706 {
4707 Wix.Feature.AbsentType absentType = Wix.Feature.ParseAbsentType(absent);
4708 switch (absentType)
4709 {
4710 case Wix.Feature.AbsentType.allow: // this is the default
4711 break;
4712 case Wix.Feature.AbsentType.disallow:
4713 bits = bits | MsiInterop.MsidbFeatureAttributesUIDisallowAbsent;
4714 break;
4715 default:
4716 this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, absent, "allow", "disallow"));
4717 break;
4718 }
4719 }
4720 break;
4721 case "AllowAdvertise":
4722 allowAdvertise = this.core.GetAttributeValue(sourceLineNumbers, attrib);
4723 if (0 < allowAdvertise.Length)
4724 {
4725 Wix.Feature.AllowAdvertiseType allowAdvertiseType = Wix.Feature.ParseAllowAdvertiseType(allowAdvertise);
4726 switch (allowAdvertiseType)
4727 {
4728 case Wix.Feature.AllowAdvertiseType.no:
4729 bits |= MsiInterop.MsidbFeatureAttributesDisallowAdvertise;
4730 break;
4731 case Wix.Feature.AllowAdvertiseType.system:
4732 bits |= MsiInterop.MsidbFeatureAttributesNoUnsupportedAdvertise;
4733 break;
4734 case Wix.Feature.AllowAdvertiseType.yes: // this is the default
4735 break;
4736 default:
4737 this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, allowAdvertise, "no", "system", "yes"));
4738 break;
4739 }
4740 }
4741 break;
4742 case "ConfigurableDirectory":
4743 configurableDirectory = this.core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, null);
4744 break;
4745 case "Description":
4746 description = this.core.GetAttributeValue(sourceLineNumbers, attrib);
4747 break;
4748 case "Display":
4749 display = this.core.GetAttributeValue(sourceLineNumbers, attrib);
4750 break;
4751 case "InstallDefault":
4752 installDefault = this.core.GetAttributeValue(sourceLineNumbers, attrib);
4753 if (0 < installDefault.Length)
4754 {
4755 Wix.Feature.InstallDefaultType installDefaultType = Wix.Feature.ParseInstallDefaultType(installDefault);
4756 switch (installDefaultType)
4757 {
4758 case Wix.Feature.InstallDefaultType.followParent:
4759 if (ComplexReferenceParentType.Product == parentType)
4760 {
4761 this.core.OnMessage(WixErrors.RootFeatureCannotFollowParent(sourceLineNumbers));
4762 }
4763 bits = bits | MsiInterop.MsidbFeatureAttributesFollowParent;
4764 break;
4765 case Wix.Feature.InstallDefaultType.local: // this is the default
4766 break;
4767 case Wix.Feature.InstallDefaultType.source:
4768 bits = bits | MsiInterop.MsidbFeatureAttributesFavorSource;
4769 break;
4770 default:
4771 this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, installDefault, "followParent", "local", "source"));
4772 break;
4773 }
4774 }
4775 break;
4776 case "Level":
4777 level = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue);
4778 break;
4779 case "Title":
4780 title = this.core.GetAttributeValue(sourceLineNumbers, attrib);
4781 if ("PUT-FEATURE-TITLE-HERE" == title)
4782 {
4783 this.core.OnMessage(WixWarnings.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, title));
4784 }
4785 break;
4786 case "TypicalDefault":
4787 typicalDefault = this.core.GetAttributeValue(sourceLineNumbers, attrib);
4788 if (0 < typicalDefault.Length)
4789 {
4790 Wix.Feature.TypicalDefaultType typicalDefaultType = Wix.Feature.ParseTypicalDefaultType(typicalDefault);
4791 switch (typicalDefaultType)
4792 {
4793 case Wix.Feature.TypicalDefaultType.advertise:
4794 bits = bits | MsiInterop.MsidbFeatureAttributesFavorAdvertise;
4795 break;
4796 case Wix.Feature.TypicalDefaultType.install: // this is the default
4797 break;
4798 default:
4799 this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, typicalDefault, "advertise", "install"));
4800 break;
4801 }
4802 }
4803 break;
4804 default:
4805 this.core.UnexpectedAttribute(node, attrib);
4806 break;
4807 }
4808 }
4809 else
4810 {
4811 this.core.ParseExtensionAttribute(node, attrib);
4812 }
4813 }
4814
4815 if (null == id)
4816 {
4817 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
4818 id = Identifier.Invalid;
4819 }
4820 else if (38 < id.Id.Length)
4821 {
4822 this.core.OnMessage(WixErrors.FeatureNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", id.Id));
4823 }
4824
4825 if (null != configurableDirectory && configurableDirectory.ToUpper(CultureInfo.InvariantCulture) != configurableDirectory)
4826 {
4827 this.core.OnMessage(WixErrors.FeatureConfigurableDirectoryNotUppercase(sourceLineNumbers, node.Name.LocalName, "ConfigurableDirectory", configurableDirectory));
4828 }
4829
4830 if ("advertise" == typicalDefault && "no" == allowAdvertise)
4831 {
4832 this.core.OnMessage(WixErrors.FeatureCannotFavorAndDisallowAdvertise(sourceLineNumbers, node.Name.LocalName, "TypicalDefault", typicalDefault, "AllowAdvertise", allowAdvertise));
4833 }
4834
4835 if (YesNoType.Yes == followParent && ("local" == installDefault || "source" == installDefault))
4836 {
4837 this.core.OnMessage(WixErrors.FeatureCannotFollowParentAndFavorLocalOrSource(sourceLineNumbers, node.Name.LocalName, "InstallDefault", "FollowParent", "yes"));
4838 }
4839
4840 int childDisplay = 0;
4841 foreach (XElement child in node.Elements())
4842 {
4843 if (CompilerCore.WixNamespace == child.Name.Namespace)
4844 {
4845 switch (child.Name.LocalName)
4846 {
4847 case "ComponentGroupRef":
4848 this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.Feature, id.Id, null);
4849 break;
4850 case "ComponentRef":
4851 this.ParseComponentRefElement(child, ComplexReferenceParentType.Feature, id.Id, null);
4852 break;
4853 case "Component":
4854 this.ParseComponentElement(child, ComplexReferenceParentType.Feature, id.Id, null, CompilerConstants.IntegerNotSet, null, null);
4855 break;
4856 case "Condition":
4857 this.ParseConditionElement(child, node.Name.LocalName, id.Id, null);
4858 break;
4859 case "Feature":
4860 this.ParseFeatureElement(child, ComplexReferenceParentType.Feature, id.Id, ref childDisplay);
4861 break;
4862 case "FeatureGroupRef":
4863 this.ParseFeatureGroupRefElement(child, ComplexReferenceParentType.Feature, id.Id);
4864 break;
4865 case "FeatureRef":
4866 this.ParseFeatureRefElement(child, ComplexReferenceParentType.Feature, id.Id);
4867 break;
4868 case "MergeRef":
4869 this.ParseMergeRefElement(child, ComplexReferenceParentType.Feature, id.Id);
4870 break;
4871 default:
4872 this.core.UnexpectedElement(node, child);
4873 break;
4874 }
4875 }
4876 else
4877 {
4878 this.core.ParseExtensionElement(node, child);
4879 }
4880 }
4881
4882 if (!this.core.EncounteredError)
4883 {
4884 Row row = this.core.CreateRow(sourceLineNumbers, "Feature", id);
4885 row[1] = null; // this column is set in the linker
4886 row[2] = title;
4887 row[3] = description;
4888 if (0 < display.Length)
4889 {
4890 switch (display)
4891 {
4892 case "collapse":
4893 lastDisplay = (lastDisplay | 1) + 1;
4894 row[4] = lastDisplay;
4895 break;
4896 case "expand":
4897 lastDisplay = (lastDisplay + 1) | 1;
4898 row[4] = lastDisplay;
4899 break;
4900 case "hidden":
4901 row[4] = 0;
4902 break;
4903 default:
4904 int value;
4905 if (!Int32.TryParse(display, NumberStyles.Integer, CultureInfo.InvariantCulture, out value))
4906 {
4907 this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Display", display, "collapse", "expand", "hidden"));
4908 }
4909 else
4910 {
4911 row[4] = value;
4912 // save the display value of this row (if its not hidden) for subsequent rows
4913 if (0 != (int)row[4])
4914 {
4915 lastDisplay = (int)row[4];
4916 }
4917 }
4918 break;
4919 }
4920 }
4921 row[5] = level;
4922 row[6] = configurableDirectory;
4923 row[7] = bits;
4924
4925 if (ComplexReferenceParentType.Unknown != parentType)
4926 {
4927 this.core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.Feature, id.Id, false);
4928 }
4929 }
4930 }
4931
4932 /// <summary>
4933 /// Parses a feature reference element.
4934 /// </summary>
4935 /// <param name="node">Element to parse.</param>
4936 /// <param name="parentType">The type of parent.</param>
4937 /// <param name="parentId">Optional identifier for parent feature.</param>
4938 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
4939 private void ParseFeatureRefElement(XElement node, ComplexReferenceParentType parentType, string parentId)
4940 {
4941 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
4942 string id = null;
4943 YesNoType ignoreParent = YesNoType.NotSet;
4944
4945 foreach (XAttribute attrib in node.Attributes())
4946 {
4947 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
4948 {
4949 switch (attrib.Name.LocalName)
4950 {
4951 case "Id":
4952 id = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
4953 this.core.CreateSimpleReference(sourceLineNumbers, "Feature", id);
4954 break;
4955 case "IgnoreParent":
4956 ignoreParent = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
4957 break;
4958 default:
4959 this.core.UnexpectedAttribute(node, attrib);
4960 break;
4961 }
4962 }
4963 else
4964 {
4965 this.core.ParseExtensionAttribute(node, attrib);
4966 }
4967 }
4968
4969
4970 if (null == id)
4971 {
4972 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
4973 }
4974
4975 int lastDisplay = 0;
4976 foreach (XElement child in node.Elements())
4977 {
4978 if (CompilerCore.WixNamespace == child.Name.Namespace)
4979 {
4980 switch (child.Name.LocalName)
4981 {
4982 case "ComponentGroupRef":
4983 this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.Feature, id, null);
4984 break;
4985 case "ComponentRef":
4986 this.ParseComponentRefElement(child, ComplexReferenceParentType.Feature, id, null);
4987 break;
4988 case "Component":
4989 this.ParseComponentElement(child, ComplexReferenceParentType.Feature, id, null, CompilerConstants.IntegerNotSet, null, null);
4990 break;
4991 case "Feature":
4992 this.ParseFeatureElement(child, ComplexReferenceParentType.Feature, id, ref lastDisplay);
4993 break;
4994 case "FeatureGroup":
4995 this.ParseFeatureGroupElement(child, ComplexReferenceParentType.Feature, id);
4996 break;
4997 case "FeatureGroupRef":
4998 this.ParseFeatureGroupRefElement(child, ComplexReferenceParentType.Feature, id);
4999 break;
5000 case "FeatureRef":
5001 this.ParseFeatureRefElement(child, ComplexReferenceParentType.Feature, id);
5002 break;
5003 case "MergeRef":
5004 this.ParseMergeRefElement(child, ComplexReferenceParentType.Feature, id);
5005 break;
5006 default:
5007 this.core.UnexpectedElement(node, child);
5008 break;
5009 }
5010 }
5011 else
5012 {
5013 this.core.ParseExtensionElement(node, child);
5014 }
5015 }
5016
5017 if (!this.core.EncounteredError)
5018 {
5019 if (ComplexReferenceParentType.Unknown != parentType && YesNoType.Yes != ignoreParent)
5020 {
5021 this.core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.Feature, id, false);
5022 }
5023 }
5024 }
5025
5026 /// <summary>
5027 /// Parses a feature group element.
5028 /// </summary>
5029 /// <param name="node">Element to parse.</param>
5030 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
5031 private void ParseFeatureGroupElement(XElement node, ComplexReferenceParentType parentType, string parentId)
5032 {
5033 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
5034 Identifier id = null;
5035
5036 foreach (XAttribute attrib in node.Attributes())
5037 {
5038 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
5039 {
5040 switch (attrib.Name.LocalName)
5041 {
5042 case "Id":
5043 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
5044 break;
5045 default:
5046 this.core.UnexpectedAttribute(node, attrib);
5047 break;
5048 }
5049 }
5050 else
5051 {
5052 this.core.ParseExtensionAttribute(node, attrib);
5053 }
5054 }
5055
5056 if (null == id)
5057 {
5058 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
5059 id = Identifier.Invalid;
5060 }
5061
5062 int lastDisplay = 0;
5063 foreach (XElement child in node.Elements())
5064 {
5065 if (CompilerCore.WixNamespace == child.Name.Namespace)
5066 {
5067 switch (child.Name.LocalName)
5068 {
5069 case "ComponentGroupRef":
5070 this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id, null);
5071 break;
5072 case "ComponentRef":
5073 this.ParseComponentRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id, null);
5074 break;
5075 case "Component":
5076 this.ParseComponentElement(child, ComplexReferenceParentType.FeatureGroup, id.Id, null, CompilerConstants.IntegerNotSet, null, null);
5077 break;
5078 case "Feature":
5079 this.ParseFeatureElement(child, ComplexReferenceParentType.FeatureGroup, id.Id, ref lastDisplay);
5080 break;
5081 case "FeatureGroupRef":
5082 this.ParseFeatureGroupRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id);
5083 break;
5084 case "FeatureRef":
5085 this.ParseFeatureRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id);
5086 break;
5087 case "MergeRef":
5088 this.ParseMergeRefElement(child, ComplexReferenceParentType.FeatureGroup, id.Id);
5089 break;
5090 default:
5091 this.core.UnexpectedElement(node, child);
5092 break;
5093 }
5094 }
5095 else
5096 {
5097 this.core.ParseExtensionElement(node, child);
5098 }
5099 }
5100
5101 if (!this.core.EncounteredError)
5102 {
5103 Row row = this.core.CreateRow(sourceLineNumbers, "WixFeatureGroup", id);
5104
5105 //Add this FeatureGroup and its parent in WixGroup.
5106 this.core.CreateWixGroupRow(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.FeatureGroup, id.Id);
5107 }
5108 }
5109
5110 /// <summary>
5111 /// Parses a feature group reference element.
5112 /// </summary>
5113 /// <param name="node">Element to parse.</param>
5114 /// <param name="parentType">The type of parent.</param>
5115 /// <param name="parentId">Identifier of parent element.</param>
5116 private void ParseFeatureGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId)
5117 {
5118 Debug.Assert(ComplexReferenceParentType.Feature == parentType || ComplexReferenceParentType.FeatureGroup == parentType || ComplexReferenceParentType.ComponentGroup == parentType || ComplexReferenceParentType.Product == parentType);
5119
5120 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
5121 string id = null;
5122 YesNoType ignoreParent = YesNoType.NotSet;
5123 YesNoType primary = YesNoType.NotSet;
5124
5125 foreach (XAttribute attrib in node.Attributes())
5126 {
5127 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
5128 {
5129 switch (attrib.Name.LocalName)
5130 {
5131 case "Id":
5132 id = this.core.GetAttributeValue(sourceLineNumbers, attrib);
5133 this.core.CreateSimpleReference(sourceLineNumbers, "WixFeatureGroup", id);
5134 break;
5135 case "IgnoreParent":
5136 ignoreParent = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
5137 break;
5138 case "Primary":
5139 primary = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
5140 break;
5141 default:
5142 this.core.UnexpectedAttribute(node, attrib);
5143 break;
5144 }
5145 }
5146 else
5147 {
5148 this.core.ParseExtensionAttribute(node, attrib);
5149 }
5150 }
5151
5152 if (null == id)
5153 {
5154 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
5155 }
5156
5157 this.core.ParseForExtensionElements(node);
5158
5159 if (!this.core.EncounteredError)
5160 {
5161 if (YesNoType.Yes != ignoreParent)
5162 {
5163 this.core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.FeatureGroup, id, (YesNoType.Yes == primary));
5164 }
5165 }
5166 }
5167
5168 /// <summary>
5169 /// Parses an environment element.
5170 /// </summary>
5171 /// <param name="node">Element to parse.</param>
5172 /// <param name="componentId">Identifier of parent component.</param>
5173 private void ParseEnvironmentElement(XElement node, string componentId)
5174 {
5175 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
5176 Identifier id = null;
5177 string action = null;
5178 string name = null;
5179 Wix.Environment.PartType partType = Wix.Environment.PartType.NotSet;
5180 string part = null;
5181 bool permanent = false;
5182 string separator = ";"; // default to ';'
5183 bool system = false;
5184 string text = null;
5185 string uninstall = "-"; // default to remove at uninstall
5186
5187 foreach (XAttribute attrib in node.Attributes())
5188 {
5189 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
5190 {
5191 switch (attrib.Name.LocalName)
5192 {
5193 case "Id":
5194 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
5195 break;
5196 case "Action":
5197 string value = this.core.GetAttributeValue(sourceLineNumbers, attrib);
5198 if (0 < value.Length)
5199 {
5200 Wix.Environment.ActionType actionType = Wix.Environment.ParseActionType(value);
5201 switch (actionType)
5202 {
5203 case Wix.Environment.ActionType.create:
5204 action = "+";
5205 break;
5206 case Wix.Environment.ActionType.set:
5207 action = "=";
5208 break;
5209 case Wix.Environment.ActionType.remove:
5210 action = "!";
5211 break;
5212 default:
5213 this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, value, "create", "set", "remove"));
5214 break;
5215 }
5216 }
5217 break;
5218 case "Name":
5219 name = this.core.GetAttributeValue(sourceLineNumbers, attrib);
5220 break;
5221 case "Part":
5222 part = this.core.GetAttributeValue(sourceLineNumbers, attrib);
5223 if (!Wix.Environment.TryParsePartType(part, out partType))
5224 {
5225 this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Part", part, "all", "first", "last"));
5226 }
5227 break;
5228 case "Permanent":
5229 permanent = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
5230 break;
5231 case "Separator":
5232 separator = this.core.GetAttributeValue(sourceLineNumbers, attrib);
5233 break;
5234 case "System":
5235 system = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
5236 break;
5237 case "Value":
5238 text = this.core.GetAttributeValue(sourceLineNumbers, attrib);
5239 break;
5240 default:
5241 this.core.UnexpectedAttribute(node, attrib);
5242 break;
5243 }
5244 }
5245 else
5246 {
5247 this.core.ParseExtensionAttribute(node, attrib);
5248 }
5249 }
5250
5251 if (null == id)
5252 {
5253 id = this.core.CreateIdentifier("env", action, name, part, system.ToString());
5254 }
5255
5256 if (null == name)
5257 {
5258 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
5259 }
5260
5261 if (Wix.Environment.PartType.NotSet != partType)
5262 {
5263 if ("+" == action)
5264 {
5265 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Part", "Action", "create"));
5266 }
5267
5268 switch (partType)
5269 {
5270 case Wix.Environment.PartType.all:
5271 break;
5272 case Wix.Environment.PartType.first:
5273 text = String.Concat(text, separator, "[~]");
5274 break;
5275 case Wix.Environment.PartType.last:
5276 text = String.Concat("[~]", separator, text);
5277 break;
5278 }
5279 }
5280
5281 if (permanent)
5282 {
5283 uninstall = null;
5284 }
5285
5286 this.core.ParseForExtensionElements(node);
5287
5288 if (!this.core.EncounteredError)
5289 {
5290 Row row = this.core.CreateRow(sourceLineNumbers, "Environment", id);
5291 row[1] = String.Concat(action, uninstall, system ? "*" : String.Empty, name);
5292 row[2] = text;
5293 row[3] = componentId;
5294 }
5295 }
5296
5297 /// <summary>
5298 /// Parses an error element.
5299 /// </summary>
5300 /// <param name="node">Element to parse.</param>
5301 private void ParseErrorElement(XElement node)
5302 {
5303 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
5304 int id = CompilerConstants.IntegerNotSet;
5305
5306 foreach (XAttribute attrib in node.Attributes())
5307 {
5308 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
5309 {
5310 switch (attrib.Name.LocalName)
5311 {
5312 case "Id":
5313 id = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue);
5314 break;
5315 default:
5316 this.core.UnexpectedAttribute(node, attrib);
5317 break;
5318 }
5319 }
5320 else
5321 {
5322 this.core.ParseExtensionAttribute(node, attrib);
5323 }
5324 }
5325
5326 if (CompilerConstants.IntegerNotSet == id)
5327 {
5328 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
5329 id = CompilerConstants.IllegalInteger;
5330 }
5331
5332 this.core.ParseForExtensionElements(node);
5333
5334 if (!this.core.EncounteredError)
5335 {
5336 Row row = this.core.CreateRow(sourceLineNumbers, "Error");
5337 row[0] = id;
5338 row[1] = Common.GetInnerText(node); // TODO: *
5339 }
5340 }
5341
5342 /// <summary>
5343 /// Parses an extension element.
5344 /// </summary>
5345 /// <param name="node">Element to parse.</param>
5346 /// <param name="componentId">Identifier of parent component.</param>
5347 /// <param name="advertise">Flag if this extension is advertised.</param>
5348 /// <param name="progId">ProgId for extension.</param>
5349 private void ParseExtensionElement(XElement node, string componentId, YesNoType advertise, string progId)
5350 {
5351 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
5352 string extension = null;
5353 string mime = null;
5354
5355 foreach (XAttribute attrib in node.Attributes())
5356 {
5357 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
5358 {
5359 switch (attrib.Name.LocalName)
5360 {
5361 case "Id":
5362 extension = this.core.GetAttributeValue(sourceLineNumbers, attrib);
5363 break;
5364 case "Advertise":
5365 YesNoType extensionAdvertise = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
5366 if ((YesNoType.No == advertise && YesNoType.Yes == extensionAdvertise) || (YesNoType.Yes == advertise && YesNoType.No == extensionAdvertise))
5367 {
5368 this.core.OnMessage(WixErrors.AdvertiseStateMustMatch(sourceLineNumbers, extensionAdvertise.ToString(), advertise.ToString()));
5369 }
5370 advertise = extensionAdvertise;
5371 break;
5372 case "ContentType":
5373 mime = this.core.GetAttributeValue(sourceLineNumbers, attrib);
5374 break;
5375 default:
5376 this.core.UnexpectedAttribute(node, attrib);
5377 break;
5378 }
5379 }
5380 else
5381 {
5382 Dictionary<string, string> context = new Dictionary<string, string>() { { "ProgId", progId }, { "ComponentId", componentId } };
5383 this.core.ParseExtensionAttribute(node, attrib, context);
5384 }
5385 }
5386
5387 if (YesNoType.NotSet == advertise)
5388 {
5389 advertise = YesNoType.No;
5390 }
5391
5392 foreach (XElement child in node.Elements())
5393 {
5394 if (CompilerCore.WixNamespace == child.Name.Namespace)
5395 {
5396 switch (child.Name.LocalName)
5397 {
5398 case "Verb":
5399 this.ParseVerbElement(child, extension, progId, componentId, advertise);
5400 break;
5401 case "MIME":
5402 string newMime = this.ParseMIMEElement(child, extension, componentId, advertise);
5403 if (null != newMime && null == mime)
5404 {
5405 mime = newMime;
5406 }
5407 break;
5408 default:
5409 this.core.UnexpectedElement(node, child);
5410 break;
5411 }
5412 }
5413 else
5414 {
5415 this.core.ParseExtensionElement(node, child);
5416 }
5417 }
5418
5419
5420 if (YesNoType.Yes == advertise)
5421 {
5422 if (!this.core.EncounteredError)
5423 {
5424 Row row = this.core.CreateRow(sourceLineNumbers, "Extension");
5425 row[0] = extension;
5426 row[1] = componentId;
5427 row[2] = progId;
5428 row[3] = mime;
5429 row[4] = Guid.Empty.ToString("B");
5430
5431 this.core.EnsureTable(sourceLineNumbers, "Verb");
5432 }
5433 }
5434 else if (YesNoType.No == advertise)
5435 {
5436 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat(".", extension), String.Empty, progId, componentId); // Extension
5437 if (null != mime)
5438 {
5439 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat(".", extension), "Content Type", mime, componentId); // Extension's MIME ContentType
5440 }
5441 }
5442 }
5443
5444
5445 /// <summary>
5446 /// Parses a file element.
5447 /// </summary>
5448 /// <param name="node">File element to parse.</param>
5449 /// <param name="componentId">Parent's component id.</param>
5450 /// <param name="directoryId">Ancestor's directory id.</param>
5451 /// <param name="diskId">Disk id inherited from parent component.</param>
5452 /// <param name="sourcePath">Default source path of parent directory.</param>
5453 /// <param name="possibleKeyPath">This will be set with the possible keyPath for the parent component.</param>
5454 /// <param name="win64Component">true if the component is 64-bit.</param>
5455 /// <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>
5456 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
5457 private YesNoType ParseFileElement(XElement node, string componentId, string directoryId, int diskId, string sourcePath, out string possibleKeyPath, bool win64Component, string componentGuid)
5458 {
5459 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
5460 Identifier id = null;
5461 FileAssemblyType assemblyType = FileAssemblyType.NotAnAssembly;
5462 string assemblyApplication = null;
5463 string assemblyManifest = null;
5464 string bindPath = null;
5465 int bits = MsiInterop.MsidbFileAttributesVital; // assume all files are vital.
5466 string companionFile = null;
5467 string defaultLanguage = null;
5468 int defaultSize = 0;
5469 string defaultVersion = null;
5470 string fontTitle = null;
5471 bool generatedShortFileName = false;
5472 YesNoType keyPath = YesNoType.NotSet;
5473 string name = null;
5474 int patchGroup = CompilerConstants.IntegerNotSet;
5475 bool patchIgnore = false;
5476 bool patchIncludeWholeFile = false;
5477 bool patchAllowIgnoreOnError = false;
5478
5479 string ignoreLengths = null;
5480 string ignoreOffsets = null;
5481 string protectLengths = null;
5482 string protectOffsets = null;
5483 string symbols = null;
5484
5485 string procArch = null;
5486 int selfRegCost = CompilerConstants.IntegerNotSet;
5487 string shortName = null;
5488 string source = sourcePath; // assume we'll use the parents as the source for this file
5489 bool sourceSet = false;
5490
5491 foreach (XAttribute attrib in node.Attributes())
5492 {
5493 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
5494 {
5495 switch (attrib.Name.LocalName)
5496 {
5497 case "Id":
5498 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
5499 break;
5500 case "Assembly":
5501 string assemblyValue = this.core.GetAttributeValue(sourceLineNumbers, attrib);
5502 if (0 < assemblyValue.Length)
5503 {
5504 Wix.File.AssemblyType parsedAssemblyType = Wix.File.ParseAssemblyType(assemblyValue);
5505 switch (parsedAssemblyType)
5506 {
5507 case Wix.File.AssemblyType.net:
5508 assemblyType = FileAssemblyType.DotNetAssembly;
5509 break;
5510 case Wix.File.AssemblyType.no:
5511 assemblyType = FileAssemblyType.NotAnAssembly;
5512 break;
5513 case Wix.File.AssemblyType.win32:
5514 assemblyType = FileAssemblyType.Win32Assembly;
5515 break;
5516 default:
5517 this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, "File", "Assembly", assemblyValue, "no", "win32", ".net"));
5518 break;
5519 }
5520 }
5521 break;
5522 case "AssemblyApplication":
5523 assemblyApplication = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
5524 this.core.CreateSimpleReference(sourceLineNumbers, "File", assemblyApplication);
5525 break;
5526 case "AssemblyManifest":
5527 assemblyManifest = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
5528 this.core.CreateSimpleReference(sourceLineNumbers, "File", assemblyManifest);
5529 break;
5530 case "BindPath":
5531 bindPath = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty);
5532 break;
5533 case "Checksum":
5534 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
5535 {
5536 bits |= MsiInterop.MsidbFileAttributesChecksum;
5537 }
5538 break;
5539 case "CompanionFile":
5540 companionFile = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
5541 this.core.CreateSimpleReference(sourceLineNumbers, "File", companionFile);
5542 break;
5543 case "Compressed":
5544 YesNoDefaultType compressed = this.core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib);
5545 if (YesNoDefaultType.Yes == compressed)
5546 {
5547 bits |= MsiInterop.MsidbFileAttributesCompressed;
5548 }
5549 else if (YesNoDefaultType.No == compressed)
5550 {
5551 bits |= MsiInterop.MsidbFileAttributesNoncompressed;
5552 }
5553 break;
5554 case "DefaultLanguage":
5555 defaultLanguage = this.core.GetAttributeValue(sourceLineNumbers, attrib);
5556 break;
5557 case "DefaultSize":
5558 defaultSize = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, int.MaxValue);
5559 break;
5560 case "DefaultVersion":
5561 defaultVersion = this.core.GetAttributeValue(sourceLineNumbers, attrib);
5562 break;
5563 case "DiskId":
5564 diskId = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, short.MaxValue);
5565 break;
5566 case "FontTitle":
5567 fontTitle = this.core.GetAttributeValue(sourceLineNumbers, attrib);
5568 break;
5569 case "Hidden":
5570 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
5571 {
5572 bits |= MsiInterop.MsidbFileAttributesHidden;
5573 }
5574 break;
5575 case "KeyPath":
5576 keyPath = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
5577 break;
5578 case "Name":
5579 name = this.core.GetAttributeLongFilename(sourceLineNumbers, attrib, false);
5580 break;
5581 case "PatchGroup":
5582 patchGroup = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, int.MaxValue);
5583 break;
5584 case "PatchIgnore":
5585 patchIgnore = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
5586 break;
5587 case "PatchWholeFile":
5588 patchIncludeWholeFile = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
5589 break;
5590 case "PatchAllowIgnoreOnError":
5591 patchAllowIgnoreOnError = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
5592 break;
5593 case "ProcessorArchitecture":
5594 string procArchValue = this.core.GetAttributeValue(sourceLineNumbers, attrib);
5595 if (0 < procArchValue.Length)
5596 {
5597 Wix.File.ProcessorArchitectureType procArchType = Wix.File.ParseProcessorArchitectureType(procArchValue);
5598 switch (procArchType)
5599 {
5600 case Wix.File.ProcessorArchitectureType.msil:
5601 procArch = "MSIL";
5602 break;
5603 case Wix.File.ProcessorArchitectureType.x86:
5604 procArch = "x86";
5605 break;
5606 case Wix.File.ProcessorArchitectureType.x64:
5607 procArch = "amd64";
5608 break;
5609 case Wix.File.ProcessorArchitectureType.ia64:
5610 procArch = "ia64";
5611 break;
5612 default:
5613 this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, "File", "ProcessorArchitecture", procArchValue, "msil", "x86", "x64", "ia64"));
5614 break;
5615 }
5616 }
5617 break;
5618 case "ReadOnly":
5619 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
5620 {
5621 bits |= MsiInterop.MsidbFileAttributesReadOnly;
5622 }
5623 break;
5624 case "SelfRegCost":
5625 selfRegCost = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue);
5626 break;
5627 case "ShortName":
5628 shortName = this.core.GetAttributeShortFilename(sourceLineNumbers, attrib, false);
5629 break;
5630 case "Source":
5631 source = this.core.GetAttributeValue(sourceLineNumbers, attrib);
5632 sourceSet = true;
5633 break;
5634 case "System":
5635 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
5636 {
5637 bits |= MsiInterop.MsidbFileAttributesSystem;
5638 }
5639 break;
5640 case "TrueType":
5641 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
5642 {
5643 fontTitle = String.Empty;
5644 }
5645 break;
5646 case "Vital":
5647 YesNoType isVital = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
5648 if (YesNoType.Yes == isVital)
5649 {
5650 bits |= MsiInterop.MsidbFileAttributesVital;
5651 }
5652 else if (YesNoType.No == isVital)
5653 {
5654 bits &= ~MsiInterop.MsidbFileAttributesVital;
5655 }
5656 break;
5657 default:
5658 this.core.UnexpectedAttribute(node, attrib);
5659 break;
5660 }
5661 }
5662 else
5663 {
5664 this.core.ParseExtensionAttribute(node, attrib);
5665 }
5666 }
5667
5668 if (null != companionFile)
5669 {
5670 // the companion file cannot be the key path of a component
5671 if (YesNoType.Yes == keyPath)
5672 {
5673 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "CompanionFile", "KeyPath", "yes"));
5674 }
5675 }
5676
5677 if (sourceSet && !source.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal) && null == name)
5678 {
5679 name = Path.GetFileName(source);
5680 if (!this.core.IsValidLongFilename(name, false))
5681 {
5682 this.core.OnMessage(WixErrors.IllegalLongFilename(sourceLineNumbers, node.Name.LocalName, "Source", name));
5683 }
5684 }
5685
5686 // generate a short file name
5687 if (null == shortName && (null != name && !this.core.IsValidShortFilename(name, false)))
5688 {
5689 shortName = this.core.CreateShortName(name, true, false, node.Name.LocalName, directoryId);
5690 generatedShortFileName = true;
5691 }
5692
5693 if (null == id)
5694 {
5695 id = this.core.CreateIdentifier("fil", directoryId, name ?? shortName);
5696 }
5697
5698 if (!this.compilingModule && CompilerConstants.IntegerNotSet == diskId)
5699 {
5700 diskId = 1; // default to first Media
5701 }
5702
5703 if (null != defaultVersion && null != companionFile)
5704 {
5705 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DefaultVersion", "CompanionFile", companionFile));
5706 }
5707
5708 if (FileAssemblyType.NotAnAssembly == assemblyType)
5709 {
5710 if (null != assemblyManifest)
5711 {
5712 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Assembly", "AssemblyManifest"));
5713 }
5714
5715 if (null != assemblyApplication)
5716 {
5717 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Assembly", "AssemblyApplication"));
5718 }
5719 }
5720 else
5721 {
5722 if (FileAssemblyType.Win32Assembly == assemblyType && null == assemblyManifest)
5723 {
5724 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "AssemblyManifest", "Assembly", "win32"));
5725 }
5726
5727 // allow "*" guid components to omit explicit KeyPath as they can have only one file and therefore this file is the keypath
5728 if (YesNoType.Yes != keyPath && "*" != componentGuid)
5729 {
5730 this.core.OnMessage(WixErrors.IllegalAttributeValueWithoutOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Assembly", (FileAssemblyType.DotNetAssembly == assemblyType ? ".net" : "win32"), "KeyPath", "yes"));
5731 }
5732 }
5733
5734 foreach (XElement child in node.Elements())
5735 {
5736 if (CompilerCore.WixNamespace == child.Name.Namespace)
5737 {
5738 switch (child.Name.LocalName)
5739 {
5740 case "AppId":
5741 this.ParseAppIdElement(child, componentId, YesNoType.NotSet, id.Id, null, null);
5742 break;
5743 case "AssemblyName":
5744 this.ParseAssemblyName(child, componentId);
5745 break;
5746 case "Class":
5747 this.ParseClassElement(child, componentId, YesNoType.NotSet, id.Id, null, null, null);
5748 break;
5749 case "CopyFile":
5750 this.ParseCopyFileElement(child, componentId, id.Id);
5751 break;
5752 case "IgnoreRange":
5753 this.ParseRangeElement(child, ref ignoreOffsets, ref ignoreLengths);
5754 break;
5755 case "ODBCDriver":
5756 this.ParseODBCDriverOrTranslator(child, componentId, id.Id, this.tableDefinitions["ODBCDriver"]);
5757 break;
5758 case "ODBCTranslator":
5759 this.ParseODBCDriverOrTranslator(child, componentId, id.Id, this.tableDefinitions["ODBCTranslator"]);
5760 break;
5761 case "Permission":
5762 this.ParsePermissionElement(child, id.Id, "File");
5763 break;
5764 case "PermissionEx":
5765 this.ParsePermissionExElement(child, id.Id, "File");
5766 break;
5767 case "ProtectRange":
5768 this.ParseRangeElement(child, ref protectOffsets, ref protectLengths);
5769 break;
5770 case "Shortcut":
5771 this.ParseShortcutElement(child, componentId, node.Name.LocalName, id.Id, keyPath);
5772 break;
5773 case "SymbolPath":
5774 if (null != symbols)
5775 {
5776 symbols += ";" + this.ParseSymbolPathElement(child);
5777 }
5778 else
5779 {
5780 symbols = this.ParseSymbolPathElement(child);
5781 }
5782 break;
5783 case "TypeLib":
5784 this.ParseTypeLibElement(child, componentId, id.Id, win64Component);
5785 break;
5786 default:
5787 this.core.UnexpectedElement(node, child);
5788 break;
5789 }
5790 }
5791 else
5792 {
5793 Dictionary<string, string> context = new Dictionary<string, string>() { { "FileId", id.Id }, { "ComponentId", componentId }, { "Win64", win64Component.ToString() } };
5794 this.core.ParseExtensionElement(node, child, context);
5795 }
5796 }
5797
5798
5799 if (!this.core.EncounteredError)
5800 {
5801 PatchAttributeType patchAttributes = PatchAttributeType.None;
5802 if (patchIgnore)
5803 {
5804 patchAttributes |= PatchAttributeType.Ignore;
5805 }
5806 if (patchIncludeWholeFile)
5807 {
5808 patchAttributes |= PatchAttributeType.IncludeWholeFile;
5809 }
5810 if (patchAllowIgnoreOnError)
5811 {
5812 patchAttributes |= PatchAttributeType.AllowIgnoreOnError;
5813 }
5814
5815 if (String.IsNullOrEmpty(source))
5816 {
5817 if (!this.useShortFileNames && null != name)
5818 {
5819 source = name;
5820 }
5821 else
5822 {
5823 source = shortName;
5824 }
5825 }
5826 else if (source.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) // if source relies on parent directories, append the file name
5827 {
5828 if (!this.useShortFileNames && null != name)
5829 {
5830 source = Path.Combine(source, name);
5831 }
5832 else
5833 {
5834 source = Path.Combine(source, shortName);
5835 }
5836 }
5837
5838 FileRow fileRow = (FileRow)this.core.CreateRow(sourceLineNumbers, "File", id);
5839 fileRow[1] = componentId;
5840 fileRow[2] = GetMsiFilenameValue(shortName, name);
5841 fileRow[3] = defaultSize;
5842 if (null != companionFile)
5843 {
5844 fileRow[4] = companionFile;
5845 }
5846 else if (null != defaultVersion)
5847 {
5848 fileRow[4] = defaultVersion;
5849 }
5850 fileRow[5] = defaultLanguage;
5851 fileRow[6] = bits;
5852
5853 // the Sequence row is set in the binder
5854
5855 WixFileRow wixFileRow = (WixFileRow)this.core.CreateRow(sourceLineNumbers, "WixFile", id);
5856 wixFileRow.AssemblyType = assemblyType;
5857 wixFileRow.AssemblyManifest = assemblyManifest;
5858 wixFileRow.AssemblyApplication = assemblyApplication;
5859 wixFileRow.Directory = directoryId;
5860 wixFileRow.DiskId = (CompilerConstants.IntegerNotSet == diskId) ? 0 : diskId;
5861 wixFileRow.Source = source;
5862 wixFileRow.ProcessorArchitecture = procArch;
5863 wixFileRow.PatchGroup = (CompilerConstants.IntegerNotSet != patchGroup ? patchGroup : -1);
5864 wixFileRow.Attributes = (generatedShortFileName ? 0x1 : 0x0);
5865 wixFileRow.PatchAttributes = patchAttributes;
5866
5867 // Always create a delta patch row for this file since other elements (like Component and Media) may
5868 // want to add symbol paths to it.
5869 WixDeltaPatchFileRow deltaPatchFileRow = (WixDeltaPatchFileRow)this.core.CreateRow(sourceLineNumbers, "WixDeltaPatchFile", id);
5870 deltaPatchFileRow.RetainLengths = protectLengths;
5871 deltaPatchFileRow.IgnoreOffsets = ignoreOffsets;
5872 deltaPatchFileRow.IgnoreLengths = ignoreLengths;
5873 deltaPatchFileRow.RetainOffsets = protectOffsets;
5874
5875 if (null != symbols)
5876 {
5877 WixDeltaPatchSymbolPathsRow symbolRow = (WixDeltaPatchSymbolPathsRow)this.core.CreateRow(sourceLineNumbers, "WixDeltaPatchSymbolPaths", id);
5878 symbolRow.Type = SymbolPathType.File;
5879 symbolRow.SymbolPaths = symbols;
5880 }
5881
5882 if (FileAssemblyType.NotAnAssembly != assemblyType)
5883 {
5884 Row row = this.core.CreateRow(sourceLineNumbers, "MsiAssembly");
5885 row[0] = componentId;
5886 row[1] = Guid.Empty.ToString("B");
5887 row[2] = assemblyManifest;
5888 row[3] = assemblyApplication;
5889 row[4] = (FileAssemblyType.DotNetAssembly == assemblyType) ? 0 : 1;
5890 }
5891
5892 if (null != bindPath)
5893 {
5894 Row row = this.core.CreateRow(sourceLineNumbers, "BindImage");
5895 row[0] = id.Id;
5896 row[1] = bindPath;
5897
5898 // TODO: technically speaking each of the properties in the "bindPath" should be added as references, but how much do we really care about BindImage?
5899 }
5900
5901 if (CompilerConstants.IntegerNotSet != selfRegCost)
5902 {
5903 Row row = this.core.CreateRow(sourceLineNumbers, "SelfReg");
5904 row[0] = id.Id;
5905 row[1] = selfRegCost;
5906 }
5907
5908 if (null != fontTitle)
5909 {
5910 Row row = this.core.CreateRow(sourceLineNumbers, "Font");
5911 row[0] = id.Id;
5912 row[1] = fontTitle;
5913 }
5914 }
5915
5916 this.core.CreateSimpleReference(sourceLineNumbers, "Media", diskId.ToString(CultureInfo.InvariantCulture.NumberFormat));
5917
5918 // If this component does not have a companion file this file is a possible keypath.
5919 possibleKeyPath = null;
5920 if (null == companionFile)
5921 {
5922 possibleKeyPath = id.Id;
5923 }
5924
5925 return keyPath;
5926 }
5927
5928 /// <summary>
5929 /// Parses a file search element.
5930 /// </summary>
5931 /// <param name="node">Element to parse.</param>
5932 /// <param name="parentSignature">Signature of parent search element.</param>
5933 /// <param name="parentDirectorySearch">Whether this search element is used to search for the parent directory.</param>
5934 /// <param name="parentDepth">The depth specified by the parent search element.</param>
5935 /// <returns>Signature of search element.</returns>
5936 private string ParseFileSearchElement(XElement node, string parentSignature, bool parentDirectorySearch, int parentDepth)
5937 {
5938 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
5939 Identifier id = null;
5940 string languages = null;
5941 int minDate = CompilerConstants.IntegerNotSet;
5942 int maxDate = CompilerConstants.IntegerNotSet;
5943 int maxSize = CompilerConstants.IntegerNotSet;
5944 int minSize = CompilerConstants.IntegerNotSet;
5945 string maxVersion = null;
5946 string minVersion = null;
5947 string name = null;
5948 string shortName = null;
5949
5950 foreach (XAttribute attrib in node.Attributes())
5951 {
5952 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
5953 {
5954 switch (attrib.Name.LocalName)
5955 {
5956 case "Id":
5957 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
5958 break;
5959 case "Name":
5960 name = this.core.GetAttributeLongFilename(sourceLineNumbers, attrib, false);
5961 break;
5962 case "MinVersion":
5963 minVersion = this.core.GetAttributeValue(sourceLineNumbers, attrib);
5964 break;
5965 case "MaxVersion":
5966 maxVersion = this.core.GetAttributeValue(sourceLineNumbers, attrib);
5967 break;
5968 case "MinSize":
5969 minSize = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, int.MaxValue);
5970 break;
5971 case "MaxSize":
5972 maxSize = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, int.MaxValue);
5973 break;
5974 case "MinDate":
5975 minDate = this.core.GetAttributeDateTimeValue(sourceLineNumbers, attrib);
5976 break;
5977 case "MaxDate":
5978 maxDate = this.core.GetAttributeDateTimeValue(sourceLineNumbers, attrib);
5979 break;
5980 case "Languages":
5981 languages = this.core.GetAttributeValue(sourceLineNumbers, attrib);
5982 break;
5983 case "ShortName":
5984 shortName = this.core.GetAttributeShortFilename(sourceLineNumbers, attrib, false);
5985 break;
5986 default:
5987 this.core.UnexpectedAttribute(node, attrib);
5988 break;
5989 }
5990 }
5991 else
5992 {
5993 this.core.ParseExtensionAttribute(node, attrib);
5994 }
5995 }
5996
5997 // Using both ShortName and Name will not always work due to a Windows Installer bug.
5998 if (null != shortName && null != name)
5999 {
6000 this.core.OnMessage(WixWarnings.FileSearchFileNameIssue(sourceLineNumbers, node.Name.LocalName, "ShortName", "Name"));
6001 }
6002 else if (null == shortName && null == name) // at least one name must be specified.
6003 {
6004 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
6005 }
6006
6007 if (this.core.IsValidShortFilename(name, false))
6008 {
6009 if (null == shortName)
6010 {
6011 shortName = name;
6012 name = null;
6013 }
6014 else
6015 {
6016 this.core.OnMessage(WixErrors.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Name", name, "ShortName"));
6017 }
6018 }
6019
6020 if (null == id)
6021 {
6022 if (String.IsNullOrEmpty(parentSignature))
6023 {
6024 id = this.core.CreateIdentifier("fs", name ?? shortName);
6025 }
6026 else // reuse parent signature in the Signature table
6027 {
6028 id = new Identifier(parentSignature, AccessModifier.Private);
6029 }
6030 }
6031
6032 bool isSameId = String.Equals(id.Id, parentSignature, StringComparison.Ordinal);
6033 if (parentDirectorySearch)
6034 {
6035 // If searching for the parent directory, the Id attribute
6036 // value must be specified and unique.
6037 if (isSameId)
6038 {
6039 this.core.OnMessage(WixErrors.UniqueFileSearchIdRequired(sourceLineNumbers, parentSignature, node.Name.LocalName));
6040 }
6041 }
6042 else if (parentDepth > 1)
6043 {
6044 // Otherwise, if the depth > 1 the Id must be absent or the same
6045 // as the parent DirectorySearch if AssignToProperty is not set.
6046 if (!isSameId)
6047 {
6048 this.core.OnMessage(WixErrors.IllegalSearchIdForParentDepth(sourceLineNumbers, id.Id, parentSignature));
6049 }
6050 }
6051
6052 this.core.ParseForExtensionElements(node);
6053
6054 if (!this.core.EncounteredError)
6055 {
6056 Row row = this.core.CreateRow(sourceLineNumbers, "Signature", id);
6057 row[1] = name ?? shortName;
6058 row[2] = minVersion;
6059 row[3] = maxVersion;
6060
6061 if (CompilerConstants.IntegerNotSet != minSize)
6062 {
6063 row[4] = minSize;
6064 }
6065 if (CompilerConstants.IntegerNotSet != maxSize)
6066 {
6067 row[5] = maxSize;
6068 }
6069 if (CompilerConstants.IntegerNotSet != minDate)
6070 {
6071 row[6] = minDate;
6072 }
6073 if (CompilerConstants.IntegerNotSet != maxDate)
6074 {
6075 row[7] = maxDate;
6076 }
6077 row[8] = languages;
6078
6079 // Create a DrLocator row to associate the file with a directory
6080 // when a different identifier is specified for the FileSearch.
6081 if (!isSameId)
6082 {
6083 if (parentDirectorySearch)
6084 {
6085 // Creates the DrLocator row for the directory search while
6086 // the parent DirectorySearch creates the file locator row.
6087 row = this.core.CreateRow(sourceLineNumbers, "DrLocator");
6088 row[0] = parentSignature;
6089 row[1] = id;
6090 }
6091 else
6092 {
6093 row = this.core.CreateRow(sourceLineNumbers, "DrLocator", id);
6094 row[1] = parentSignature;
6095 }
6096 }
6097 }
6098
6099 return id.Id; // the id of the FileSearch element is its signature
6100 }
6101
6102
6103 /// <summary>
6104 /// Parses a fragment element.
6105 /// </summary>
6106 /// <param name="node">Element to parse.</param>
6107 private void ParseFragmentElement(XElement node)
6108 {
6109 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
6110 string id = null;
6111
6112 this.activeName = null;
6113 this.activeLanguage = null;
6114
6115 foreach (XAttribute attrib in node.Attributes())
6116 {
6117 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
6118 {
6119 switch (attrib.Name.LocalName)
6120 {
6121 case "Id":
6122 id = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
6123 break;
6124 default:
6125 this.core.UnexpectedAttribute(node, attrib);
6126 break;
6127 }
6128 }
6129 else
6130 {
6131 this.core.ParseExtensionAttribute(node, attrib);
6132 }
6133 }
6134
6135 // NOTE: Id is not required for Fragments, this is a departure from the normal run of the mill processing.
6136
6137 this.core.CreateActiveSection(id, SectionType.Fragment, 0);
6138
6139 int featureDisplay = 0;
6140 foreach (XElement child in node.Elements())
6141 {
6142 if (CompilerCore.WixNamespace == child.Name.Namespace)
6143 {
6144 switch (child.Name.LocalName)
6145 {
6146 case "_locDefinition":
6147 break;
6148 case "AdminExecuteSequence":
6149 this.ParseSequenceElement(child, child.Name.LocalName);
6150 break;
6151 case "AdminUISequence":
6152 this.ParseSequenceElement(child, child.Name.LocalName);
6153 break;
6154 case "AdvertiseExecuteSequence":
6155 this.ParseSequenceElement(child, child.Name.LocalName);
6156 break;
6157 case "InstallExecuteSequence":
6158 this.ParseSequenceElement(child, child.Name.LocalName);
6159 break;
6160 case "InstallUISequence":
6161 this.ParseSequenceElement(child, child.Name.LocalName);
6162 break;
6163 case "AppId":
6164 this.ParseAppIdElement(child, null, YesNoType.Yes, null, null, null);
6165 break;
6166 case "Binary":
6167 this.ParseBinaryElement(child);
6168 break;
6169 case "BootstrapperApplication":
6170 this.ParseBootstrapperApplicationElement(child);
6171 break;
6172 case "BootstrapperApplicationRef":
6173 this.ParseBootstrapperApplicationRefElement(child);
6174 break;
6175 case "ComplianceCheck":
6176 this.ParseComplianceCheckElement(child);
6177 break;
6178 case "Component":
6179 this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, CompilerConstants.IntegerNotSet, null, null);
6180 break;
6181 case "ComponentGroup":
6182 this.ParseComponentGroupElement(child, ComplexReferenceParentType.Unknown, id);
6183 break;
6184 case "Condition":
6185 this.ParseConditionElement(child, node.Name.LocalName, null, null);
6186 break;
6187 case "Container":
6188 this.ParseContainerElement(child);
6189 break;
6190 case "CustomAction":
6191 this.ParseCustomActionElement(child);
6192 break;
6193 case "CustomActionRef":
6194 this.ParseSimpleRefElement(child, "CustomAction");
6195 break;
6196 case "CustomTable":
6197 this.ParseCustomTableElement(child);
6198 break;
6199 case "Directory":
6200 this.ParseDirectoryElement(child, null, CompilerConstants.IntegerNotSet, String.Empty);
6201 break;
6202 case "DirectoryRef":
6203 this.ParseDirectoryRefElement(child);
6204 break;
6205 case "EmbeddedChainer":
6206 this.ParseEmbeddedChainerElement(child);
6207 break;
6208 case "EmbeddedChainerRef":
6209 this.ParseSimpleRefElement(child, "MsiEmbeddedChainer");
6210 break;
6211 case "EnsureTable":
6212 this.ParseEnsureTableElement(child);
6213 break;
6214 case "Feature":
6215 this.ParseFeatureElement(child, ComplexReferenceParentType.Unknown, null, ref featureDisplay);
6216 break;
6217 case "FeatureGroup":
6218 this.ParseFeatureGroupElement(child, ComplexReferenceParentType.Unknown, id);
6219 break;
6220 case "FeatureRef":
6221 this.ParseFeatureRefElement(child, ComplexReferenceParentType.Unknown, null);
6222 break;
6223 case "Icon":
6224 this.ParseIconElement(child);
6225 break;
6226 case "IgnoreModularization":
6227 this.ParseIgnoreModularizationElement(child);
6228 break;
6229 case "Media":
6230 this.ParseMediaElement(child, null);
6231 break;
6232 case "MediaTemplate":
6233 this.ParseMediaTemplateElement(child, null);
6234 break;
6235 case "PackageGroup":
6236 this.ParsePackageGroupElement(child);
6237 break;
6238 case "PackageCertificates":
6239 case "PatchCertificates":
6240 this.ParseCertificatesElement(child);
6241 break;
6242 case "PatchFamily":
6243 this.ParsePatchFamilyElement(child, ComplexReferenceParentType.Unknown, id);
6244 break;
6245 case "PatchFamilyGroup":
6246 this.ParsePatchFamilyGroupElement(child, ComplexReferenceParentType.Unknown, id);
6247 break;
6248 case "PatchFamilyGroupRef":
6249 this.ParsePatchFamilyGroupRefElement(child, ComplexReferenceParentType.Unknown, id);
6250 break;
6251 case "PayloadGroup":
6252 this.ParsePayloadGroupElement(child, ComplexReferenceParentType.Unknown, null);
6253 break;
6254 case "Property":
6255 this.ParsePropertyElement(child);
6256 break;
6257 case "PropertyRef":
6258 this.ParseSimpleRefElement(child, "Property");
6259 break;
6260 case "RelatedBundle":
6261 this.ParseRelatedBundleElement(child);
6262 break;
6263 case "SetDirectory":
6264 this.ParseSetDirectoryElement(child);
6265 break;
6266 case "SetProperty":
6267 this.ParseSetPropertyElement(child);
6268 break;
6269 case "SFPCatalog":
6270 string parentName = null;
6271 this.ParseSFPCatalogElement(child, ref parentName);
6272 break;
6273 case "UI":
6274 this.ParseUIElement(child);
6275 break;
6276 case "UIRef":
6277 this.ParseSimpleRefElement(child, "WixUI");
6278 break;
6279 case "Upgrade":
6280 this.ParseUpgradeElement(child);
6281 break;
6282 case "Variable":
6283 this.ParseVariableElement(child);
6284 break;
6285 case "WixVariable":
6286 this.ParseWixVariableElement(child);
6287 break;
6288 default:
6289 this.core.UnexpectedElement(node, child);
6290 break;
6291 }
6292 }
6293 else
6294 {
6295 this.core.ParseExtensionElement(node, child);
6296 }
6297 }
6298
6299 if (!this.core.EncounteredError && null != id)
6300 {
6301 Row row = this.core.CreateRow(sourceLineNumbers, "WixFragment");
6302 row[0] = id;
6303 }
6304 }
6305
6306
6307 /// <summary>
6308 /// Parses a condition element.
6309 /// </summary>
6310 /// <param name="node">Element to parse.</param>
6311 /// <param name="parentElementLocalName">LocalName of the parent element.</param>
6312 /// <param name="id">Id of the parent element.</param>
6313 /// <param name="dialog">Dialog of the parent element if its a Control.</param>
6314 /// <returns>The condition if one was found.</returns>
6315 private string ParseConditionElement(XElement node, string parentElementLocalName, string id, string dialog)
6316 {
6317 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
6318 string action = null;
6319 string condition = null;
6320 int level = CompilerConstants.IntegerNotSet;
6321 string message = null;
6322
6323 foreach (XAttribute attrib in node.Attributes())
6324 {
6325 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
6326 {
6327 switch (attrib.Name.LocalName)
6328 {
6329 case "Action":
6330 if ("Control" == parentElementLocalName)
6331 {
6332 action = this.core.GetAttributeValue(sourceLineNumbers, attrib);
6333 if (0 < action.Length)
6334 {
6335 Wix.Condition.ActionType actionType;
6336 if (Wix.Condition.TryParseActionType(action, out actionType))
6337 {
6338 action = Compiler.UppercaseFirstChar(action);
6339 }
6340 else
6341 {
6342 this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, action, "default", "disable", "enable", "hide", "show"));
6343 }
6344 }
6345 }
6346 else
6347 {
6348 this.core.UnexpectedAttribute(node, attrib);
6349 }
6350 break;
6351 case "Level":
6352 if ("Feature" == parentElementLocalName)
6353 {
6354 level = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue);
6355 }
6356 else
6357 {
6358 this.core.UnexpectedAttribute(node, attrib);
6359 }
6360 break;
6361 case "Message":
6362 if ("Fragment" == parentElementLocalName || "Product" == parentElementLocalName)
6363 {
6364 message = this.core.GetAttributeValue(sourceLineNumbers, attrib);
6365 }
6366 else
6367 {
6368 this.core.UnexpectedAttribute(node, attrib);
6369 }
6370 break;
6371 default:
6372 this.core.UnexpectedAttribute(node, attrib);
6373 break;
6374 }
6375 }
6376 else
6377 {
6378 this.core.ParseExtensionAttribute(node, attrib);
6379 }
6380 }
6381
6382 // get the condition from the inner text of the element
6383 condition = this.core.GetConditionInnerText(node);
6384
6385 this.core.ParseForExtensionElements(node);
6386
6387 // the condition should not be empty
6388 if (null == condition || 0 == condition.Length)
6389 {
6390 condition = null;
6391 this.core.OnMessage(WixErrors.ConditionExpected(sourceLineNumbers, node.Name.LocalName));
6392 }
6393
6394 switch (parentElementLocalName)
6395 {
6396 case "Control":
6397 if (null == action)
6398 {
6399 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Action"));
6400 }
6401
6402 if (!this.core.EncounteredError)
6403 {
6404 Row row = this.core.CreateRow(sourceLineNumbers, "ControlCondition");
6405 row[0] = dialog;
6406 row[1] = id;
6407 row[2] = action;
6408 row[3] = condition;
6409 }
6410 break;
6411 case "Feature":
6412 if (CompilerConstants.IntegerNotSet == level)
6413 {
6414 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Level"));
6415 level = CompilerConstants.IllegalInteger;
6416 }
6417
6418 if (!this.core.EncounteredError)
6419 {
6420 Row row = this.core.CreateRow(sourceLineNumbers, "Condition");
6421 row[0] = id;
6422 row[1] = level;
6423 row[2] = condition;
6424 }
6425 break;
6426 case "Fragment":
6427 case "Product":
6428 if (null == message)
6429 {
6430 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Message"));
6431 }
6432
6433 if (!this.core.EncounteredError)
6434 {
6435 Row row = this.core.CreateRow(sourceLineNumbers, "LaunchCondition");
6436 row[0] = condition;
6437 row[1] = message;
6438 }
6439 break;
6440 }
6441
6442 return condition;
6443 }
6444
6445 /// <summary>
6446 /// Parses a IniFile element.
6447 /// </summary>
6448 /// <param name="node">Element to parse.</param>
6449 /// <param name="componentId">Identifier of the parent component.</param>
6450 private void ParseIniFileElement(XElement node, string componentId)
6451 {
6452 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
6453 Identifier id = null;
6454 int action = CompilerConstants.IntegerNotSet;
6455 string directory = null;
6456 string key = null;
6457 string name = null;
6458 string section = null;
6459 string shortName = null;
6460 string tableName = null;
6461 string value = null;
6462
6463 foreach (XAttribute attrib in node.Attributes())
6464 {
6465 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
6466 {
6467 switch (attrib.Name.LocalName)
6468 {
6469 case "Id":
6470 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
6471 break;
6472 case "Action":
6473 string actionValue = this.core.GetAttributeValue(sourceLineNumbers, attrib);
6474 if (0 < actionValue.Length)
6475 {
6476 Wix.IniFile.ActionType actionType = Wix.IniFile.ParseActionType(actionValue);
6477 switch (actionType)
6478 {
6479 case Wix.IniFile.ActionType.addLine:
6480 action = MsiInterop.MsidbIniFileActionAddLine;
6481 break;
6482 case Wix.IniFile.ActionType.addTag:
6483 action = MsiInterop.MsidbIniFileActionAddTag;
6484 break;
6485 case Wix.IniFile.ActionType.createLine:
6486 action = MsiInterop.MsidbIniFileActionCreateLine;
6487 break;
6488 case Wix.IniFile.ActionType.removeLine:
6489 action = MsiInterop.MsidbIniFileActionRemoveLine;
6490 break;
6491 case Wix.IniFile.ActionType.removeTag:
6492 action = MsiInterop.MsidbIniFileActionRemoveTag;
6493 break;
6494 default:
6495 this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Action", actionValue, "addLine", "addTag", "createLine", "removeLine", "removeTag"));
6496 break;
6497 }
6498 }
6499 break;
6500 case "Directory":
6501 directory = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
6502 break;
6503 case "Key":
6504 key = this.core.GetAttributeValue(sourceLineNumbers, attrib);
6505 break;
6506 case "Name":
6507 name = this.core.GetAttributeLongFilename(sourceLineNumbers, attrib, false);
6508 break;
6509 case "Section":
6510 section = this.core.GetAttributeValue(sourceLineNumbers, attrib);
6511 break;
6512 case "ShortName":
6513 shortName = this.core.GetAttributeShortFilename(sourceLineNumbers, attrib, false);
6514 break;
6515 case "Value":
6516 value = this.core.GetAttributeValue(sourceLineNumbers, attrib);
6517 break;
6518 default:
6519 this.core.UnexpectedAttribute(node, attrib);
6520 break;
6521 }
6522 }
6523 else
6524 {
6525 this.core.ParseExtensionAttribute(node, attrib);
6526 }
6527 }
6528
6529 if (CompilerConstants.IntegerNotSet == action)
6530 {
6531 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Action"));
6532 action = CompilerConstants.IllegalInteger;
6533 }
6534
6535 if (null == key)
6536 {
6537 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key"));
6538 }
6539
6540 if (null == name)
6541 {
6542 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
6543 }
6544 else if (0 < name.Length)
6545 {
6546 if (this.core.IsValidShortFilename(name, false))
6547 {
6548 if (null == shortName)
6549 {
6550 shortName = name;
6551 name = null;
6552 }
6553 else
6554 {
6555 this.core.OnMessage(WixErrors.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Name", name, "ShortName"));
6556 }
6557 }
6558 else // generate a short file name.
6559 {
6560 if (null == shortName)
6561 {
6562 shortName = this.core.CreateShortName(name, true, false, node.Name.LocalName, componentId);
6563 }
6564 }
6565 }
6566
6567 if (null == section)
6568 {
6569 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Section"));
6570 }
6571
6572 if (null == id)
6573 {
6574 id = this.core.CreateIdentifier("ini", directory, name ?? shortName, section, key, name);
6575 }
6576
6577 this.core.ParseForExtensionElements(node);
6578
6579 if (MsiInterop.MsidbIniFileActionRemoveLine == action || MsiInterop.MsidbIniFileActionRemoveTag == action)
6580 {
6581 tableName = "RemoveIniFile";
6582 }
6583 else
6584 {
6585 if (null == value)
6586 {
6587 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value"));
6588 }
6589
6590 tableName = "IniFile";
6591 }
6592
6593 if (!this.core.EncounteredError)
6594 {
6595 Row row = this.core.CreateRow(sourceLineNumbers, tableName, id);
6596 row[1] = GetMsiFilenameValue(shortName, name);
6597 row[2] = directory;
6598 row[3] = section;
6599 row[4] = key;
6600 row[5] = value;
6601 row[6] = action;
6602 row[7] = componentId;
6603 }
6604 }
6605
6606 /// <summary>
6607 /// Parses an IniFile search element.
6608 /// </summary>
6609 /// <param name="node">Element to parse.</param>
6610 /// <returns>Signature for search element.</returns>
6611 private string ParseIniFileSearchElement(XElement node)
6612 {
6613 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
6614 Identifier id = null;
6615 int field = CompilerConstants.IntegerNotSet;
6616 string key = null;
6617 string name = null;
6618 string section = null;
6619 string shortName = null;
6620 string signature = null;
6621 int type = 1; // default is file
6622
6623 foreach (XAttribute attrib in node.Attributes())
6624 {
6625 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
6626 {
6627 switch (attrib.Name.LocalName)
6628 {
6629 case "Id":
6630 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
6631 break;
6632 case "Field":
6633 field = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue);
6634 break;
6635 case "Key":
6636 key = this.core.GetAttributeValue(sourceLineNumbers, attrib);
6637 break;
6638 case "Name":
6639 name = this.core.GetAttributeLongFilename(sourceLineNumbers, attrib, false);
6640 break;
6641 case "Section":
6642 section = this.core.GetAttributeValue(sourceLineNumbers, attrib);
6643 break;
6644 case "ShortName":
6645 shortName = this.core.GetAttributeShortFilename(sourceLineNumbers, attrib, false);
6646 break;
6647 case "Type":
6648 string typeValue = this.core.GetAttributeValue(sourceLineNumbers, attrib);
6649 if (0 < typeValue.Length)
6650 {
6651 Wix.IniFileSearch.TypeType typeType = Wix.IniFileSearch.ParseTypeType(typeValue);
6652 switch (typeType)
6653 {
6654 case Wix.IniFileSearch.TypeType.directory:
6655 type = 0;
6656 break;
6657 case Wix.IniFileSearch.TypeType.file:
6658 type = 1;
6659 break;
6660 case Wix.IniFileSearch.TypeType.raw:
6661 type = 2;
6662 break;
6663 default:
6664 this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Type", typeValue, "directory", "file", "registry"));
6665 break;
6666 }
6667 }
6668 break;
6669 default:
6670 this.core.UnexpectedAttribute(node, attrib);
6671 break;
6672 }
6673 }
6674 else
6675 {
6676 this.core.ParseExtensionAttribute(node, attrib);
6677 }
6678 }
6679
6680 if (null == key)
6681 {
6682 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key"));
6683 }
6684
6685 if (null == name)
6686 {
6687 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
6688 }
6689 else if (0 < name.Length)
6690 {
6691 if (this.core.IsValidShortFilename(name, false))
6692 {
6693 if (null == shortName)
6694 {
6695 shortName = name;
6696 name = null;
6697 }
6698 else
6699 {
6700 this.core.OnMessage(WixErrors.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Name", name, "ShortName"));
6701 }
6702 }
6703 else if (null == shortName) // generate a short file name.
6704 {
6705 shortName = this.core.CreateShortName(name, true, false, node.Name.LocalName);
6706 }
6707 }
6708
6709 if (null == section)
6710 {
6711 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Section"));
6712 }
6713
6714 if (null == id)
6715 {
6716 id = this.core.CreateIdentifier("ini", name, section, key, field.ToString(), type.ToString());
6717 }
6718
6719 signature = id.Id;
6720
6721 bool oneChild = false;
6722 foreach (XElement child in node.Elements())
6723 {
6724 if (CompilerCore.WixNamespace == child.Name.Namespace)
6725 {
6726 SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
6727 switch (child.Name.LocalName)
6728 {
6729 case "DirectorySearch":
6730 if (oneChild)
6731 {
6732 this.core.OnMessage(WixErrors.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName));
6733 }
6734 oneChild = true;
6735
6736 // directorysearch parentage should work like directory element, not the rest of the signature type because of the DrLocator.Parent column
6737 signature = this.ParseDirectorySearchElement(child, id.Id);
6738 break;
6739 case "DirectorySearchRef":
6740 if (oneChild)
6741 {
6742 this.core.OnMessage(WixErrors.TooManySearchElements(childSourceLineNumbers, node.Name.LocalName));
6743 }
6744 oneChild = true;
6745 signature = this.ParseDirectorySearchRefElement(child, id.Id);
6746 break;
6747 case "FileSearch":
6748 if (oneChild)
6749 {
6750 this.core.OnMessage(WixErrors.TooManySearchElements(sourceLineNumbers, node.Name.LocalName));
6751 }
6752 oneChild = true;
6753 signature = this.ParseFileSearchElement(child, id.Id, false, CompilerConstants.IntegerNotSet);
6754 id = new Identifier(signature, AccessModifier.Private); // FileSearch signatures override parent signatures
6755 break;
6756 case "FileSearchRef":
6757 if (oneChild)
6758 {
6759 this.core.OnMessage(WixErrors.TooManySearchElements(sourceLineNumbers, node.Name.LocalName));
6760 }
6761 oneChild = true;
6762 string newId = this.ParseSimpleRefElement(child, "Signature"); // FileSearch signatures override parent signatures
6763 id = new Identifier(newId, AccessModifier.Private);
6764 signature = null;
6765 break;
6766 default:
6767 this.core.UnexpectedElement(node, child);
6768 break;
6769 }
6770 }
6771 else
6772 {
6773 this.core.ParseExtensionElement(node, child);
6774 }
6775 }
6776
6777 if (!this.core.EncounteredError)
6778 {
6779 Row row = this.core.CreateRow(sourceLineNumbers, "IniLocator", id);
6780 row[1] = GetMsiFilenameValue(shortName, name);
6781 row[2] = section;
6782 row[3] = key;
6783 if (CompilerConstants.IntegerNotSet != field)
6784 {
6785 row[4] = field;
6786 }
6787 row[5] = type;
6788 }
6789
6790 return signature;
6791 }
6792
6793 /// <summary>
6794 /// Parses an isolated component element.
6795 /// </summary>
6796 /// <param name="node">Element to parse.</param>
6797 /// <param name="componentId">Identifier of parent component.</param>
6798 private void ParseIsolateComponentElement(XElement node, string componentId)
6799 {
6800 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
6801 string shared = null;
6802
6803 foreach (XAttribute attrib in node.Attributes())
6804 {
6805 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
6806 {
6807 switch (attrib.Name.LocalName)
6808 {
6809 case "Shared":
6810 shared = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
6811 this.core.CreateSimpleReference(sourceLineNumbers, "Component", shared);
6812 break;
6813 default:
6814 this.core.UnexpectedAttribute(node, attrib);
6815 break;
6816 }
6817 }
6818 else
6819 {
6820 this.core.ParseExtensionAttribute(node, attrib);
6821 }
6822 }
6823
6824 if (null == shared)
6825 {
6826 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Shared"));
6827 }
6828
6829 this.core.ParseForExtensionElements(node);
6830
6831 if (!this.core.EncounteredError)
6832 {
6833 Row row = this.core.CreateRow(sourceLineNumbers, "IsolatedComponent");
6834 row[0] = shared;
6835 row[1] = componentId;
6836 }
6837 }
6838
6839 /// <summary>
6840 /// Parses a PatchCertificates or PackageCertificates element.
6841 /// </summary>
6842 /// <param name="node">The element to parse.</param>
6843 private void ParseCertificatesElement(XElement node)
6844 {
6845 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
6846
6847 // no attributes are supported for this element
6848 foreach (XAttribute attrib in node.Attributes())
6849 {
6850 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
6851 {
6852 this.core.UnexpectedAttribute(node, attrib);
6853 }
6854 else
6855 {
6856 this.core.ParseExtensionAttribute(node, attrib);
6857 }
6858 }
6859
6860 foreach (XElement child in node.Elements())
6861 {
6862 if (CompilerCore.WixNamespace == child.Name.Namespace)
6863 {
6864 switch (child.Name.LocalName)
6865 {
6866 case "DigitalCertificate":
6867 string name = this.ParseDigitalCertificateElement(child);
6868
6869 if (!this.core.EncounteredError)
6870 {
6871 Row row = this.core.CreateRow(sourceLineNumbers, "PatchCertificates" == node.Name.LocalName ? "MsiPatchCertificate" : "MsiPackageCertificate");
6872 row[0] = name;
6873 row[1] = name;
6874 }
6875 break;
6876 default:
6877 this.core.UnexpectedElement(node, child);
6878 break;
6879 }
6880 }
6881 else
6882 {
6883 this.core.ParseExtensionElement(node, child);
6884 }
6885 }
6886 }
6887
6888 /// <summary>
6889 /// Parses an digital certificate element.
6890 /// </summary>
6891 /// <param name="node">Element to parse.</param>
6892 /// <returns>The identifier of the certificate.</returns>
6893 private string ParseDigitalCertificateElement(XElement node)
6894 {
6895 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
6896 Identifier id = null;
6897 string sourceFile = null;
6898
6899 foreach (XAttribute attrib in node.Attributes())
6900 {
6901 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
6902 {
6903 switch (attrib.Name.LocalName)
6904 {
6905 case "Id":
6906 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
6907 break;
6908 case "SourceFile":
6909 sourceFile = this.core.GetAttributeValue(sourceLineNumbers, attrib);
6910 break;
6911 default:
6912 this.core.UnexpectedAttribute(node, attrib);
6913 break;
6914 }
6915 }
6916 else
6917 {
6918 this.core.ParseExtensionAttribute(node, attrib);
6919 }
6920 }
6921
6922 if (null == id)
6923 {
6924 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
6925 id = Identifier.Invalid;
6926 }
6927 else if (40 < id.Id.Length)
6928 {
6929 this.core.OnMessage(WixErrors.StreamNameTooLong(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, id.Id.Length, 40));
6930
6931 // No need to check for modularization problems since DigitalSignature and thus DigitalCertificate
6932 // currently have no usage in merge modules.
6933 }
6934
6935 if (null == sourceFile)
6936 {
6937 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile"));
6938 }
6939
6940 this.core.ParseForExtensionElements(node);
6941
6942 if (!this.core.EncounteredError)
6943 {
6944 Row row = this.core.CreateRow(sourceLineNumbers, "MsiDigitalCertificate", id);
6945 row[1] = sourceFile;
6946 }
6947
6948 return id.Id;
6949 }
6950
6951 /// <summary>
6952 /// Parses an digital signature element.
6953 /// </summary>
6954 /// <param name="node">Element to parse.</param>
6955 /// <param name="diskId">Disk id inherited from parent media.</param>
6956 private void ParseDigitalSignatureElement(XElement node, string diskId)
6957 {
6958 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
6959 string certificateId = null;
6960 string sourceFile = null;
6961
6962 foreach (XAttribute attrib in node.Attributes())
6963 {
6964 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
6965 {
6966 switch (attrib.Name.LocalName)
6967 {
6968 case "SourceFile":
6969 sourceFile = this.core.GetAttributeValue(sourceLineNumbers, attrib);
6970 break;
6971 default:
6972 this.core.UnexpectedAttribute(node, attrib);
6973 break;
6974 }
6975 }
6976 else
6977 {
6978 this.core.ParseExtensionAttribute(node, attrib);
6979 }
6980 }
6981
6982 // sanity check for debug to ensure the stream name will not be a problem
6983 if (null != sourceFile)
6984 {
6985 Debug.Assert(62 >= "MsiDigitalSignature.Media.".Length + diskId.Length);
6986 }
6987
6988 foreach (XElement child in node.Elements())
6989 {
6990 if (CompilerCore.WixNamespace == child.Name.Namespace)
6991 {
6992 switch (child.Name.LocalName)
6993 {
6994 case "DigitalCertificate":
6995 certificateId = this.ParseDigitalCertificateElement(child);
6996 break;
6997 default:
6998 this.core.UnexpectedElement(node, child);
6999 break;
7000 }
7001 }
7002 else
7003 {
7004 this.core.ParseExtensionElement(node, child);
7005 }
7006 }
7007
7008 if (null == certificateId)
7009 {
7010 this.core.OnMessage(WixErrors.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "DigitalCertificate"));
7011 }
7012
7013 if (!this.core.EncounteredError)
7014 {
7015 Row row = this.core.CreateRow(sourceLineNumbers, "MsiDigitalSignature");
7016 row[0] = "Media";
7017 row[1] = diskId;
7018 row[2] = certificateId;
7019 row[3] = sourceFile;
7020 }
7021 }
7022
7023 /// <summary>
7024 /// Parses a MajorUpgrade element.
7025 /// </summary>
7026 /// <param name="node">The element to parse.</param>
7027 /// <param name="parentElement">The parent element.</param>
7028 private void ParseMajorUpgradeElement(XElement node, IDictionary<string, string> contextValues)
7029 {
7030 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
7031 int options = MsiInterop.MsidbUpgradeAttributesMigrateFeatures;
7032 bool allowDowngrades = false;
7033 bool allowSameVersionUpgrades = false;
7034 bool blockUpgrades = false;
7035 string downgradeErrorMessage = null;
7036 string disallowUpgradeErrorMessage = null;
7037 string removeFeatures = null;
7038 string schedule = null;
7039
7040 string upgradeCode = contextValues["UpgradeCode"];
7041 if (String.IsNullOrEmpty(upgradeCode))
7042 {
7043 this.core.OnMessage(WixErrors.ParentElementAttributeRequired(sourceLineNumbers, "Product", "UpgradeCode", node.Name.LocalName));
7044 }
7045
7046 string productVersion = contextValues["ProductVersion"];
7047 if (String.IsNullOrEmpty(productVersion))
7048 {
7049 this.core.OnMessage(WixErrors.ParentElementAttributeRequired(sourceLineNumbers, "Product", "Version", node.Name.LocalName));
7050 }
7051
7052 string productLanguage = contextValues["ProductLanguage"];
7053
7054 foreach (XAttribute attrib in node.Attributes())
7055 {
7056 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
7057 {
7058 switch (attrib.Name.LocalName)
7059 {
7060 case "AllowDowngrades":
7061 allowDowngrades = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
7062 break;
7063 case "AllowSameVersionUpgrades":
7064 allowSameVersionUpgrades = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
7065 break;
7066 case "Disallow":
7067 blockUpgrades = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
7068 break;
7069 case "DowngradeErrorMessage":
7070 downgradeErrorMessage = this.core.GetAttributeValue(sourceLineNumbers, attrib);
7071 break;
7072 case "DisallowUpgradeErrorMessage":
7073 disallowUpgradeErrorMessage = this.core.GetAttributeValue(sourceLineNumbers, attrib);
7074 break;
7075 case "MigrateFeatures":
7076 if (YesNoType.No == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
7077 {
7078 options &= ~MsiInterop.MsidbUpgradeAttributesMigrateFeatures;
7079 }
7080 break;
7081 case "IgnoreLanguage":
7082 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
7083 {
7084 productLanguage = null;
7085 }
7086 break;
7087 case "IgnoreRemoveFailure":
7088 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
7089 {
7090 options |= MsiInterop.MsidbUpgradeAttributesIgnoreRemoveFailure;
7091 }
7092 break;
7093 case "RemoveFeatures":
7094 removeFeatures = this.core.GetAttributeValue(sourceLineNumbers, attrib);
7095 break;
7096 case "Schedule":
7097 schedule = this.core.GetAttributeValue(sourceLineNumbers, attrib);
7098 break;
7099 default:
7100 this.core.UnexpectedAttribute(node, attrib);
7101 break;
7102 }
7103 }
7104 else
7105 {
7106 this.core.ParseExtensionAttribute(node, attrib);
7107 }
7108 }
7109
7110 this.core.ParseForExtensionElements(node);
7111
7112 if (!allowDowngrades && String.IsNullOrEmpty(downgradeErrorMessage))
7113 {
7114 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DowngradeErrorMessage", "AllowDowngrades", "yes", true));
7115 }
7116
7117 if (allowDowngrades && !String.IsNullOrEmpty(downgradeErrorMessage))
7118 {
7119 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DowngradeErrorMessage", "AllowDowngrades", "yes"));
7120 }
7121
7122 if (allowDowngrades && allowSameVersionUpgrades)
7123 {
7124 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "AllowSameVersionUpgrades", "AllowDowngrades", "yes"));
7125 }
7126
7127 if (blockUpgrades && String.IsNullOrEmpty(disallowUpgradeErrorMessage))
7128 {
7129 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisallowUpgradeErrorMessage", "Disallow", "yes", true));
7130 }
7131
7132 if (!blockUpgrades && !String.IsNullOrEmpty(disallowUpgradeErrorMessage))
7133 {
7134 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DisallowUpgradeErrorMessage", "Disallow", "yes"));
7135 }
7136
7137 if (!this.core.EncounteredError)
7138 {
7139 // create the row that performs the upgrade (or downgrade)
7140 Row row = this.core.CreateRow(sourceLineNumbers, "Upgrade");
7141 row[0] = upgradeCode;
7142 if (allowDowngrades)
7143 {
7144 row[1] = "0"; // let any version satisfy
7145 // row[2] = maximum version; omit so we don't have to fake a version like "255.255.65535";
7146 row[3] = productLanguage;
7147 row[4] = options | MsiInterop.MsidbUpgradeAttributesVersionMinInclusive;
7148 }
7149 else
7150 {
7151 // row[1] = minimum version; skip it so we detect all prior versions.
7152 row[2] = productVersion;
7153 row[3] = productLanguage;
7154 row[4] = allowSameVersionUpgrades ? (options | MsiInterop.MsidbUpgradeAttributesVersionMaxInclusive) : options;
7155 }
7156
7157 row[5] = removeFeatures;
7158 row[6] = Compiler.UpgradeDetectedProperty;
7159
7160 // Ensure the action property is secure.
7161 this.AddWixPropertyRow(sourceLineNumbers, new Identifier(Compiler.UpgradeDetectedProperty, AccessModifier.Public), false, true, false);
7162
7163 // Add launch condition that blocks upgrades
7164 if (blockUpgrades)
7165 {
7166 row = this.core.CreateRow(sourceLineNumbers, "LaunchCondition");
7167 row[0] = Compiler.UpgradePreventedCondition;
7168 row[1] = disallowUpgradeErrorMessage;
7169 }
7170
7171 // now create the Upgrade row and launch conditions to prevent downgrades (unless explicitly permitted)
7172 if (!allowDowngrades)
7173 {
7174 row = this.core.CreateRow(sourceLineNumbers, "Upgrade");
7175 row[0] = upgradeCode;
7176 row[1] = productVersion;
7177 // row[2] = maximum version; skip it so we detect all future versions.
7178 row[3] = productLanguage;
7179 row[4] = MsiInterop.MsidbUpgradeAttributesOnlyDetect;
7180 // row[5] = removeFeatures;
7181 row[6] = Compiler.DowngradeDetectedProperty;
7182
7183 // Ensure the action property is secure.
7184 this.AddWixPropertyRow(sourceLineNumbers, new Identifier(Compiler.DowngradeDetectedProperty, AccessModifier.Public), false, true, false);
7185
7186 row = this.core.CreateRow(sourceLineNumbers, "LaunchCondition");
7187 row[0] = Compiler.DowngradePreventedCondition;
7188 row[1] = downgradeErrorMessage;
7189 }
7190
7191 // finally, schedule RemoveExistingProducts
7192 row = this.core.CreateRow(sourceLineNumbers, "WixAction");
7193 row[0] = "InstallExecuteSequence";
7194 row[1] = "RemoveExistingProducts";
7195 // row[2] = condition;
7196 // row[3] = sequence;
7197 row[6] = 0; // overridable
7198
7199 switch (schedule)
7200 {
7201 case null:
7202 case "afterInstallValidate":
7203 // row[4] = beforeAction;
7204 row[5] = "InstallValidate";
7205 break;
7206 case "afterInstallInitialize":
7207 // row[4] = beforeAction;
7208 row[5] = "InstallInitialize";
7209 break;
7210 case "afterInstallExecute":
7211 // row[4] = beforeAction;
7212 row[5] = "InstallExecute";
7213 break;
7214 case "afterInstallExecuteAgain":
7215 // row[4] = beforeAction;
7216 row[5] = "InstallExecuteAgain";
7217 break;
7218 case "afterInstallFinalize":
7219 // row[4] = beforeAction;
7220 row[5] = "InstallFinalize";
7221 break;
7222 }
7223 }
7224 }
7225
7226 /// <summary>
7227 /// Parses a media element.
7228 /// </summary>
7229 /// <param name="node">Element to parse.</param>
7230 /// <param name="patchId">Set to the PatchId if parsing Patch/Media element otherwise null.</param>
7231 private void ParseMediaElement(XElement node, string patchId)
7232 {
7233 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
7234 int id = CompilerConstants.IntegerNotSet;
7235 string cabinet = null;
7236 CompressionLevel? compressionLevel = null;
7237 string diskPrompt = null;
7238 string layout = null;
7239 bool patch = null != patchId;
7240 string volumeLabel = null;
7241 string source = null;
7242 string symbols = null;
7243
7244 YesNoType embedCab = patch ? YesNoType.Yes : YesNoType.NotSet;
7245
7246 foreach (XAttribute attrib in node.Attributes())
7247 {
7248 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
7249 {
7250 switch (attrib.Name.LocalName)
7251 {
7252 case "Id":
7253 id = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, short.MaxValue);
7254 break;
7255 case "Cabinet":
7256 cabinet = this.core.GetAttributeValue(sourceLineNumbers, attrib);
7257 break;
7258 case "CompressionLevel":
7259 string compressionLevelString = this.core.GetAttributeValue(sourceLineNumbers, attrib);
7260 if (0 < compressionLevelString.Length)
7261 {
7262 Wix.CompressionLevelType compressionLevelType;
7263 if (!Wix.Enums.TryParseCompressionLevelType(compressionLevelString, out compressionLevelType))
7264 {
7265 this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, compressionLevelString, "high", "low", "medium", "mszip", "none"));
7266 }
7267 else
7268 {
7269 compressionLevel = (CompressionLevel)Enum.Parse(typeof(CompressionLevel), compressionLevelString, true);
7270 }
7271 }
7272 break;
7273 case "DiskPrompt":
7274 diskPrompt = this.core.GetAttributeValue(sourceLineNumbers, attrib);
7275 this.core.CreateSimpleReference(sourceLineNumbers, "Property", "DiskPrompt"); // ensure the output has a DiskPrompt Property defined
7276 break;
7277 case "EmbedCab":
7278 embedCab = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
7279 break;
7280 case "Layout":
7281 case "src":
7282 if (null != layout)
7283 {
7284 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Layout", "src"));
7285 }
7286
7287 if ("src" == attrib.Name.LocalName)
7288 {
7289 this.core.OnMessage(WixWarnings.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Layout"));
7290 }
7291 layout = this.core.GetAttributeValue(sourceLineNumbers, attrib);
7292 break;
7293 case "VolumeLabel":
7294 volumeLabel = this.core.GetAttributeValue(sourceLineNumbers, attrib);
7295 break;
7296 case "Source":
7297 source = this.core.GetAttributeValue(sourceLineNumbers, attrib);
7298 break;
7299 default:
7300 this.core.UnexpectedAttribute(node, attrib);
7301 break;
7302 }
7303 }
7304 else
7305 {
7306 this.core.ParseExtensionAttribute(node, attrib);
7307 }
7308 }
7309
7310 if (CompilerConstants.IntegerNotSet == id)
7311 {
7312 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
7313 id = CompilerConstants.IllegalInteger;
7314 }
7315
7316 if (YesNoType.IllegalValue != embedCab)
7317 {
7318 if (YesNoType.Yes == embedCab)
7319 {
7320 if (null == cabinet)
7321 {
7322 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Cabinet", "EmbedCab", "yes"));
7323 }
7324 else
7325 {
7326 if (62 < cabinet.Length)
7327 {
7328 this.core.OnMessage(WixErrors.MediaEmbeddedCabinetNameTooLong(sourceLineNumbers, node.Name.LocalName, "Cabinet", cabinet, cabinet.Length));
7329 }
7330
7331 cabinet = String.Concat("#", cabinet);
7332 }
7333 }
7334 else // external cabinet file
7335 {
7336 // external cabinet files must use 8.3 filenames
7337 if (!String.IsNullOrEmpty(cabinet) && !this.core.IsValidShortFilename(cabinet, false))
7338 {
7339 // WiX variables in the name will trip the "not a valid 8.3 name" switch, so let them through
7340 if (!Common.WixVariableRegex.Match(cabinet).Success)
7341 {
7342 this.core.OnMessage(WixWarnings.MediaExternalCabinetFilenameIllegal(sourceLineNumbers, node.Name.LocalName, "Cabinet", cabinet));
7343 }
7344 }
7345 }
7346 }
7347
7348 if (null != compressionLevel && null == cabinet)
7349 {
7350 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Cabinet", "CompressionLevel"));
7351 }
7352
7353 if (patch)
7354 {
7355 // Default Source to a form of the Patch Id if none is specified.
7356 if (null == source)
7357 {
7358 source = String.Concat("_", new Guid(patchId).ToString("N", CultureInfo.InvariantCulture).ToUpper(CultureInfo.InvariantCulture));
7359 }
7360 }
7361
7362 foreach (XElement child in node.Elements())
7363 {
7364 SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
7365 if (CompilerCore.WixNamespace == child.Name.Namespace)
7366 {
7367 switch (child.Name.LocalName)
7368 {
7369 case "DigitalSignature":
7370 if (YesNoType.Yes == embedCab)
7371 {
7372 this.core.OnMessage(WixErrors.SignedEmbeddedCabinet(childSourceLineNumbers));
7373 }
7374 else if (null == cabinet)
7375 {
7376 this.core.OnMessage(WixErrors.ExpectedSignedCabinetName(childSourceLineNumbers));
7377 }
7378 else
7379 {
7380 this.ParseDigitalSignatureElement(child, id.ToString(CultureInfo.InvariantCulture.NumberFormat));
7381 }
7382 break;
7383 case "PatchBaseline":
7384 if (patch)
7385 {
7386 this.ParsePatchBaselineElement(child, id);
7387 }
7388 else
7389 {
7390 this.core.UnexpectedElement(node, child);
7391 }
7392 break;
7393 case "SymbolPath":
7394 if (null != symbols)
7395 {
7396 symbols += "" + this.ParseSymbolPathElement(child);
7397 }
7398 else
7399 {
7400 symbols = this.ParseSymbolPathElement(child);
7401 }
7402 break;
7403 default:
7404 this.core.UnexpectedElement(node, child);
7405 break;
7406 }
7407 }
7408 else
7409 {
7410 this.core.ParseExtensionElement(node, child);
7411 }
7412 }
7413
7414
7415
7416 // add the row to the section
7417 if (!this.core.EncounteredError)
7418 {
7419 MediaRow mediaRow = (MediaRow)this.core.CreateRow(sourceLineNumbers, "Media");
7420 mediaRow.DiskId = id;
7421 mediaRow.LastSequence = 0; // this is set in the binder
7422 mediaRow.DiskPrompt = diskPrompt;
7423 mediaRow.Cabinet = cabinet;
7424 mediaRow.VolumeLabel = volumeLabel;
7425 mediaRow.Source = source;
7426
7427 // the Source column is only set when creating a patch
7428
7429 if (null != compressionLevel || null != layout)
7430 {
7431 WixMediaRow row = (WixMediaRow)this.core.CreateRow(sourceLineNumbers, "WixMedia");
7432 row.DiskId = id;
7433 row.CompressionLevel = compressionLevel;
7434 row.Layout = layout;
7435 }
7436
7437 if (null != symbols)
7438 {
7439 WixDeltaPatchSymbolPathsRow symbolRow = (WixDeltaPatchSymbolPathsRow)this.core.CreateRow(sourceLineNumbers, "WixDeltaPatchSymbolPaths");
7440 symbolRow.Id = id.ToString(CultureInfo.InvariantCulture);
7441 symbolRow.Type = SymbolPathType.Media;
7442 symbolRow.SymbolPaths = symbols;
7443 }
7444 }
7445 }
7446
7447 /// <summary>
7448 /// Parses a media template element.
7449 /// </summary>
7450 /// <param name="node">Element to parse.</param>
7451 /// <param name="patchId">Set to the PatchId if parsing Patch/Media element otherwise null.</param>
7452 private void ParseMediaTemplateElement(XElement node, string patchId)
7453 {
7454 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
7455 string cabinetTemplate = "cab{0}.cab";
7456 string compressionLevel = null; // this defaults to mszip in Binder
7457 string diskPrompt = null;
7458 bool patch = null != patchId;
7459 string volumeLabel = null;
7460 int maximumUncompressedMediaSize = CompilerConstants.IntegerNotSet;
7461 int maximumCabinetSizeForLargeFileSplitting = CompilerConstants.IntegerNotSet;
7462 Wix.CompressionLevelType compressionLevelType = Wix.CompressionLevelType.NotSet;
7463
7464 YesNoType embedCab = patch ? YesNoType.Yes : YesNoType.NotSet;
7465
7466 foreach (XAttribute attrib in node.Attributes())
7467 {
7468 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
7469 {
7470 switch (attrib.Name.LocalName)
7471 {
7472 case "CabinetTemplate":
7473 string authoredCabinetTemplateValue = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty);
7474 if (!String.IsNullOrEmpty(authoredCabinetTemplateValue))
7475 {
7476 cabinetTemplate = authoredCabinetTemplateValue;
7477 }
7478
7479 // Create an example cabinet name using the maximum number of cabinets supported, 999.
7480 string exampleCabinetName = String.Format(cabinetTemplate, "###");
7481 if (!this.core.IsValidLocIdentifier(exampleCabinetName))
7482 {
7483 // The example name should not match the authored template since that would nullify the
7484 // reason for having multiple cabients. External cabinet files must also be valid file names.
7485 if (exampleCabinetName.Equals(authoredCabinetTemplateValue) || !this.core.IsValidLongFilename(exampleCabinetName, false))
7486 {
7487 this.core.OnMessage(WixErrors.InvalidCabinetTemplate(sourceLineNumbers, cabinetTemplate));
7488 }
7489 else if (!this.core.IsValidShortFilename(exampleCabinetName, false) && !Common.WixVariableRegex.Match(exampleCabinetName).Success) // ignore short names with wix variables because it rarely works out.
7490 {
7491 this.core.OnMessage(WixWarnings.MediaExternalCabinetFilenameIllegal(sourceLineNumbers, node.Name.LocalName, "CabinetTemplate", cabinetTemplate));
7492 }
7493 }
7494 break;
7495 case "CompressionLevel":
7496 compressionLevel = this.core.GetAttributeValue(sourceLineNumbers, attrib);
7497 if (0 < compressionLevel.Length)
7498 {
7499 if (!Wix.Enums.TryParseCompressionLevelType(compressionLevel, out compressionLevelType))
7500 {
7501 this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, compressionLevel, "high", "low", "medium", "mszip", "none"));
7502 }
7503 }
7504 break;
7505 case "DiskPrompt":
7506 diskPrompt = this.core.GetAttributeValue(sourceLineNumbers, attrib);
7507 this.core.CreateSimpleReference(sourceLineNumbers, "Property", "DiskPrompt"); // ensure the output has a DiskPrompt Property defined
7508 this.core.OnMessage(WixWarnings.ReservedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName));
7509 break;
7510 case "EmbedCab":
7511 embedCab = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
7512 break;
7513 case "VolumeLabel":
7514 volumeLabel = this.core.GetAttributeValue(sourceLineNumbers, attrib);
7515 this.core.OnMessage(WixWarnings.ReservedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName));
7516 break;
7517 case "MaximumUncompressedMediaSize":
7518 maximumUncompressedMediaSize = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, int.MaxValue);
7519 break;
7520 case "MaximumCabinetSizeForLargeFileSplitting":
7521 maximumCabinetSizeForLargeFileSplitting = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, CompilerCore.MinValueOfMaxCabSizeForLargeFileSplitting, CompilerCore.MaxValueOfMaxCabSizeForLargeFileSplitting);
7522 break;
7523 default:
7524 this.core.UnexpectedAttribute(node, attrib);
7525 break;
7526 }
7527 }
7528 else
7529 {
7530 this.core.ParseExtensionAttribute(node, attrib);
7531 }
7532 }
7533
7534 if (YesNoType.IllegalValue != embedCab)
7535 {
7536 if (YesNoType.Yes == embedCab)
7537 {
7538 cabinetTemplate = String.Concat("#", cabinetTemplate);
7539 }
7540 }
7541
7542 if (!this.core.EncounteredError)
7543 {
7544 MediaRow temporaryMediaRow = (MediaRow)this.core.CreateRow(sourceLineNumbers, "Media");
7545 temporaryMediaRow.DiskId = 1;
7546 WixMediaTemplateRow mediaTemplateRow = (WixMediaTemplateRow)this.core.CreateRow(sourceLineNumbers, "WixMediaTemplate");
7547 mediaTemplateRow.CabinetTemplate = cabinetTemplate;
7548 mediaTemplateRow.VolumeLabel = volumeLabel;
7549 mediaTemplateRow.DiskPrompt = diskPrompt;
7550 mediaTemplateRow.VolumeLabel = volumeLabel;
7551
7552 if (maximumUncompressedMediaSize != CompilerConstants.IntegerNotSet)
7553 {
7554 mediaTemplateRow.MaximumUncompressedMediaSize = maximumUncompressedMediaSize;
7555 }
7556 else
7557 {
7558 mediaTemplateRow.MaximumUncompressedMediaSize = CompilerCore.DefaultMaximumUncompressedMediaSize;
7559 }
7560
7561 if (maximumCabinetSizeForLargeFileSplitting != CompilerConstants.IntegerNotSet)
7562 {
7563 mediaTemplateRow.MaximumCabinetSizeForLargeFileSplitting = maximumCabinetSizeForLargeFileSplitting;
7564 }
7565 else
7566 {
7567 mediaTemplateRow.MaximumCabinetSizeForLargeFileSplitting = 0; // Default value of 0 corresponds to max size of 2048 MB (i.e. 2 GB)
7568 }
7569
7570 switch (compressionLevelType)
7571 {
7572 case Wix.CompressionLevelType.high:
7573 mediaTemplateRow.CompressionLevel = CompressionLevel.High;
7574 break;
7575 case Wix.CompressionLevelType.low:
7576 mediaTemplateRow.CompressionLevel = CompressionLevel.Low;
7577 break;
7578 case Wix.CompressionLevelType.medium:
7579 mediaTemplateRow.CompressionLevel = CompressionLevel.Medium;
7580 break;
7581 case Wix.CompressionLevelType.none:
7582 mediaTemplateRow.CompressionLevel = CompressionLevel.None;
7583 break;
7584 case Wix.CompressionLevelType.mszip:
7585 mediaTemplateRow.CompressionLevel = CompressionLevel.Mszip;
7586 break;
7587 }
7588 }
7589 }
7590
7591 /// <summary>
7592 /// Parses a merge element.
7593 /// </summary>
7594 /// <param name="node">Element to parse.</param>
7595 /// <param name="directoryId">Identifier for parent directory.</param>
7596 /// <param name="diskId">Disk id inherited from parent directory.</param>
7597 private void ParseMergeElement(XElement node, string directoryId, int diskId)
7598 {
7599 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
7600 Identifier id = null;
7601 string configData = String.Empty;
7602 YesNoType fileCompression = YesNoType.NotSet;
7603 string language = null;
7604 string sourceFile = null;
7605
7606 foreach (XAttribute attrib in node.Attributes())
7607 {
7608 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
7609 {
7610 switch (attrib.Name.LocalName)
7611 {
7612 case "Id":
7613 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
7614 break;
7615 case "DiskId":
7616 diskId = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, short.MaxValue);
7617 this.core.CreateSimpleReference(sourceLineNumbers, "Media", diskId.ToString(CultureInfo.InvariantCulture.NumberFormat));
7618 break;
7619 case "FileCompression":
7620 fileCompression = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
7621 break;
7622 case "Language":
7623 language = this.core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue);
7624 break;
7625 case "SourceFile":
7626 sourceFile = this.core.GetAttributeValue(sourceLineNumbers, attrib);
7627 break;
7628 default:
7629 this.core.UnexpectedAttribute(node, attrib);
7630 break;
7631 }
7632 }
7633 else
7634 {
7635 this.core.ParseExtensionAttribute(node, attrib);
7636 }
7637 }
7638
7639 if (null == id)
7640 {
7641 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
7642 }
7643
7644 if (null == language)
7645 {
7646 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Language"));
7647 }
7648
7649 if (null == sourceFile)
7650 {
7651 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile"));
7652 }
7653
7654 if (CompilerConstants.IntegerNotSet == diskId)
7655 {
7656 this.core.OnMessage(WixErrors.ExpectedAttributeInElementOrParent(sourceLineNumbers, node.Name.LocalName, "DiskId", "Directory"));
7657 diskId = CompilerConstants.IllegalInteger;
7658 }
7659
7660 foreach (XElement child in node.Elements())
7661 {
7662 if (CompilerCore.WixNamespace == child.Name.Namespace)
7663 {
7664 switch (child.Name.LocalName)
7665 {
7666 case "ConfigurationData":
7667 if (0 == configData.Length)
7668 {
7669 configData = this.ParseConfigurationDataElement(child);
7670 }
7671 else
7672 {
7673 configData = String.Concat(configData, ",", this.ParseConfigurationDataElement(child));
7674 }
7675 break;
7676 default:
7677 this.core.UnexpectedElement(node, child);
7678 break;
7679 }
7680 }
7681 else
7682 {
7683 this.core.ParseExtensionElement(node, child);
7684 }
7685 }
7686
7687 if (!this.core.EncounteredError)
7688 {
7689 Row row = this.core.CreateRow(sourceLineNumbers, "WixMerge", id);
7690 row[1] = language;
7691 row[2] = directoryId;
7692 row[3] = sourceFile;
7693 row[4] = diskId;
7694 if (YesNoType.Yes == fileCompression)
7695 {
7696 row[5] = 1;
7697 }
7698 else if (YesNoType.No == fileCompression)
7699 {
7700 row[5] = 0;
7701 }
7702 else // YesNoType.NotSet == fileCompression
7703 {
7704 // and we leave the column null
7705 }
7706 row[6] = configData;
7707 row[7] = Guid.Empty.ToString("B");
7708 }
7709 }
7710
7711 /// <summary>
7712 /// Parses a configuration data element.
7713 /// </summary>
7714 /// <param name="node">Element to parse.</param>
7715 /// <returns>String in format "name=value" with '%', ',' and '=' hex encoded.</returns>
7716 private string ParseConfigurationDataElement(XElement node)
7717 {
7718 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
7719 string name = null;
7720 string value = null;
7721
7722 foreach (XAttribute attrib in node.Attributes())
7723 {
7724 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
7725 {
7726 switch (attrib.Name.LocalName)
7727 {
7728 case "Name":
7729 name = this.core.GetAttributeValue(sourceLineNumbers, attrib);
7730 break;
7731 case "Value":
7732 value = this.core.GetAttributeValue(sourceLineNumbers, attrib);
7733 break;
7734 default:
7735 this.core.UnexpectedAttribute(node, attrib);
7736 break;
7737 }
7738 }
7739 else
7740 {
7741 this.core.ParseExtensionAttribute(node, attrib);
7742 }
7743 }
7744
7745 if (null == name)
7746 {
7747 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
7748 }
7749 else // need to hex encode these characters
7750 {
7751 name = name.Replace("%", "%25");
7752 name = name.Replace("=", "%3D");
7753 name = name.Replace(",", "%2C");
7754 }
7755
7756 if (null == value)
7757 {
7758 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value"));
7759 }
7760 else // need to hex encode these characters
7761 {
7762 value = value.Replace("%", "%25");
7763 value = value.Replace("=", "%3D");
7764 value = value.Replace(",", "%2C");
7765 }
7766
7767 this.core.ParseForExtensionElements(node);
7768
7769 return String.Concat(name, "=", value);
7770 }
7771
7772 /// <summary>
7773 /// Parses a merge reference element.
7774 /// </summary>
7775 /// <param name="node">Element to parse.</param>
7776 /// <param name="parentType">Parents complex reference type.</param>
7777 /// <param name="parentId">Identifier for parent feature or feature group.</param>
7778 private void ParseMergeRefElement(XElement node, ComplexReferenceParentType parentType, string parentId)
7779 {
7780 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
7781 string id = null;
7782 YesNoType primary = YesNoType.NotSet;
7783
7784 foreach (XAttribute attrib in node.Attributes())
7785 {
7786 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
7787 {
7788 switch (attrib.Name.LocalName)
7789 {
7790 case "Id":
7791 id = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
7792 this.core.CreateSimpleReference(sourceLineNumbers, "WixMerge", id);
7793 break;
7794 case "Primary":
7795 primary = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
7796 break;
7797 default:
7798 this.core.UnexpectedAttribute(node, attrib);
7799 break;
7800 }
7801 }
7802 else
7803 {
7804 this.core.ParseExtensionAttribute(node, attrib);
7805 }
7806 }
7807
7808 if (null == id)
7809 {
7810 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
7811 }
7812
7813 this.core.ParseForExtensionElements(node);
7814
7815 this.core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.Module, id, (YesNoType.Yes == primary));
7816 }
7817
7818 /// <summary>
7819 /// Parses a mime element.
7820 /// </summary>
7821 /// <param name="node">Element to parse.</param>
7822 /// <param name="extension">Identifier for parent extension.</param>
7823 /// <param name="componentId">Identifier for parent component.</param>
7824 /// <param name="parentAdvertised">Flag if the parent element is advertised.</param>
7825 /// <returns>Content type if this is the default for the MIME type.</returns>
7826 private string ParseMIMEElement(XElement node, string extension, string componentId, YesNoType parentAdvertised)
7827 {
7828 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
7829 string classId = null;
7830 string contentType = null;
7831 YesNoType advertise = parentAdvertised;
7832 YesNoType returnContentType = YesNoType.NotSet;
7833
7834 foreach (XAttribute attrib in node.Attributes())
7835 {
7836 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
7837 {
7838 switch (attrib.Name.LocalName)
7839 {
7840 case "Advertise":
7841 advertise = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
7842 break;
7843 case "Class":
7844 classId = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
7845 break;
7846 case "ContentType":
7847 contentType = this.core.GetAttributeValue(sourceLineNumbers, attrib);
7848 break;
7849 case "Default":
7850 returnContentType = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
7851 break;
7852 default:
7853 this.core.UnexpectedAttribute(node, attrib);
7854 break;
7855 }
7856 }
7857 else
7858 {
7859 this.core.ParseExtensionAttribute(node, attrib);
7860 }
7861 }
7862
7863 if (null == contentType)
7864 {
7865 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ContentType"));
7866 }
7867
7868 // if the advertise state has not been set, default to non-advertised
7869 if (YesNoType.NotSet == advertise)
7870 {
7871 advertise = YesNoType.No;
7872 }
7873
7874 this.core.ParseForExtensionElements(node);
7875
7876 if (YesNoType.Yes == advertise)
7877 {
7878 if (YesNoType.Yes != parentAdvertised)
7879 {
7880 this.core.OnMessage(WixErrors.AdvertiseStateMustMatch(sourceLineNumbers, advertise.ToString(), parentAdvertised.ToString()));
7881 }
7882
7883 if (!this.core.EncounteredError)
7884 {
7885 Row row = this.core.CreateRow(sourceLineNumbers, "MIME");
7886 row[0] = contentType;
7887 row[1] = extension;
7888 row[2] = classId;
7889 }
7890 }
7891 else if (YesNoType.No == advertise)
7892 {
7893 if (YesNoType.Yes == returnContentType && YesNoType.Yes == parentAdvertised)
7894 {
7895 this.core.OnMessage(WixErrors.CannotDefaultMismatchedAdvertiseStates(sourceLineNumbers));
7896 }
7897
7898 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat("MIME\\Database\\Content Type\\", contentType), "Extension", String.Concat(".", extension), componentId);
7899 if (null != classId)
7900 {
7901 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, 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 module element.
7910 /// </summary>
7911 /// <param name="node">Element to parse.</param>
7912 private void ParseModuleElement(XElement node)
7913 {
7914 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
7915 int codepage = 0;
7916 string moduleId = null;
7917 string version = null;
7918
7919 this.activeName = null;
7920 this.activeLanguage = null;
7921
7922 foreach (XAttribute attrib in node.Attributes())
7923 {
7924 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
7925 {
7926 switch (attrib.Name.LocalName)
7927 {
7928 case "Id":
7929 this.activeName = this.core.GetAttributeValue(sourceLineNumbers, attrib);
7930 if ("PUT-MODULE-NAME-HERE" == this.activeName)
7931 {
7932 this.core.OnMessage(WixWarnings.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, this.activeName));
7933 }
7934 else
7935 {
7936 this.activeName = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
7937 }
7938 break;
7939 case "Codepage":
7940 codepage = this.core.GetAttributeCodePageValue(sourceLineNumbers, attrib);
7941 break;
7942 case "Guid":
7943 moduleId = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
7944 this.core.OnMessage(WixWarnings.DeprecatedModuleGuidAttribute(sourceLineNumbers));
7945 break;
7946 case "Language":
7947 this.activeLanguage = this.core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue);
7948 break;
7949 case "Version":
7950 version = this.core.GetAttributeVersionValue(sourceLineNumbers, attrib);
7951 break;
7952 default:
7953 this.core.UnexpectedAttribute(node, attrib);
7954 break;
7955 }
7956 }
7957 else
7958 {
7959 this.core.ParseExtensionAttribute(node, attrib);
7960 }
7961 }
7962
7963 if (null == this.activeName)
7964 {
7965 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
7966 }
7967
7968 if (null == this.activeLanguage)
7969 {
7970 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Language"));
7971 }
7972
7973 if (null == version)
7974 {
7975 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version"));
7976 }
7977 else if (!CompilerCore.IsValidModuleOrBundleVersion(version))
7978 {
7979 this.core.OnMessage(WixWarnings.InvalidModuleOrBundleVersion(sourceLineNumbers, "Module", version));
7980 }
7981
7982 try
7983 {
7984 this.compilingModule = true; // notice that we are actually building a Merge Module here
7985 this.core.CreateActiveSection(this.activeName, SectionType.Module, codepage);
7986
7987 foreach (XElement child in node.Elements())
7988 {
7989 if (CompilerCore.WixNamespace == child.Name.Namespace)
7990 {
7991 switch (child.Name.LocalName)
7992 {
7993 case "AdminExecuteSequence":
7994 case "AdminUISequence":
7995 case "AdvertiseExecuteSequence":
7996 case "InstallExecuteSequence":
7997 case "InstallUISequence":
7998 this.ParseSequenceElement(child, child.Name.LocalName);
7999 break;
8000 case "AppId":
8001 this.ParseAppIdElement(child, null, YesNoType.Yes, null, null, null);
8002 break;
8003 case "Binary":
8004 this.ParseBinaryElement(child);
8005 break;
8006 case "Component":
8007 this.ParseComponentElement(child, ComplexReferenceParentType.Module, this.activeName, this.activeLanguage, CompilerConstants.IntegerNotSet, null, null);
8008 break;
8009 case "ComponentGroupRef":
8010 this.ParseComponentGroupRefElement(child, ComplexReferenceParentType.Module, this.activeName, this.activeLanguage);
8011 break;
8012 case "ComponentRef":
8013 this.ParseComponentRefElement(child, ComplexReferenceParentType.Module, this.activeName, this.activeLanguage);
8014 break;
8015 case "Configuration":
8016 this.ParseConfigurationElement(child);
8017 break;
8018 case "CustomAction":
8019 this.ParseCustomActionElement(child);
8020 break;
8021 case "CustomActionRef":
8022 this.ParseSimpleRefElement(child, "CustomAction");
8023 break;
8024 case "CustomTable":
8025 this.ParseCustomTableElement(child);
8026 break;
8027 case "Dependency":
8028 this.ParseDependencyElement(child);
8029 break;
8030 case "Directory":
8031 this.ParseDirectoryElement(child, null, CompilerConstants.IntegerNotSet, String.Empty);
8032 break;
8033 case "DirectoryRef":
8034 this.ParseDirectoryRefElement(child);
8035 break;
8036 case "EmbeddedChainer":
8037 this.ParseEmbeddedChainerElement(child);
8038 break;
8039 case "EmbeddedChainerRef":
8040 this.ParseSimpleRefElement(child, "MsiEmbeddedChainer");
8041 break;
8042 case "EnsureTable":
8043 this.ParseEnsureTableElement(child);
8044 break;
8045 case "Exclusion":
8046 this.ParseExclusionElement(child);
8047 break;
8048 case "Icon":
8049 this.ParseIconElement(child);
8050 break;
8051 case "IgnoreModularization":
8052 this.ParseIgnoreModularizationElement(child);
8053 break;
8054 case "IgnoreTable":
8055 this.ParseIgnoreTableElement(child);
8056 break;
8057 case "Package":
8058 this.ParsePackageElement(child, null, moduleId);
8059 break;
8060 case "Property":
8061 this.ParsePropertyElement(child);
8062 break;
8063 case "PropertyRef":
8064 this.ParseSimpleRefElement(child, "Property");
8065 break;
8066 case "SetDirectory":
8067 this.ParseSetDirectoryElement(child);
8068 break;
8069 case "SetProperty":
8070 this.ParseSetPropertyElement(child);
8071 break;
8072 case "SFPCatalog":
8073 string parentName = null;
8074 this.ParseSFPCatalogElement(child, ref parentName);
8075 break;
8076 case "Substitution":
8077 this.ParseSubstitutionElement(child);
8078 break;
8079 case "UI":
8080 this.ParseUIElement(child);
8081 break;
8082 case "UIRef":
8083 this.ParseSimpleRefElement(child, "WixUI");
8084 break;
8085 case "WixVariable":
8086 this.ParseWixVariableElement(child);
8087 break;
8088 default:
8089 this.core.UnexpectedElement(node, child);
8090 break;
8091 }
8092 }
8093 else
8094 {
8095 this.core.ParseExtensionElement(node, child);
8096 }
8097 }
8098
8099
8100 if (!this.core.EncounteredError)
8101 {
8102 Row row = this.core.CreateRow(sourceLineNumbers, "ModuleSignature");
8103 row[0] = this.activeName;
8104 row[1] = this.activeLanguage;
8105 row[2] = version;
8106 }
8107 }
8108 finally
8109 {
8110 this.compilingModule = false; // notice that we are no longer building a Merge Module here
8111 }
8112 }
8113
8114 /// <summary>
8115 /// Parses a patch creation element.
8116 /// </summary>
8117 /// <param name="node">The element to parse.</param>
8118 private void ParsePatchCreationElement(XElement node)
8119 {
8120 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
8121 bool clean = true; // Default is to clean
8122 int codepage = 0;
8123 string outputPath = null;
8124 bool productMismatches = false;
8125 string replaceGuids = String.Empty;
8126 string sourceList = null;
8127 string symbolFlags = null;
8128 string targetProducts = String.Empty;
8129 bool versionMismatches = false;
8130 bool wholeFiles = false;
8131
8132 foreach (XAttribute attrib in node.Attributes())
8133 {
8134 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
8135 {
8136 switch (attrib.Name.LocalName)
8137 {
8138 case "Id":
8139 this.activeName = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
8140 break;
8141 case "AllowMajorVersionMismatches":
8142 versionMismatches = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
8143 break;
8144 case "AllowProductCodeMismatches":
8145 productMismatches = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
8146 break;
8147 case "CleanWorkingFolder":
8148 clean = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
8149 break;
8150 case "Codepage":
8151 codepage = this.core.GetAttributeCodePageValue(sourceLineNumbers, attrib);
8152 break;
8153 case "OutputPath":
8154 outputPath = this.core.GetAttributeValue(sourceLineNumbers, attrib);
8155 break;
8156 case "SourceList":
8157 sourceList = this.core.GetAttributeValue(sourceLineNumbers, attrib);
8158 break;
8159 case "SymbolFlags":
8160 symbolFlags = String.Format(CultureInfo.InvariantCulture, "0x{0:x8}", this.core.GetAttributeLongValue(sourceLineNumbers, attrib, 0, uint.MaxValue));
8161 break;
8162 case "WholeFilesOnly":
8163 wholeFiles = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
8164 break;
8165 default:
8166 this.core.UnexpectedAttribute(node, attrib);
8167 break;
8168 }
8169 }
8170 else
8171 {
8172 this.core.ParseExtensionAttribute(node, attrib);
8173 }
8174 }
8175
8176 if (null == this.activeName)
8177 {
8178 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
8179 }
8180
8181 this.core.CreateActiveSection(this.activeName, SectionType.PatchCreation, codepage);
8182
8183 foreach (XElement child in node.Elements())
8184 {
8185 if (CompilerCore.WixNamespace == child.Name.Namespace)
8186 {
8187 switch (child.Name.LocalName)
8188 {
8189 case "Family":
8190 this.ParseFamilyElement(child);
8191 break;
8192 case "PatchInformation":
8193 this.ParsePatchInformationElement(child);
8194 break;
8195 case "PatchMetadata":
8196 this.ParsePatchMetadataElement(child);
8197 break;
8198 case "PatchProperty":
8199 this.ParsePatchPropertyElement(child, false);
8200 break;
8201 case "PatchSequence":
8202 this.ParsePatchSequenceElement(child);
8203 break;
8204 case "ReplacePatch":
8205 replaceGuids = String.Concat(replaceGuids, this.ParseReplacePatchElement(child));
8206 break;
8207 case "TargetProductCode":
8208 string targetProduct = this.ParseTargetProductCodeElement(child);
8209 if (0 < targetProducts.Length)
8210 {
8211 targetProducts = String.Concat(targetProducts, ";");
8212 }
8213 targetProducts = String.Concat(targetProducts, targetProduct);
8214 break;
8215 default:
8216 this.core.UnexpectedElement(node, child);
8217 break;
8218 }
8219 }
8220 else
8221 {
8222 this.core.ParseExtensionElement(node, child);
8223 }
8224 }
8225
8226 this.ProcessProperties(sourceLineNumbers, "PatchGUID", this.activeName);
8227 this.ProcessProperties(sourceLineNumbers, "AllowProductCodeMismatches", productMismatches ? "1" : "0");
8228 this.ProcessProperties(sourceLineNumbers, "AllowProductVersionMajorMismatches", versionMismatches ? "1" : "0");
8229 this.ProcessProperties(sourceLineNumbers, "DontRemoveTempFolderWhenFinished", clean ? "0" : "1");
8230 this.ProcessProperties(sourceLineNumbers, "IncludeWholeFilesOnly", wholeFiles ? "1" : "0");
8231
8232 if (null != symbolFlags)
8233 {
8234 this.ProcessProperties(sourceLineNumbers, "ApiPatchingSymbolFlags", symbolFlags);
8235 }
8236
8237 if (0 < replaceGuids.Length)
8238 {
8239 this.ProcessProperties(sourceLineNumbers, "ListOfPatchGUIDsToReplace", replaceGuids);
8240 }
8241
8242 if (0 < targetProducts.Length)
8243 {
8244 this.ProcessProperties(sourceLineNumbers, "ListOfTargetProductCodes", targetProducts);
8245 }
8246
8247 if (null != outputPath)
8248 {
8249 this.ProcessProperties(sourceLineNumbers, "PatchOutputPath", outputPath);
8250 }
8251
8252 if (null != sourceList)
8253 {
8254 this.ProcessProperties(sourceLineNumbers, "PatchSourceList", sourceList);
8255 }
8256 }
8257
8258 /// <summary>
8259 /// Parses a family element.
8260 /// </summary>
8261 /// <param name="node">The element to parse.</param>
8262 private void ParseFamilyElement(XElement node)
8263 {
8264 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
8265 int diskId = CompilerConstants.IntegerNotSet;
8266 string diskPrompt = null;
8267 string mediaSrcProp = null;
8268 string name = null;
8269 int sequenceStart = CompilerConstants.IntegerNotSet;
8270 string volumeLabel = null;
8271
8272 foreach (XAttribute attrib in node.Attributes())
8273 {
8274 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
8275 {
8276 switch (attrib.Name.LocalName)
8277 {
8278 case "DiskId":
8279 diskId = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, short.MaxValue);
8280 break;
8281 case "DiskPrompt":
8282 diskPrompt = this.core.GetAttributeValue(sourceLineNumbers, attrib);
8283 break;
8284 case "MediaSrcProp":
8285 mediaSrcProp = this.core.GetAttributeValue(sourceLineNumbers, attrib);
8286 break;
8287 case "Name":
8288 name = this.core.GetAttributeValue(sourceLineNumbers, attrib);
8289 break;
8290 case "SequenceStart":
8291 sequenceStart = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, int.MaxValue);
8292 break;
8293 case "VolumeLabel":
8294 volumeLabel = this.core.GetAttributeValue(sourceLineNumbers, attrib);
8295 break;
8296 default:
8297 this.core.UnexpectedAttribute(node, attrib);
8298 break;
8299 }
8300 }
8301 else
8302 {
8303 this.core.ParseExtensionAttribute(node, attrib);
8304 }
8305 }
8306
8307 if (null == name)
8308 {
8309 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
8310 }
8311 else if (0 < name.Length)
8312 {
8313 if (8 < name.Length) // check the length
8314 {
8315 this.core.OnMessage(WixErrors.FamilyNameTooLong(sourceLineNumbers, node.Name.LocalName, "Name", name, name.Length));
8316 }
8317 else // check for illegal characters
8318 {
8319 foreach (char character in name)
8320 {
8321 if (!Char.IsLetterOrDigit(character) && '_' != character)
8322 {
8323 this.core.OnMessage(WixErrors.IllegalFamilyName(sourceLineNumbers, node.Name.LocalName, "Name", name));
8324 }
8325 }
8326 }
8327 }
8328
8329 foreach (XElement child in node.Elements())
8330 {
8331 if (CompilerCore.WixNamespace == child.Name.Namespace)
8332 {
8333 switch (child.Name.LocalName)
8334 {
8335 case "UpgradeImage":
8336 this.ParseUpgradeImageElement(child, name);
8337 break;
8338 case "ExternalFile":
8339 this.ParseExternalFileElement(child, name);
8340 break;
8341 case "ProtectFile":
8342 this.ParseProtectFileElement(child, name);
8343 break;
8344 default:
8345 this.core.UnexpectedElement(node, child);
8346 break;
8347 }
8348 }
8349 else
8350 {
8351 this.core.ParseExtensionElement(node, child);
8352 }
8353 }
8354
8355 if (!this.core.EncounteredError)
8356 {
8357 Row row = this.core.CreateRow(sourceLineNumbers, "ImageFamilies");
8358 row[0] = name;
8359 row[1] = mediaSrcProp;
8360 if (CompilerConstants.IntegerNotSet != diskId)
8361 {
8362 row[2] = diskId;
8363 }
8364
8365 if (CompilerConstants.IntegerNotSet != sequenceStart)
8366 {
8367 row[3] = sequenceStart;
8368 }
8369 row[4] = diskPrompt;
8370 row[5] = volumeLabel;
8371 }
8372 }
8373
8374 /// <summary>
8375 /// Parses an upgrade image element.
8376 /// </summary>
8377 /// <param name="node">The element to parse.</param>
8378 /// <param name="family">The family for this element.</param>
8379 private void ParseUpgradeImageElement(XElement node, string family)
8380 {
8381 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
8382 string sourceFile = null;
8383 string sourcePatch = null;
8384 List<string> symbols = new List<string>();
8385 string upgrade = null;
8386
8387 foreach (XAttribute attrib in node.Attributes())
8388 {
8389 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
8390 {
8391 switch (attrib.Name.LocalName)
8392 {
8393 case "Id":
8394 upgrade = this.core.GetAttributeValue(sourceLineNumbers, attrib);
8395 if (13 < upgrade.Length)
8396 {
8397 this.core.OnMessage(WixErrors.IdentifierTooLongError(sourceLineNumbers, node.Name.LocalName, "Id", upgrade, 13));
8398 }
8399 break;
8400 case "SourceFile":
8401 case "src":
8402 if (null != sourceFile)
8403 {
8404 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "src", "SourceFile"));
8405 }
8406
8407 if ("src" == attrib.Name.LocalName)
8408 {
8409 this.core.OnMessage(WixWarnings.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "SourceFile"));
8410 }
8411 sourceFile = this.core.GetAttributeValue(sourceLineNumbers, attrib);
8412 break;
8413 case "SourcePatch":
8414 case "srcPatch":
8415 if (null != sourcePatch)
8416 {
8417 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "srcPatch", "SourcePatch"));
8418 }
8419
8420 if ("srcPatch" == attrib.Name.LocalName)
8421 {
8422 this.core.OnMessage(WixWarnings.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "SourcePatch"));
8423 }
8424 sourcePatch = this.core.GetAttributeValue(sourceLineNumbers, attrib);
8425 break;
8426 default:
8427 this.core.UnexpectedAttribute(node, attrib);
8428 break;
8429 }
8430 }
8431 else
8432 {
8433 this.core.ParseExtensionAttribute(node, attrib);
8434 }
8435 }
8436
8437 if (null == upgrade)
8438 {
8439 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
8440 }
8441
8442 if (null == sourceFile)
8443 {
8444 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile"));
8445 }
8446
8447 foreach (XElement child in node.Elements())
8448 {
8449 if (CompilerCore.WixNamespace == child.Name.Namespace)
8450 {
8451 switch (child.Name.LocalName)
8452 {
8453 case "SymbolPath":
8454 symbols.Add(this.ParseSymbolPathElement(child));
8455 break;
8456 case "TargetImage":
8457 this.ParseTargetImageElement(child, upgrade, family);
8458 break;
8459 case "UpgradeFile":
8460 this.ParseUpgradeFileElement(child, upgrade);
8461 break;
8462 default:
8463 this.core.UnexpectedElement(node, child);
8464 break;
8465 }
8466 }
8467 else
8468 {
8469 this.core.ParseExtensionElement(node, child);
8470 }
8471 }
8472
8473 if (!this.core.EncounteredError)
8474 {
8475 Row row = this.core.CreateRow(sourceLineNumbers, "UpgradedImages");
8476 row[0] = upgrade;
8477 row[1] = sourceFile;
8478 row[2] = sourcePatch;
8479 row[3] = String.Join(";", symbols);
8480 row[4] = family;
8481 }
8482 }
8483
8484 /// <summary>
8485 /// Parses an upgrade file element.
8486 /// </summary>
8487 /// <param name="node">The element to parse.</param>
8488 /// <param name="upgrade">The upgrade key for this element.</param>
8489 private void ParseUpgradeFileElement(XElement node, string upgrade)
8490 {
8491 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
8492 bool allowIgnoreOnError = false;
8493 string file = null;
8494 bool ignore = false;
8495 List<string> symbols = new List<string>();
8496 bool wholeFile = false;
8497
8498 foreach (XAttribute attrib in node.Attributes())
8499 {
8500 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
8501 {
8502 switch (attrib.Name.LocalName)
8503 {
8504 case "AllowIgnoreOnError":
8505 allowIgnoreOnError = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
8506 break;
8507 case "File":
8508 file = this.core.GetAttributeValue(sourceLineNumbers, attrib);
8509 break;
8510 case "Ignore":
8511 ignore = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
8512 break;
8513 case "WholeFile":
8514 wholeFile = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
8515 break;
8516 default:
8517 this.core.UnexpectedAttribute(node, attrib);
8518 break;
8519 }
8520 }
8521 else
8522 {
8523 this.core.ParseExtensionAttribute(node, attrib);
8524 }
8525 }
8526
8527 if (null == file)
8528 {
8529 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "File"));
8530 }
8531
8532 foreach (XElement child in node.Elements())
8533 {
8534 if (CompilerCore.WixNamespace == child.Name.Namespace)
8535 {
8536 switch (child.Name.LocalName)
8537 {
8538 case "SymbolPath":
8539 symbols.Add(this.ParseSymbolPathElement(child));
8540 break;
8541 default:
8542 this.core.UnexpectedElement(node, child);
8543 break;
8544 }
8545 }
8546 else
8547 {
8548 this.core.ParseExtensionElement(node, child);
8549 }
8550 }
8551
8552 if (!this.core.EncounteredError)
8553 {
8554 if (ignore)
8555 {
8556 Row row = this.core.CreateRow(sourceLineNumbers, "UpgradedFilesToIgnore");
8557 row[0] = upgrade;
8558 row[1] = file;
8559 }
8560 else
8561 {
8562 Row row = this.core.CreateRow(sourceLineNumbers, "UpgradedFiles_OptionalData");
8563 row[0] = upgrade;
8564 row[1] = file;
8565 row[2] = String.Join(";", symbols);
8566 row[3] = allowIgnoreOnError ? 1 : 0;
8567 row[4] = wholeFile ? 1 : 0;
8568 }
8569 }
8570 }
8571
8572 /// <summary>
8573 /// Parses a target image element.
8574 /// </summary>
8575 /// <param name="node">The element to parse.</param>
8576 /// <param name="upgrade">The upgrade key for this element.</param>
8577 /// <param name="family">The family key for this element.</param>
8578 private void ParseTargetImageElement(XElement node, string upgrade, string family)
8579 {
8580 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
8581 bool ignore = false;
8582 int order = CompilerConstants.IntegerNotSet;
8583 string sourceFile = null;
8584 string symbols = null;
8585 string target = null;
8586 string validation = null;
8587
8588 foreach (XAttribute attrib in node.Attributes())
8589 {
8590 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
8591 {
8592 switch (attrib.Name.LocalName)
8593 {
8594 case "Id":
8595 target = this.core.GetAttributeValue(sourceLineNumbers, attrib);
8596 if (target.Length > 13)
8597 {
8598 this.core.OnMessage(WixErrors.IdentifierTooLongError(sourceLineNumbers, node.Name.LocalName, "Id", target, 13));
8599 }
8600 break;
8601 case "IgnoreMissingFiles":
8602 ignore = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
8603 break;
8604 case "Order":
8605 order = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, int.MinValue + 2, int.MaxValue);
8606 break;
8607 case "SourceFile":
8608 case "src":
8609 if (null != sourceFile)
8610 {
8611 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "src", "SourceFile"));
8612 }
8613
8614 if ("src" == attrib.Name.LocalName)
8615 {
8616 this.core.OnMessage(WixWarnings.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "SourceFile"));
8617 }
8618 sourceFile = this.core.GetAttributeValue(sourceLineNumbers, attrib);
8619 break;
8620 case "Validation":
8621 validation = this.core.GetAttributeValue(sourceLineNumbers, attrib);
8622 break;
8623 default:
8624 this.core.UnexpectedAttribute(node, attrib);
8625 break;
8626 }
8627 }
8628 else
8629 {
8630 this.core.ParseExtensionAttribute(node, attrib);
8631 }
8632 }
8633
8634 if (null == target)
8635 {
8636 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
8637 }
8638
8639 if (null == sourceFile)
8640 {
8641 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile"));
8642 }
8643
8644 if (CompilerConstants.IntegerNotSet == order)
8645 {
8646 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Order"));
8647 }
8648
8649 foreach (XElement child in node.Elements())
8650 {
8651 if (CompilerCore.WixNamespace == child.Name.Namespace)
8652 {
8653 switch (child.Name.LocalName)
8654 {
8655 case "SymbolPath":
8656 if (null != symbols)
8657 {
8658 symbols = String.Concat(symbols, ";", this.ParseSymbolPathElement(child));
8659 }
8660 else
8661 {
8662 symbols = this.ParseSymbolPathElement(child);
8663 }
8664 break;
8665 case "TargetFile":
8666 this.ParseTargetFileElement(child, target, family);
8667 break;
8668 default:
8669 this.core.UnexpectedElement(node, child);
8670 break;
8671 }
8672 }
8673 else
8674 {
8675 this.core.ParseExtensionElement(node, child);
8676 }
8677 }
8678
8679 if (!this.core.EncounteredError)
8680 {
8681 Row row = this.core.CreateRow(sourceLineNumbers, "TargetImages");
8682 row[0] = target;
8683 row[1] = sourceFile;
8684 row[2] = symbols;
8685 row[3] = upgrade;
8686 row[4] = order;
8687 row[5] = validation;
8688 row[6] = ignore ? 1 : 0;
8689 }
8690 }
8691
8692 /// <summary>
8693 /// Parses an upgrade file element.
8694 /// </summary>
8695 /// <param name="node">The element to parse.</param>
8696 /// <param name="target">The upgrade key for this element.</param>
8697 /// <param name="family">The family key for this element.</param>
8698 private void ParseTargetFileElement(XElement node, string target, string family)
8699 {
8700 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
8701 string file = null;
8702 string ignoreLengths = null;
8703 string ignoreOffsets = null;
8704 string protectLengths = null;
8705 string protectOffsets = null;
8706 string symbols = null;
8707
8708 foreach (XAttribute attrib in node.Attributes())
8709 {
8710 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
8711 {
8712 switch (attrib.Name.LocalName)
8713 {
8714 case "Id":
8715 file = this.core.GetAttributeValue(sourceLineNumbers, attrib);
8716 break;
8717 default:
8718 this.core.UnexpectedAttribute(node, attrib);
8719 break;
8720 }
8721 }
8722 else
8723 {
8724 this.core.ParseExtensionAttribute(node, attrib);
8725 }
8726 }
8727
8728 if (null == file)
8729 {
8730 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
8731 }
8732
8733 foreach (XElement child in node.Elements())
8734 {
8735 if (CompilerCore.WixNamespace == child.Name.Namespace)
8736 {
8737 switch (child.Name.LocalName)
8738 {
8739 case "IgnoreRange":
8740 this.ParseRangeElement(child, ref ignoreOffsets, ref ignoreLengths);
8741 break;
8742 case "ProtectRange":
8743 this.ParseRangeElement(child, ref protectOffsets, ref protectLengths);
8744 break;
8745 case "SymbolPath":
8746 symbols = this.ParseSymbolPathElement(child);
8747 break;
8748 default:
8749 this.core.UnexpectedElement(node, child);
8750 break;
8751 }
8752 }
8753 else
8754 {
8755 this.core.ParseExtensionElement(node, child);
8756 }
8757 }
8758
8759 if (!this.core.EncounteredError)
8760 {
8761 Row row = this.core.CreateRow(sourceLineNumbers, "TargetFiles_OptionalData");
8762 row[0] = target;
8763 row[1] = file;
8764 row[2] = symbols;
8765 row[3] = ignoreOffsets;
8766 row[4] = ignoreLengths;
8767
8768 if (null != protectOffsets)
8769 {
8770 row[5] = protectOffsets;
8771
8772 Row row2 = this.core.CreateRow(sourceLineNumbers, "FamilyFileRanges");
8773 row2[0] = family;
8774 row2[1] = file;
8775 row2[2] = protectOffsets;
8776 row2[3] = protectLengths;
8777 }
8778 }
8779 }
8780
8781 /// <summary>
8782 /// Parses an external file element.
8783 /// </summary>
8784 /// <param name="node">The element to parse.</param>
8785 /// <param name="family">The family for this element.</param>
8786 private void ParseExternalFileElement(XElement node, string family)
8787 {
8788 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
8789 string file = null;
8790 string ignoreLengths = null;
8791 string ignoreOffsets = null;
8792 int order = CompilerConstants.IntegerNotSet;
8793 string protectLengths = null;
8794 string protectOffsets = null;
8795 string source = null;
8796 string symbols = null;
8797
8798 foreach (XAttribute attrib in node.Attributes())
8799 {
8800 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
8801 {
8802 switch (attrib.Name.LocalName)
8803 {
8804 case "File":
8805 file = this.core.GetAttributeValue(sourceLineNumbers, attrib);
8806 break;
8807 case "Order":
8808 order = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, int.MinValue + 2, int.MaxValue);
8809 break;
8810 case "Source":
8811 case "src":
8812 if (null != source)
8813 {
8814 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "src", "Source"));
8815 }
8816
8817 if ("src" == attrib.Name.LocalName)
8818 {
8819 this.core.OnMessage(WixWarnings.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Source"));
8820 }
8821 source = this.core.GetAttributeValue(sourceLineNumbers, attrib);
8822 break;
8823 default:
8824 this.core.UnexpectedAttribute(node, attrib);
8825 break;
8826 }
8827 }
8828 else
8829 {
8830 this.core.ParseExtensionAttribute(node, attrib);
8831 }
8832 }
8833
8834 if (null == file)
8835 {
8836 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "File"));
8837 }
8838
8839 if (null == source)
8840 {
8841 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Source"));
8842 }
8843
8844 if (CompilerConstants.IntegerNotSet == order)
8845 {
8846 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Order"));
8847 }
8848
8849 foreach (XElement child in node.Elements())
8850 {
8851 if (CompilerCore.WixNamespace == child.Name.Namespace)
8852 {
8853 switch (child.Name.LocalName)
8854 {
8855 case "IgnoreRange":
8856 this.ParseRangeElement(child, ref ignoreOffsets, ref ignoreLengths);
8857 break;
8858 case "ProtectRange":
8859 this.ParseRangeElement(child, ref protectOffsets, ref protectLengths);
8860 break;
8861 case "SymbolPath":
8862 symbols = this.ParseSymbolPathElement(child);
8863 break;
8864 default:
8865 this.core.UnexpectedElement(node, child);
8866 break;
8867 }
8868 }
8869 else
8870 {
8871 this.core.ParseExtensionElement(node, child);
8872 }
8873 }
8874
8875 if (!this.core.EncounteredError)
8876 {
8877 Row row = this.core.CreateRow(sourceLineNumbers, "ExternalFiles");
8878 row[0] = family;
8879 row[1] = file;
8880 row[2] = source;
8881 row[3] = symbols;
8882 row[4] = ignoreOffsets;
8883 row[5] = ignoreLengths;
8884 if (null != protectOffsets)
8885 {
8886 row[6] = protectOffsets;
8887 }
8888
8889 if (CompilerConstants.IntegerNotSet != order)
8890 {
8891 row[7] = order;
8892 }
8893
8894 if (null != protectOffsets)
8895 {
8896 Row row2 = this.core.CreateRow(sourceLineNumbers, "FamilyFileRanges");
8897 row2[0] = family;
8898 row2[1] = file;
8899 row2[2] = protectOffsets;
8900 row2[3] = protectLengths;
8901 }
8902 }
8903 }
8904
8905 /// <summary>
8906 /// Parses a protect file element.
8907 /// </summary>
8908 /// <param name="node">The element to parse.</param>
8909 /// <param name="family">The family for this element.</param>
8910 private void ParseProtectFileElement(XElement node, string family)
8911 {
8912 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
8913 string file = null;
8914 string protectLengths = null;
8915 string protectOffsets = null;
8916
8917 foreach (XAttribute attrib in node.Attributes())
8918 {
8919 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
8920 {
8921 switch (attrib.Name.LocalName)
8922 {
8923 case "File":
8924 file = this.core.GetAttributeValue(sourceLineNumbers, attrib);
8925 break;
8926 default:
8927 this.core.UnexpectedAttribute(node, attrib);
8928 break;
8929 }
8930 }
8931 else
8932 {
8933 this.core.ParseExtensionAttribute(node, attrib);
8934 }
8935 }
8936
8937 if (null == file)
8938 {
8939 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "File"));
8940 }
8941
8942 foreach (XElement child in node.Elements())
8943 {
8944 if (CompilerCore.WixNamespace == child.Name.Namespace)
8945 {
8946 switch (child.Name.LocalName)
8947 {
8948 case "ProtectRange":
8949 this.ParseRangeElement(child, ref protectOffsets, ref protectLengths);
8950 break;
8951 default:
8952 this.core.UnexpectedElement(node, child);
8953 break;
8954 }
8955 }
8956 else
8957 {
8958 this.core.ParseExtensionElement(node, child);
8959 }
8960 }
8961
8962 if (null == protectOffsets || null == protectLengths)
8963 {
8964 this.core.OnMessage(WixErrors.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "ProtectRange"));
8965 }
8966
8967 if (!this.core.EncounteredError)
8968 {
8969 Row row = this.core.CreateRow(sourceLineNumbers, "FamilyFileRanges");
8970 row[0] = family;
8971 row[1] = file;
8972 row[2] = protectOffsets;
8973 row[3] = protectLengths;
8974 }
8975 }
8976
8977 /// <summary>
8978 /// Parses a range element (ProtectRange, IgnoreRange, etc).
8979 /// </summary>
8980 /// <param name="node">The element to parse.</param>
8981 /// <param name="offsets">Reference to the offsets string.</param>
8982 /// <param name="lengths">Reference to the lengths string.</param>
8983 private void ParseRangeElement(XElement node, ref string offsets, ref string lengths)
8984 {
8985 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
8986 string length = null;
8987 string offset = null;
8988
8989 foreach (XAttribute attrib in node.Attributes())
8990 {
8991 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
8992 {
8993 switch (attrib.Name.LocalName)
8994 {
8995 case "Length":
8996 length = this.core.GetAttributeValue(sourceLineNumbers, attrib);
8997 break;
8998 case "Offset":
8999 offset = this.core.GetAttributeValue(sourceLineNumbers, attrib);
9000 break;
9001 default:
9002 this.core.UnexpectedAttribute(node, attrib);
9003 break;
9004 }
9005 }
9006 else
9007 {
9008 this.core.ParseExtensionAttribute(node, attrib);
9009 }
9010 }
9011
9012 if (null == length)
9013 {
9014 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Length"));
9015 }
9016
9017 if (null == offset)
9018 {
9019 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Offset"));
9020 }
9021
9022 this.core.ParseForExtensionElements(node);
9023
9024 if (null != lengths)
9025 {
9026 lengths = String.Concat(lengths, ",", length);
9027 }
9028 else
9029 {
9030 lengths = length;
9031 }
9032
9033 if (null != offsets)
9034 {
9035 offsets = String.Concat(offsets, ",", offset);
9036 }
9037 else
9038 {
9039 offsets = offset;
9040 }
9041 }
9042
9043 /// <summary>
9044 /// Parses a patch property element.
9045 /// </summary>
9046 /// <param name="node">The element to parse.</param>
9047 /// <param name="patch">True if parsing an patch element.</param>
9048 private void ParsePatchPropertyElement(XElement node, bool patch)
9049 {
9050 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
9051 string name = null;
9052 string company = null;
9053 string value = null;
9054
9055 foreach (XAttribute attrib in node.Attributes())
9056 {
9057 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
9058 {
9059 switch (attrib.Name.LocalName)
9060 {
9061 case "Id":
9062 case "Name":
9063 name = this.core.GetAttributeValue(sourceLineNumbers, attrib);
9064 break;
9065 case "Company":
9066 company = this.core.GetAttributeValue(sourceLineNumbers, attrib);
9067 break;
9068 case "Value":
9069 value = this.core.GetAttributeValue(sourceLineNumbers, attrib);
9070 break;
9071 default:
9072 this.core.UnexpectedAttribute(node, attrib);
9073 break;
9074 }
9075 }
9076 else
9077 {
9078 this.core.ParseExtensionAttribute(node, attrib);
9079 }
9080 }
9081
9082 if (null == name)
9083 {
9084 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
9085 }
9086
9087 if (null == value)
9088 {
9089 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value"));
9090 }
9091
9092 this.core.ParseForExtensionElements(node);
9093
9094 if (patch)
9095 {
9096 // /Patch/PatchProperty goes directly into MsiPatchMetadata table
9097 Row row = this.core.CreateRow(sourceLineNumbers, "MsiPatchMetadata");
9098 row[0] = company;
9099 row[1] = name;
9100 row[2] = value;
9101 }
9102 else
9103 {
9104 if (null != company)
9105 {
9106 this.core.OnMessage(WixErrors.UnexpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Company"));
9107 }
9108 this.ProcessProperties(sourceLineNumbers, name, value);
9109 }
9110 }
9111
9112 /// <summary>
9113 /// Parses a patch sequence element.
9114 /// </summary>
9115 /// <param name="node">The element to parse.</param>
9116 private void ParsePatchSequenceElement(XElement node)
9117 {
9118 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
9119 string family = null;
9120 string target = null;
9121 string sequence = null;
9122 int attributes = 0;
9123
9124 foreach (XAttribute attrib in node.Attributes())
9125 {
9126 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
9127 {
9128 switch (attrib.Name.LocalName)
9129 {
9130 case "PatchFamily":
9131 family = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
9132 break;
9133 case "ProductCode":
9134 if (null != target)
9135 {
9136 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Target", "TargetImage"));
9137 }
9138 target = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
9139 break;
9140 case "Target":
9141 if (null != target)
9142 {
9143 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "TargetImage", "ProductCode"));
9144 }
9145 this.core.OnMessage(WixWarnings.DeprecatedPatchSequenceTargetAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName));
9146 target = this.core.GetAttributeValue(sourceLineNumbers, attrib);
9147 break;
9148 case "TargetImage":
9149 if (null != target)
9150 {
9151 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Target", "ProductCode"));
9152 }
9153 target = this.core.GetAttributeValue(sourceLineNumbers, attrib);
9154 this.core.CreateSimpleReference(sourceLineNumbers, "TargetImages", target);
9155 break;
9156 case "Sequence":
9157 sequence = this.core.GetAttributeVersionValue(sourceLineNumbers, attrib);
9158 break;
9159 case "Supersede":
9160 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
9161 {
9162 attributes |= 0x1;
9163 }
9164 break;
9165 default:
9166 this.core.UnexpectedAttribute(node, attrib);
9167 break;
9168 }
9169 }
9170 else
9171 {
9172 this.core.ParseExtensionAttribute(node, attrib);
9173 }
9174 }
9175
9176 if (null == family)
9177 {
9178 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "PatchFamily"));
9179 }
9180
9181 this.core.ParseForExtensionElements(node);
9182
9183 if (!this.core.EncounteredError)
9184 {
9185 Row row = this.core.CreateRow(sourceLineNumbers, "PatchSequence");
9186 row[0] = family;
9187 row[1] = target;
9188 if (!String.IsNullOrEmpty(sequence))
9189 {
9190 row[2] = sequence;
9191 }
9192 row[3] = attributes;
9193 }
9194 }
9195
9196 /// <summary>
9197 /// Parses a TargetProductCode element.
9198 /// </summary>
9199 /// <param name="node">The element to parse.</param>
9200 /// <returns>The id from the node.</returns>
9201 private string ParseTargetProductCodeElement(XElement node)
9202 {
9203 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
9204 string id = null;
9205
9206 foreach (XAttribute attrib in node.Attributes())
9207 {
9208 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
9209 {
9210 switch (attrib.Name.LocalName)
9211 {
9212 case "Id":
9213 id = this.core.GetAttributeValue(sourceLineNumbers, attrib);
9214 if (id.Length > 0 && "*" != id)
9215 {
9216 id = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
9217 }
9218 break;
9219 default:
9220 this.core.UnexpectedAttribute(node, attrib);
9221 break;
9222 }
9223 }
9224 else
9225 {
9226 this.core.ParseExtensionAttribute(node, attrib);
9227 }
9228 }
9229
9230 if (null == id)
9231 {
9232 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
9233 }
9234
9235 this.core.ParseForExtensionElements(node);
9236
9237 return id;
9238 }
9239
9240 /// <summary>
9241 /// Parses a TargetProductCodes element.
9242 /// </summary>
9243 /// <param name="node">The element to parse.</param>
9244 private void ParseTargetProductCodesElement(XElement node)
9245 {
9246 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
9247 bool replace = false;
9248 List<string> targetProductCodes = new List<string>();
9249
9250 foreach (XAttribute attrib in node.Attributes())
9251 {
9252 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
9253 {
9254 switch (attrib.Name.LocalName)
9255 {
9256 case "Replace":
9257 replace = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
9258 break;
9259 default:
9260 this.core.UnexpectedAttribute(node, attrib);
9261 break;
9262 }
9263 }
9264 else
9265 {
9266 this.core.ParseExtensionAttribute(node, attrib);
9267 }
9268 }
9269
9270 foreach (XElement child in node.Elements())
9271 {
9272 if (CompilerCore.WixNamespace == child.Name.Namespace)
9273 {
9274 switch (child.Name.LocalName)
9275 {
9276 case "TargetProductCode":
9277 string id = this.ParseTargetProductCodeElement(child);
9278 if (0 == String.CompareOrdinal("*", id))
9279 {
9280 this.core.OnMessage(WixErrors.IllegalAttributeValueWhenNested(sourceLineNumbers, child.Name.LocalName, "Id", id, node.Name.LocalName));
9281 }
9282 else
9283 {
9284 targetProductCodes.Add(id);
9285 }
9286 break;
9287 default:
9288 this.core.UnexpectedElement(node, child);
9289 break;
9290 }
9291 }
9292 else
9293 {
9294 this.core.ParseExtensionElement(node, child);
9295 }
9296 }
9297
9298 if (!this.core.EncounteredError)
9299 {
9300 // By default, target ProductCodes should be added.
9301 if (!replace)
9302 {
9303 Row row = this.core.CreateRow(sourceLineNumbers, "WixPatchTarget");
9304 row[0] = "*";
9305 }
9306
9307 foreach (string targetProductCode in targetProductCodes)
9308 {
9309 Row row = this.core.CreateRow(sourceLineNumbers, "WixPatchTarget");
9310 row[0] = targetProductCode;
9311 }
9312 }
9313 }
9314
9315 /// <summary>
9316 /// Parses a ReplacePatch element.
9317 /// </summary>
9318 /// <param name="node">The element to parse.</param>
9319 /// <returns>The id from the node.</returns>
9320 private string ParseReplacePatchElement(XElement node)
9321 {
9322 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
9323 string id = null;
9324
9325 foreach (XAttribute attrib in node.Attributes())
9326 {
9327 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
9328 {
9329 switch (attrib.Name.LocalName)
9330 {
9331 case "Id":
9332 id = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
9333 break;
9334 default:
9335 this.core.UnexpectedAttribute(node, attrib);
9336 break;
9337 }
9338 }
9339 else
9340 {
9341 this.core.ParseExtensionAttribute(node, attrib);
9342 }
9343 }
9344
9345 if (null == id)
9346 {
9347 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
9348 }
9349
9350 this.core.ParseForExtensionElements(node);
9351
9352 return id;
9353 }
9354
9355 /// <summary>
9356 /// Parses a symbol path element.
9357 /// </summary>
9358 /// <param name="node">The element to parse.</param>
9359 /// <returns>The path from the node.</returns>
9360 private string ParseSymbolPathElement(XElement node)
9361 {
9362 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
9363 string path = null;
9364
9365 foreach (XAttribute attrib in node.Attributes())
9366 {
9367 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
9368 {
9369 switch (attrib.Name.LocalName)
9370 {
9371 case "Path":
9372 path = this.core.GetAttributeValue(sourceLineNumbers, attrib);
9373 break;
9374 default:
9375 this.core.UnexpectedAttribute(node, attrib);
9376 break;
9377 }
9378 }
9379 else
9380 {
9381 this.core.ParseExtensionAttribute(node, attrib);
9382 }
9383 }
9384
9385 if (null == path)
9386 {
9387 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Path"));
9388 }
9389
9390 this.core.ParseForExtensionElements(node);
9391
9392 return path;
9393 }
9394
9395 /// <summary>
9396 /// Parses an patch element.
9397 /// </summary>
9398 /// <param name="node">The element to parse.</param>
9399 private void ParsePatchElement(XElement node)
9400 {
9401 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
9402 string patchId = null;
9403 int codepage = 0;
9404 ////bool versionMismatches = false;
9405 ////bool productMismatches = false;
9406 bool allowRemoval = false;
9407 string classification = null;
9408 string clientPatchId = null;
9409 string description = null;
9410 string displayName = null;
9411 string comments = null;
9412 string manufacturer = null;
9413 YesNoType minorUpdateTargetRTM = YesNoType.NotSet;
9414 string moreInfoUrl = null;
9415 int optimizeCA = CompilerConstants.IntegerNotSet;
9416 YesNoType optimizedInstallMode = YesNoType.NotSet;
9417 string targetProductName = null;
9418 // string replaceGuids = String.Empty;
9419 int apiPatchingSymbolFlags = 0;
9420 bool optimizePatchSizeForLargeFiles = false;
9421
9422 foreach (XAttribute attrib in node.Attributes())
9423 {
9424 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
9425 {
9426 switch (attrib.Name.LocalName)
9427 {
9428 case "Id":
9429 patchId = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, true);
9430 break;
9431 case "Codepage":
9432 codepage = this.core.GetAttributeCodePageValue(sourceLineNumbers, attrib);
9433 break;
9434 case "AllowMajorVersionMismatches":
9435 ////versionMismatches = (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib));
9436 break;
9437 case "AllowProductCodeMismatches":
9438 ////productMismatches = (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib));
9439 break;
9440 case "AllowRemoval":
9441 allowRemoval = (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib));
9442 break;
9443 case "Classification":
9444 classification = this.core.GetAttributeValue(sourceLineNumbers, attrib);
9445 break;
9446 case "ClientPatchId":
9447 clientPatchId = this.core.GetAttributeValue(sourceLineNumbers, attrib);
9448 break;
9449 case "Description":
9450 description = this.core.GetAttributeValue(sourceLineNumbers, attrib);
9451 break;
9452 case "DisplayName":
9453 displayName = this.core.GetAttributeValue(sourceLineNumbers, attrib);
9454 break;
9455 case "Comments":
9456 comments = this.core.GetAttributeValue(sourceLineNumbers, attrib);
9457 break;
9458 case "Manufacturer":
9459 manufacturer = this.core.GetAttributeValue(sourceLineNumbers, attrib);
9460 break;
9461 case "MinorUpdateTargetRTM":
9462 minorUpdateTargetRTM = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
9463 break;
9464 case "MoreInfoURL":
9465 moreInfoUrl = this.core.GetAttributeValue(sourceLineNumbers, attrib);
9466 break;
9467 case "OptimizedInstallMode":
9468 optimizedInstallMode = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
9469 break;
9470 case "TargetProductName":
9471 targetProductName = this.core.GetAttributeValue(sourceLineNumbers, attrib);
9472 break;
9473 case "ApiPatchingSymbolNoImagehlpFlag":
9474 apiPatchingSymbolFlags |= (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? (int)PatchAPI.PatchInterop.PatchSymbolFlagsType.PATCH_SYMBOL_NO_IMAGEHLP : 0;
9475 break;
9476 case "ApiPatchingSymbolNoFailuresFlag":
9477 apiPatchingSymbolFlags |= (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? (int)PatchAPI.PatchInterop.PatchSymbolFlagsType.PATCH_SYMBOL_NO_FAILURES : 0;
9478 break;
9479 case "ApiPatchingSymbolUndecoratedTooFlag":
9480 apiPatchingSymbolFlags |= (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? (int)PatchAPI.PatchInterop.PatchSymbolFlagsType.PATCH_SYMBOL_UNDECORATED_TOO : 0;
9481 break;
9482 case "OptimizePatchSizeForLargeFiles":
9483 optimizePatchSizeForLargeFiles = (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib));
9484 break;
9485 default:
9486 this.core.UnexpectedAttribute(node, attrib);
9487 break;
9488 }
9489 }
9490 else
9491 {
9492 this.core.ParseExtensionAttribute(node, attrib);
9493 }
9494 }
9495
9496 if (patchId == null || patchId == "*")
9497 {
9498 // auto-generate at compile time, since this value gets dispersed to several locations
9499 patchId = Common.GenerateGuid();
9500 }
9501 this.activeName = patchId;
9502
9503 if (null == this.activeName)
9504 {
9505 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
9506 }
9507 if (null == classification)
9508 {
9509 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Classification"));
9510 }
9511 if (null == clientPatchId)
9512 {
9513 clientPatchId = String.Concat("_", new Guid(patchId).ToString("N", CultureInfo.InvariantCulture).ToUpper(CultureInfo.InvariantCulture));
9514 }
9515 if (null == description)
9516 {
9517 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Description"));
9518 }
9519 if (null == displayName)
9520 {
9521 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisplayName"));
9522 }
9523 if (null == manufacturer)
9524 {
9525 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Manufacturer"));
9526 }
9527
9528 this.core.CreateActiveSection(this.activeName, SectionType.Patch, codepage);
9529
9530 foreach (XElement child in node.Elements())
9531 {
9532 if (CompilerCore.WixNamespace == child.Name.Namespace)
9533 {
9534 switch (child.Name.LocalName)
9535 {
9536 case "PatchInformation":
9537 this.ParsePatchInformationElement(child);
9538 break;
9539 case "Media":
9540 this.ParseMediaElement(child, patchId);
9541 break;
9542 case "OptimizeCustomActions":
9543 optimizeCA = this.ParseOptimizeCustomActionsElement(child);
9544 break;
9545 case "PatchFamily":
9546 this.ParsePatchFamilyElement(child, ComplexReferenceParentType.Patch, patchId);
9547 break;
9548 case "PatchFamilyRef":
9549 this.ParsePatchFamilyRefElement(child, ComplexReferenceParentType.Patch, patchId);
9550 break;
9551 case "PatchFamilyGroup":
9552 this.ParsePatchFamilyGroupElement(child, ComplexReferenceParentType.Patch, patchId);
9553 break;
9554 case "PatchFamilyGroupRef":
9555 this.ParsePatchFamilyGroupRefElement(child, ComplexReferenceParentType.Patch, patchId);
9556 break;
9557 case "PatchProperty":
9558 this.ParsePatchPropertyElement(child, true);
9559 break;
9560 case "TargetProductCodes":
9561 this.ParseTargetProductCodesElement(child);
9562 break;
9563 default:
9564 this.core.UnexpectedElement(node, child);
9565 break;
9566 }
9567 }
9568 else
9569 {
9570 this.core.ParseExtensionElement(node, child);
9571 }
9572 }
9573
9574
9575 if (!this.core.EncounteredError)
9576 {
9577 Row patchIdRow = this.core.CreateRow(sourceLineNumbers, "WixPatchId");
9578 patchIdRow[0] = patchId;
9579 patchIdRow[1] = clientPatchId;
9580 patchIdRow[2] = optimizePatchSizeForLargeFiles ? 1 : 0;
9581 patchIdRow[3] = apiPatchingSymbolFlags;
9582
9583 if (allowRemoval)
9584 {
9585 Row row = this.core.CreateRow(sourceLineNumbers, "MsiPatchMetadata");
9586 row[0] = null;
9587 row[1] = "AllowRemoval";
9588 row[2] = allowRemoval ? "1" : "0";
9589 }
9590
9591 if (null != classification)
9592 {
9593 Row row = this.core.CreateRow(sourceLineNumbers, "MsiPatchMetadata");
9594 row[0] = null;
9595 row[1] = "Classification";
9596 row[2] = classification;
9597 }
9598
9599 // always generate the CreationTimeUTC
9600 {
9601 Row row = this.core.CreateRow(sourceLineNumbers, "MsiPatchMetadata");
9602 row[0] = null;
9603 row[1] = "CreationTimeUTC";
9604 row[2] = DateTime.UtcNow.ToString("MM-dd-yy HH:mm", CultureInfo.InvariantCulture);
9605 }
9606
9607 if (null != description)
9608 {
9609 Row row = this.core.CreateRow(sourceLineNumbers, "MsiPatchMetadata");
9610 row[0] = null;
9611 row[1] = "Description";
9612 row[2] = description;
9613 }
9614
9615 if (null != displayName)
9616 {
9617 Row row = this.core.CreateRow(sourceLineNumbers, "MsiPatchMetadata");
9618 row[0] = null;
9619 row[1] = "DisplayName";
9620 row[2] = displayName;
9621 }
9622
9623 if (null != manufacturer)
9624 {
9625 Row row = this.core.CreateRow(sourceLineNumbers, "MsiPatchMetadata");
9626 row[0] = null;
9627 row[1] = "ManufacturerName";
9628 row[2] = manufacturer;
9629 }
9630
9631 if (YesNoType.NotSet != minorUpdateTargetRTM)
9632 {
9633 Row row = this.core.CreateRow(sourceLineNumbers, "MsiPatchMetadata");
9634 row[0] = null;
9635 row[1] = "MinorUpdateTargetRTM";
9636 row[2] = YesNoType.Yes == minorUpdateTargetRTM ? "1" : "0";
9637 }
9638
9639 if (null != moreInfoUrl)
9640 {
9641 Row row = this.core.CreateRow(sourceLineNumbers, "MsiPatchMetadata");
9642 row[0] = null;
9643 row[1] = "MoreInfoURL";
9644 row[2] = moreInfoUrl;
9645 }
9646
9647 if (CompilerConstants.IntegerNotSet != optimizeCA)
9648 {
9649 Row row = this.core.CreateRow(sourceLineNumbers, "MsiPatchMetadata");
9650 row[0] = null;
9651 row[1] = "OptimizeCA";
9652 row[2] = optimizeCA.ToString(CultureInfo.InvariantCulture);
9653 }
9654
9655 if (YesNoType.NotSet != optimizedInstallMode)
9656 {
9657 Row row = this.core.CreateRow(sourceLineNumbers, "MsiPatchMetadata");
9658 row[0] = null;
9659 row[1] = "OptimizedInstallMode";
9660 row[2] = YesNoType.Yes == optimizedInstallMode ? "1" : "0";
9661 }
9662
9663 if (null != targetProductName)
9664 {
9665 Row row = this.core.CreateRow(sourceLineNumbers, "MsiPatchMetadata");
9666 row[0] = null;
9667 row[1] = "TargetProductName";
9668 row[2] = targetProductName;
9669 }
9670
9671 if (null != comments)
9672 {
9673 Row row = this.core.CreateRow(sourceLineNumbers, "WixPatchMetadata");
9674 row[0] = "Comments";
9675 row[1] = comments;
9676 }
9677 }
9678 // TODO: do something with versionMismatches and productMismatches
9679 }
9680
9681 /// <summary>
9682 /// Parses a PatchFamily element.
9683 /// </summary>
9684 /// <param name="node">The element to parse.</param>
9685 private void ParsePatchFamilyElement(XElement node, ComplexReferenceParentType parentType, string parentId)
9686 {
9687 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
9688 Identifier id = null;
9689 string productCode = null;
9690 string version = null;
9691 int attributes = 0;
9692
9693 foreach (XAttribute attrib in node.Attributes())
9694 {
9695 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
9696 {
9697 switch (attrib.Name.LocalName)
9698 {
9699 case "Id":
9700 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
9701 break;
9702 case "ProductCode":
9703 productCode = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
9704 break;
9705 case "Version":
9706 version = this.core.GetAttributeVersionValue(sourceLineNumbers, attrib);
9707 break;
9708 case "Supersede":
9709 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
9710 {
9711 attributes |= 0x1;
9712 }
9713 break;
9714 default:
9715 this.core.UnexpectedAttribute(node, attrib);
9716 break;
9717 }
9718 }
9719 else
9720 {
9721 this.core.ParseExtensionAttribute(node, attrib);
9722 }
9723 }
9724
9725 if (null == id)
9726 {
9727 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
9728 id = Identifier.Invalid;
9729 }
9730
9731 if (String.IsNullOrEmpty(version))
9732 {
9733 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version"));
9734 }
9735 else if (!CompilerCore.IsValidProductVersion(version))
9736 {
9737 this.core.OnMessage(WixErrors.InvalidProductVersion(sourceLineNumbers, version));
9738 }
9739
9740 // find unexpected child elements
9741 foreach (XElement child in node.Elements())
9742 {
9743 if (CompilerCore.WixNamespace == child.Name.Namespace)
9744 {
9745 switch (child.Name.LocalName)
9746 {
9747 case "All":
9748 this.ParseAllElement(child);
9749 break;
9750 case "BinaryRef":
9751 this.ParsePatchChildRefElement(child, "Binary");
9752 break;
9753 case "ComponentRef":
9754 this.ParsePatchChildRefElement(child, "Component");
9755 break;
9756 case "CustomActionRef":
9757 this.ParsePatchChildRefElement(child, "CustomAction");
9758 break;
9759 case "DirectoryRef":
9760 this.ParsePatchChildRefElement(child, "Directory");
9761 break;
9762 case "DigitalCertificateRef":
9763 this.ParsePatchChildRefElement(child, "MsiDigitalCertificate");
9764 break;
9765 case "FeatureRef":
9766 this.ParsePatchChildRefElement(child, "Feature");
9767 break;
9768 case "IconRef":
9769 this.ParsePatchChildRefElement(child, "Icon");
9770 break;
9771 case "PropertyRef":
9772 this.ParsePatchChildRefElement(child, "Property");
9773 break;
9774 case "UIRef":
9775 this.ParsePatchChildRefElement(child, "WixUI");
9776 break;
9777 default:
9778 this.core.UnexpectedElement(node, child);
9779 break;
9780 }
9781 }
9782 else
9783 {
9784 this.core.ParseExtensionElement(node, child);
9785 }
9786 }
9787
9788
9789 if (!this.core.EncounteredError)
9790 {
9791 Row row = this.core.CreateRow(sourceLineNumbers, "MsiPatchSequence", id);
9792 row[1] = productCode;
9793 row[2] = version;
9794 row[3] = attributes;
9795
9796 if (ComplexReferenceParentType.Unknown != parentType)
9797 {
9798 this.core.CreateComplexReference(sourceLineNumbers, parentType, parentId, null, ComplexReferenceChildType.PatchFamily, id.Id, ComplexReferenceParentType.Patch == parentType);
9799 }
9800 }
9801 }
9802
9803 /// <summary>
9804 /// Parses the All element under a PatchFamily.
9805 /// </summary>
9806 /// <param name="node">The element to parse.</param>
9807 private void ParseAllElement(XElement node)
9808 {
9809 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
9810
9811 // find unexpected attributes
9812 foreach (XAttribute attrib in node.Attributes())
9813 {
9814 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
9815 {
9816 this.core.UnexpectedAttribute(node, attrib);
9817 }
9818 else
9819 {
9820 this.core.ParseExtensionAttribute(node, attrib);
9821 }
9822 }
9823
9824 this.core.ParseForExtensionElements(node);
9825
9826 // Always warn when using the All element.
9827 this.core.OnMessage(WixWarnings.AllChangesIncludedInPatch(sourceLineNumbers));
9828
9829 if (!this.core.EncounteredError)
9830 {
9831 this.core.CreatePatchFamilyChildReference(sourceLineNumbers, "*", "*");
9832 }
9833 }
9834
9835 /// <summary>
9836 /// Parses all reference elements under a PatchFamily.
9837 /// </summary>
9838 /// <param name="node">The element to parse.</param>
9839 /// <param name="tableName">Table that reference was made to.</param>
9840 private void ParsePatchChildRefElement(XElement node, string tableName)
9841 {
9842 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
9843 string id = null;
9844
9845 foreach (XAttribute attrib in node.Attributes())
9846 {
9847 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
9848 {
9849 switch (attrib.Name.LocalName)
9850 {
9851 case "Id":
9852 id = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
9853 break;
9854 default:
9855 this.core.UnexpectedAttribute(node, attrib);
9856 break;
9857 }
9858 }
9859 else
9860 {
9861 this.core.ParseExtensionAttribute(node, attrib);
9862 }
9863 }
9864
9865 if (null == id)
9866 {
9867 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
9868 }
9869
9870 this.core.ParseForExtensionElements(node);
9871
9872 if (!this.core.EncounteredError)
9873 {
9874 this.core.CreatePatchFamilyChildReference(sourceLineNumbers, tableName, id);
9875 }
9876 }
9877
9878 /// <summary>
9879 /// Parses a PatchBaseline element.
9880 /// </summary>
9881 /// <param name="node">The element to parse.</param>
9882 /// <param name="diskId">Media index from parent element.</param>
9883 private void ParsePatchBaselineElement(XElement node, int diskId)
9884 {
9885 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
9886 Identifier id = null;
9887 bool parsedValidate = false;
9888 TransformFlags validationFlags = TransformFlags.PatchTransformDefault;
9889
9890 foreach (XAttribute attrib in node.Attributes())
9891 {
9892 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
9893 {
9894 switch (attrib.Name.LocalName)
9895 {
9896 case "Id":
9897 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
9898 break;
9899 default:
9900 this.core.UnexpectedAttribute(node, attrib);
9901 break;
9902 }
9903 }
9904 else
9905 {
9906 this.core.ParseExtensionAttribute(node, attrib);
9907 }
9908 }
9909
9910 if (null == id)
9911 {
9912 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
9913 id = Identifier.Invalid;
9914 }
9915 else if (27 < id.Id.Length)
9916 {
9917 this.core.OnMessage(WixErrors.IdentifierTooLongError(sourceLineNumbers, node.Name.LocalName, "Id", id.Id, 27));
9918 }
9919
9920 foreach (XElement child in node.Elements())
9921 {
9922 if (CompilerCore.WixNamespace == child.Name.Namespace)
9923 {
9924 switch (child.Name.LocalName)
9925 {
9926 case "Validate":
9927 if (parsedValidate)
9928 {
9929 SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
9930 this.core.OnMessage(WixErrors.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, child.Name.LocalName));
9931 }
9932 else
9933 {
9934 this.ParseValidateElement(child, ref validationFlags);
9935 parsedValidate = true;
9936 }
9937 break;
9938 default:
9939 this.core.UnexpectedElement(node, child);
9940 break;
9941 }
9942 }
9943 else
9944 {
9945 this.core.ParseExtensionElement(node, child);
9946 }
9947 }
9948
9949 if (!this.core.EncounteredError)
9950 {
9951 Row row = this.core.CreateRow(sourceLineNumbers, "WixPatchBaseline", id);
9952 row[1] = diskId;
9953 row[2] = (int)validationFlags;
9954 }
9955 }
9956
9957 /// <summary>
9958 /// Parses a Validate element.
9959 /// </summary>
9960 /// <param name="node">The element to parse.</param>
9961 /// <param name="validationFlags">TransformValidation flags to use when creating the authoring patch transform.</param>
9962 private void ParseValidateElement(XElement node, ref TransformFlags validationFlags)
9963 {
9964 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
9965
9966 foreach (XAttribute attrib in node.Attributes())
9967 {
9968 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
9969 {
9970 switch (attrib.Name.LocalName)
9971 {
9972 case "ProductId":
9973 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
9974 {
9975 validationFlags |= TransformFlags.ValidateProduct;
9976 }
9977 else
9978 {
9979 validationFlags &= ~TransformFlags.ValidateProduct;
9980 }
9981 break;
9982 case "ProductLanguage":
9983 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
9984 {
9985 validationFlags |= TransformFlags.ValidateLanguage;
9986 }
9987 else
9988 {
9989 validationFlags &= ~TransformFlags.ValidateLanguage;
9990 }
9991 break;
9992 case "ProductVersion":
9993 string check = this.core.GetAttributeValue(sourceLineNumbers, attrib);
9994 validationFlags &= ~TransformFlags.ProductVersionMask;
9995 Wix.Validate.ProductVersionType productVersionType = Wix.Validate.ParseProductVersionType(check);
9996 switch (productVersionType)
9997 {
9998 case Wix.Validate.ProductVersionType.Major:
9999 validationFlags |= TransformFlags.ValidateMajorVersion;
10000 break;
10001 case Wix.Validate.ProductVersionType.Minor:
10002 validationFlags |= TransformFlags.ValidateMinorVersion;
10003 break;
10004 case Wix.Validate.ProductVersionType.Update:
10005 validationFlags |= TransformFlags.ValidateUpdateVersion;
10006 break;
10007 default:
10008 this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Version", check, "Major", "Minor", "Update"));
10009 break;
10010 }
10011 break;
10012 case "ProductVersionOperator":
10013 string op = this.core.GetAttributeValue(sourceLineNumbers, attrib);
10014 validationFlags &= ~TransformFlags.ProductVersionOperatorMask;
10015 Wix.Validate.ProductVersionOperatorType opType = Wix.Validate.ParseProductVersionOperatorType(op);
10016 switch (opType)
10017 {
10018 case Wix.Validate.ProductVersionOperatorType.Lesser:
10019 validationFlags |= TransformFlags.ValidateNewLessBaseVersion;
10020 break;
10021 case Wix.Validate.ProductVersionOperatorType.LesserOrEqual:
10022 validationFlags |= TransformFlags.ValidateNewLessEqualBaseVersion;
10023 break;
10024 case Wix.Validate.ProductVersionOperatorType.Equal:
10025 validationFlags |= TransformFlags.ValidateNewEqualBaseVersion;
10026 break;
10027 case Wix.Validate.ProductVersionOperatorType.GreaterOrEqual:
10028 validationFlags |= TransformFlags.ValidateNewGreaterEqualBaseVersion;
10029 break;
10030 case Wix.Validate.ProductVersionOperatorType.Greater:
10031 validationFlags |= TransformFlags.ValidateNewGreaterBaseVersion;
10032 break;
10033 default:
10034 this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Operator", op, "Lesser", "LesserOrEqual", "Equal", "GreaterOrEqual", "Greater"));
10035 break;
10036 }
10037 break;
10038 case "UpgradeCode":
10039 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
10040 {
10041 validationFlags |= TransformFlags.ValidateUpgradeCode;
10042 }
10043 else
10044 {
10045 validationFlags &= ~TransformFlags.ValidateUpgradeCode;
10046 }
10047 break;
10048 case "IgnoreAddExistingRow":
10049 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
10050 {
10051 validationFlags |= TransformFlags.ErrorAddExistingRow;
10052 }
10053 else
10054 {
10055 validationFlags &= ~TransformFlags.ErrorAddExistingRow;
10056 }
10057 break;
10058 case "IgnoreAddExistingTable":
10059 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
10060 {
10061 validationFlags |= TransformFlags.ErrorAddExistingTable;
10062 }
10063 else
10064 {
10065 validationFlags &= ~TransformFlags.ErrorAddExistingTable;
10066 }
10067 break;
10068 case "IgnoreDeleteMissingRow":
10069 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
10070 {
10071 validationFlags |= TransformFlags.ErrorDeleteMissingRow;
10072 }
10073 else
10074 {
10075 validationFlags &= ~TransformFlags.ErrorDeleteMissingRow;
10076 }
10077 break;
10078 case "IgnoreDeleteMissingTable":
10079 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
10080 {
10081 validationFlags |= TransformFlags.ErrorDeleteMissingTable;
10082 }
10083 else
10084 {
10085 validationFlags &= ~TransformFlags.ErrorDeleteMissingTable;
10086 }
10087 break;
10088 case "IgnoreUpdateMissingRow":
10089 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
10090 {
10091 validationFlags |= TransformFlags.ErrorUpdateMissingRow;
10092 }
10093 else
10094 {
10095 validationFlags &= ~TransformFlags.ErrorUpdateMissingRow;
10096 }
10097 break;
10098 case "IgnoreChangingCodePage":
10099 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
10100 {
10101 validationFlags |= TransformFlags.ErrorChangeCodePage;
10102 }
10103 else
10104 {
10105 validationFlags &= ~TransformFlags.ErrorChangeCodePage;
10106 }
10107 break;
10108 default:
10109 this.core.UnexpectedAttribute(node, attrib);
10110 break;
10111 }
10112 }
10113 else
10114 {
10115 this.core.ParseExtensionAttribute(node, attrib);
10116 }
10117 }
10118
10119 }
10120
10121 /// <summary>
10122 /// Adds a row to the properties table.
10123 /// </summary>
10124 /// <param name="sourceLineNumbers">Source line numbers.</param>
10125 /// <param name="name">Name of the property.</param>
10126 /// <param name="value">Value of the property.</param>
10127 private void ProcessProperties(SourceLineNumber sourceLineNumbers, string name, string value)
10128 {
10129 if (!this.core.EncounteredError)
10130 {
10131 Row row = this.core.CreateRow(sourceLineNumbers, "Properties");
10132 row[0] = name;
10133 row[1] = value;
10134 }
10135 }
10136
10137 /// <summary>
10138 /// Parses a dependency element.
10139 /// </summary>
10140 /// <param name="node">Element to parse.</param>
10141 private void ParseDependencyElement(XElement node)
10142 {
10143 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
10144 string requiredId = null;
10145 int requiredLanguage = CompilerConstants.IntegerNotSet;
10146 string requiredVersion = null;
10147
10148 foreach (XAttribute attrib in node.Attributes())
10149 {
10150 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
10151 {
10152 switch (attrib.Name.LocalName)
10153 {
10154 case "RequiredId":
10155 requiredId = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
10156 break;
10157 case "RequiredLanguage":
10158 requiredLanguage = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue);
10159 break;
10160 case "RequiredVersion":
10161 requiredVersion = this.core.GetAttributeValue(sourceLineNumbers, attrib);
10162 break;
10163 default:
10164 this.core.UnexpectedAttribute(node, attrib);
10165 break;
10166 }
10167 }
10168 else
10169 {
10170 this.core.ParseExtensionAttribute(node, attrib);
10171 }
10172 }
10173
10174 if (null == requiredId)
10175 {
10176 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "RequiredId"));
10177 requiredId = String.Empty;
10178 }
10179
10180 if (CompilerConstants.IntegerNotSet == requiredLanguage)
10181 {
10182 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "RequiredLanguage"));
10183 requiredLanguage = CompilerConstants.IllegalInteger;
10184 }
10185
10186 this.core.ParseForExtensionElements(node);
10187
10188 if (!this.core.EncounteredError)
10189 {
10190 Row row = this.core.CreateRow(sourceLineNumbers, "ModuleDependency");
10191 row[0] = this.activeName;
10192 row[1] = this.activeLanguage;
10193 row[2] = requiredId;
10194 row[3] = requiredLanguage.ToString(CultureInfo.InvariantCulture);
10195 row[4] = requiredVersion;
10196 }
10197 }
10198
10199 /// <summary>
10200 /// Parses an exclusion element.
10201 /// </summary>
10202 /// <param name="node">Element to parse.</param>
10203 private void ParseExclusionElement(XElement node)
10204 {
10205 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
10206 string excludedId = null;
10207 int excludeExceptLanguage = CompilerConstants.IntegerNotSet;
10208 int excludeLanguage = CompilerConstants.IntegerNotSet;
10209 string excludedLanguageField = "0";
10210 string excludedMaxVersion = null;
10211 string excludedMinVersion = null;
10212
10213 foreach (XAttribute attrib in node.Attributes())
10214 {
10215 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
10216 {
10217 switch (attrib.Name.LocalName)
10218 {
10219 case "ExcludedId":
10220 excludedId = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
10221 break;
10222 case "ExcludeExceptLanguage":
10223 excludeExceptLanguage = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue);
10224 break;
10225 case "ExcludeLanguage":
10226 excludeLanguage = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue);
10227 break;
10228 case "ExcludedMaxVersion":
10229 excludedMaxVersion = this.core.GetAttributeValue(sourceLineNumbers, attrib);
10230 break;
10231 case "ExcludedMinVersion":
10232 excludedMinVersion = this.core.GetAttributeValue(sourceLineNumbers, attrib);
10233 break;
10234 default:
10235 this.core.UnexpectedAttribute(node, attrib);
10236 break;
10237 }
10238 }
10239 else
10240 {
10241 this.core.ParseExtensionAttribute(node, attrib);
10242 }
10243 }
10244
10245 if (null == excludedId)
10246 {
10247 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ExcludedId"));
10248 excludedId = String.Empty;
10249 }
10250
10251 if (CompilerConstants.IntegerNotSet != excludeExceptLanguage && CompilerConstants.IntegerNotSet != excludeLanguage)
10252 {
10253 this.core.OnMessage(WixErrors.IllegalModuleExclusionLanguageAttributes(sourceLineNumbers));
10254 }
10255 else if (CompilerConstants.IntegerNotSet != excludeExceptLanguage)
10256 {
10257 excludedLanguageField = Convert.ToString(-excludeExceptLanguage, CultureInfo.InvariantCulture);
10258 }
10259 else if (CompilerConstants.IntegerNotSet != excludeLanguage)
10260 {
10261 excludedLanguageField = Convert.ToString(excludeLanguage, CultureInfo.InvariantCulture);
10262 }
10263
10264 this.core.ParseForExtensionElements(node);
10265
10266 if (!this.core.EncounteredError)
10267 {
10268 Row row = this.core.CreateRow(sourceLineNumbers, "ModuleExclusion");
10269 row[0] = this.activeName;
10270 row[1] = this.activeLanguage;
10271 row[2] = excludedId;
10272 row[3] = excludedLanguageField;
10273 row[4] = excludedMinVersion;
10274 row[5] = excludedMaxVersion;
10275 }
10276 }
10277
10278 /// <summary>
10279 /// Parses a configuration element for a configurable merge module.
10280 /// </summary>
10281 /// <param name="node">Element to parse.</param>
10282 private void ParseConfigurationElement(XElement node)
10283 {
10284 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
10285 int attributes = 0;
10286 string contextData = null;
10287 string defaultValue = null;
10288 string description = null;
10289 string displayName = null;
10290 int format = CompilerConstants.IntegerNotSet;
10291 string helpKeyword = null;
10292 string helpLocation = null;
10293 string name = null;
10294 string type = null;
10295
10296 foreach (XAttribute attrib in node.Attributes())
10297 {
10298 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
10299 {
10300 switch (attrib.Name.LocalName)
10301 {
10302 case "Name":
10303 name = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
10304 break;
10305 case "ContextData":
10306 contextData = this.core.GetAttributeValue(sourceLineNumbers, attrib);
10307 break;
10308 case "Description":
10309 description = this.core.GetAttributeValue(sourceLineNumbers, attrib);
10310 break;
10311 case "DefaultValue":
10312 defaultValue = this.core.GetAttributeValue(sourceLineNumbers, attrib);
10313 break;
10314 case "DisplayName":
10315 displayName = this.core.GetAttributeValue(sourceLineNumbers, attrib);
10316 break;
10317 case "Format":
10318 string formatStr = this.core.GetAttributeValue(sourceLineNumbers, attrib);
10319 if (0 < formatStr.Length)
10320 {
10321 Wix.Configuration.FormatType formatType = Wix.Configuration.ParseFormatType(formatStr);
10322 switch (formatType)
10323 {
10324 case Wix.Configuration.FormatType.Text:
10325 format = 0;
10326 break;
10327 case Wix.Configuration.FormatType.Key:
10328 format = 1;
10329 break;
10330 case Wix.Configuration.FormatType.Integer:
10331 format = 2;
10332 break;
10333 case Wix.Configuration.FormatType.Bitfield:
10334 format = 3;
10335 break;
10336 default:
10337 this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Format", formatStr, "Text", "Key", "Integer", "Bitfield"));
10338 break;
10339 }
10340 }
10341 break;
10342 case "HelpKeyword":
10343 helpKeyword = this.core.GetAttributeValue(sourceLineNumbers, attrib);
10344 break;
10345 case "HelpLocation":
10346 helpLocation = this.core.GetAttributeValue(sourceLineNumbers, attrib);
10347 break;
10348 case "KeyNoOrphan":
10349 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
10350 {
10351 attributes |= MsiInterop.MsidbMsmConfigurableOptionKeyNoOrphan;
10352 }
10353 break;
10354 case "NonNullable":
10355 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
10356 {
10357 attributes |= MsiInterop.MsidbMsmConfigurableOptionNonNullable;
10358 }
10359 break;
10360 case "Type":
10361 type = this.core.GetAttributeValue(sourceLineNumbers, attrib);
10362 break;
10363 default:
10364 this.core.UnexpectedAttribute(node, attrib);
10365 break;
10366 }
10367 }
10368 else
10369 {
10370 this.core.ParseExtensionAttribute(node, attrib);
10371 }
10372 }
10373
10374 if (null == name)
10375 {
10376 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
10377 name = String.Empty;
10378 }
10379
10380 if (CompilerConstants.IntegerNotSet == format)
10381 {
10382 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Format"));
10383 format = CompilerConstants.IllegalInteger;
10384 }
10385
10386 this.core.ParseForExtensionElements(node);
10387
10388 if (!this.core.EncounteredError)
10389 {
10390 Row row = this.core.CreateRow(sourceLineNumbers, "ModuleConfiguration");
10391 row[0] = name;
10392 row[1] = format;
10393 row[2] = type;
10394 row[3] = contextData;
10395 row[4] = defaultValue;
10396 row[5] = attributes;
10397 row[6] = displayName;
10398 row[7] = description;
10399 row[8] = helpLocation;
10400 row[9] = helpKeyword;
10401 }
10402 }
10403
10404 /// <summary>
10405 /// Parses a substitution element for a configurable merge module.
10406 /// </summary>
10407 /// <param name="node">Element to parse.</param>
10408 private void ParseSubstitutionElement(XElement node)
10409 {
10410 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
10411 string column = null;
10412 string rowKeys = null;
10413 string table = null;
10414 string value = null;
10415
10416 foreach (XAttribute attrib in node.Attributes())
10417 {
10418 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
10419 {
10420 switch (attrib.Name.LocalName)
10421 {
10422 case "Column":
10423 column = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
10424 break;
10425 case "Row":
10426 rowKeys = this.core.GetAttributeValue(sourceLineNumbers, attrib);
10427 break;
10428 case "Table":
10429 table = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
10430 break;
10431 case "Value":
10432 value = this.core.GetAttributeValue(sourceLineNumbers, attrib);
10433 break;
10434 default:
10435 this.core.UnexpectedAttribute(node, attrib);
10436 break;
10437 }
10438 }
10439 else
10440 {
10441 this.core.ParseExtensionAttribute(node, attrib);
10442 }
10443 }
10444
10445 if (null == column)
10446 {
10447 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Column"));
10448 column = String.Empty;
10449 }
10450
10451 if (null == table)
10452 {
10453 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Table"));
10454 table = String.Empty;
10455 }
10456
10457 if (null == rowKeys)
10458 {
10459 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Row"));
10460 }
10461
10462 this.core.ParseForExtensionElements(node);
10463
10464 if (!this.core.EncounteredError)
10465 {
10466 Row row = this.core.CreateRow(sourceLineNumbers, "ModuleSubstitution");
10467 row[0] = table;
10468 row[1] = rowKeys;
10469 row[2] = column;
10470 row[3] = value;
10471 }
10472 }
10473
10474 /// <summary>
10475 /// Parses an IgnoreTable element.
10476 /// </summary>
10477 /// <param name="node">Element to parse.</param>
10478 private void ParseIgnoreTableElement(XElement node)
10479 {
10480 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
10481 string id = null;
10482
10483 foreach (XAttribute attrib in node.Attributes())
10484 {
10485 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
10486 {
10487 switch (attrib.Name.LocalName)
10488 {
10489 case "Id":
10490 id = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
10491 break;
10492 default:
10493 this.core.UnexpectedAttribute(node, attrib);
10494 break;
10495 }
10496 }
10497 else
10498 {
10499 this.core.ParseExtensionAttribute(node, attrib);
10500 }
10501 }
10502
10503 if (null == id)
10504 {
10505 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
10506 }
10507
10508 this.core.ParseForExtensionElements(node);
10509
10510 if (!this.core.EncounteredError)
10511 {
10512 Row row = this.core.CreateRow(sourceLineNumbers, "ModuleIgnoreTable");
10513 row[0] = id;
10514 }
10515 }
10516
10517 /// <summary>
10518 /// Parses an odbc driver or translator element.
10519 /// </summary>
10520 /// <param name="node">Element to parse.</param>
10521 /// <param name="componentId">Identifier of parent component.</param>
10522 /// <param name="fileId">Default identifer for driver/translator file.</param>
10523 /// <param name="table">Table we're processing for.</param>
10524 private void ParseODBCDriverOrTranslator(XElement node, string componentId, string fileId, TableDefinition table)
10525 {
10526 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
10527 Identifier id = null;
10528 string driver = fileId;
10529 string name = null;
10530 string setup = fileId;
10531
10532 foreach (XAttribute attrib in node.Attributes())
10533 {
10534 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
10535 {
10536 switch (attrib.Name.LocalName)
10537 {
10538 case "Id":
10539 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
10540 break;
10541 case "File":
10542 driver = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
10543 this.core.CreateSimpleReference(sourceLineNumbers, "File", driver);
10544 break;
10545 case "Name":
10546 name = this.core.GetAttributeValue(sourceLineNumbers, attrib);
10547 break;
10548 case "SetupFile":
10549 setup = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
10550 this.core.CreateSimpleReference(sourceLineNumbers, "File", setup);
10551 break;
10552 default:
10553 this.core.UnexpectedAttribute(node, attrib);
10554 break;
10555 }
10556 }
10557 else
10558 {
10559 this.core.ParseExtensionAttribute(node, attrib);
10560 }
10561 }
10562
10563 if (null == name)
10564 {
10565 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
10566 }
10567
10568 if (null == id)
10569 {
10570 id = this.core.CreateIdentifier("odb", name, fileId, setup);
10571 }
10572
10573 // drivers have a few possible children
10574 if ("ODBCDriver" == table.Name)
10575 {
10576 // process any data sources for the driver
10577 foreach (XElement child in node.Elements())
10578 {
10579 if (CompilerCore.WixNamespace == child.Name.Namespace)
10580 {
10581 switch (child.Name.LocalName)
10582 {
10583 case "ODBCDataSource":
10584 string ignoredKeyPath = null;
10585 this.ParseODBCDataSource(child, componentId, name, out ignoredKeyPath);
10586 break;
10587 case "Property":
10588 this.ParseODBCProperty(child, id.Id, "ODBCAttribute");
10589 break;
10590 default:
10591 this.core.UnexpectedElement(node, child);
10592 break;
10593 }
10594 }
10595 else
10596 {
10597 this.core.ParseExtensionElement(node, child);
10598 }
10599 }
10600 }
10601 else
10602 {
10603 this.core.ParseForExtensionElements(node);
10604 }
10605
10606 if (!this.core.EncounteredError)
10607 {
10608 Row row = this.core.CreateRow(sourceLineNumbers, table.Name, id);
10609 row[1] = componentId;
10610 row[2] = name;
10611 row[3] = driver;
10612 row[4] = setup;
10613 }
10614 }
10615
10616 /// <summary>
10617 /// Parses a Property element underneath an ODBC driver or translator.
10618 /// </summary>
10619 /// <param name="node">Element to parse.</param>
10620 /// <param name="parentId">Identifier of parent driver or translator.</param>
10621 /// <param name="tableName">Name of the table to create property in.</param>
10622 private void ParseODBCProperty(XElement node, string parentId, string tableName)
10623 {
10624 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
10625 string id = null;
10626 string propertyValue = null;
10627
10628 foreach (XAttribute attrib in node.Attributes())
10629 {
10630 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
10631 {
10632 switch (attrib.Name.LocalName)
10633 {
10634 case "Id":
10635 id = this.core.GetAttributeValue(sourceLineNumbers, attrib);
10636 break;
10637 case "Value":
10638 propertyValue = this.core.GetAttributeValue(sourceLineNumbers, attrib);
10639 break;
10640 default:
10641 this.core.UnexpectedAttribute(node, attrib);
10642 break;
10643 }
10644 }
10645 else
10646 {
10647 this.core.ParseExtensionAttribute(node, attrib);
10648 }
10649 }
10650
10651 if (null == id)
10652 {
10653 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
10654 }
10655
10656 this.core.ParseForExtensionElements(node);
10657
10658 if (!this.core.EncounteredError)
10659 {
10660 Row row = this.core.CreateRow(sourceLineNumbers, tableName);
10661 row[0] = parentId;
10662 row[1] = id;
10663 row[2] = propertyValue;
10664 }
10665 }
10666
10667 /// <summary>
10668 /// Parse an odbc data source element.
10669 /// </summary>
10670 /// <param name="node">Element to parse.</param>
10671 /// <param name="componentId">Identifier of parent component.</param>
10672 /// <param name="driverName">Default name of driver.</param>
10673 /// <param name="possibleKeyPath">Identifier of this element in case it is a keypath.</param>
10674 /// <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>
10675 private YesNoType ParseODBCDataSource(XElement node, string componentId, string driverName, out string possibleKeyPath)
10676 {
10677 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
10678 Identifier id = null;
10679 YesNoType keyPath = YesNoType.NotSet;
10680 string name = null;
10681 int registration = CompilerConstants.IntegerNotSet;
10682
10683 foreach (XAttribute attrib in node.Attributes())
10684 {
10685 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
10686 {
10687 switch (attrib.Name.LocalName)
10688 {
10689 case "Id":
10690 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
10691 break;
10692 case "DriverName":
10693 driverName = this.core.GetAttributeValue(sourceLineNumbers, attrib);
10694 break;
10695 case "KeyPath":
10696 keyPath = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
10697 break;
10698 case "Name":
10699 name = this.core.GetAttributeValue(sourceLineNumbers, attrib);
10700 break;
10701 case "Registration":
10702 string registrationValue = this.core.GetAttributeValue(sourceLineNumbers, attrib);
10703 if (0 < registrationValue.Length)
10704 {
10705 Wix.ODBCDataSource.RegistrationType registrationType = Wix.ODBCDataSource.ParseRegistrationType(registrationValue);
10706 switch (registrationType)
10707 {
10708 case Wix.ODBCDataSource.RegistrationType.machine:
10709 registration = 0;
10710 break;
10711 case Wix.ODBCDataSource.RegistrationType.user:
10712 registration = 1;
10713 break;
10714 default:
10715 this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Registration", registrationValue, "machine", "user"));
10716 break;
10717 }
10718 }
10719 break;
10720 default:
10721 this.core.UnexpectedAttribute(node, attrib);
10722 break;
10723 }
10724 }
10725 else
10726 {
10727 this.core.ParseExtensionAttribute(node, attrib);
10728 }
10729 }
10730
10731 if (CompilerConstants.IntegerNotSet == registration)
10732 {
10733 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Registration"));
10734 registration = CompilerConstants.IllegalInteger;
10735 }
10736
10737 if (null == id)
10738 {
10739 id = this.core.CreateIdentifier("odc", name, driverName, registration.ToString());
10740 }
10741
10742 foreach (XElement child in node.Elements())
10743 {
10744 if (CompilerCore.WixNamespace == child.Name.Namespace)
10745 {
10746 switch (child.Name.LocalName)
10747 {
10748 case "Property":
10749 this.ParseODBCProperty(child, id.Id, "ODBCSourceAttribute");
10750 break;
10751 default:
10752 this.core.UnexpectedElement(node, child);
10753 break;
10754 }
10755 }
10756 else
10757 {
10758 this.core.ParseExtensionElement(node, child);
10759 }
10760 }
10761
10762 if (!this.core.EncounteredError)
10763 {
10764 Row row = this.core.CreateRow(sourceLineNumbers, "ODBCDataSource", id);
10765 row[1] = componentId;
10766 row[2] = name;
10767 row[3] = driverName;
10768 row[4] = registration;
10769 }
10770
10771 possibleKeyPath = id.Id;
10772 return keyPath;
10773 }
10774
10775 /// <summary>
10776 /// Parses a package element.
10777 /// </summary>
10778 /// <param name="node">Element to parse.</param>
10779 /// <param name="productAuthor">Default package author.</param>
10780 /// <param name="moduleId">The module guid - this is necessary until Module/@Guid is removed.</param>
10781 private void ParsePackageElement(XElement node, string productAuthor, string moduleId)
10782 {
10783 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
10784 string codepage = "65001";
10785 string comments = String.Format(CultureInfo.InvariantCulture, "This installer database contains the logic and data required to install {0}.", this.activeName);
10786 string keywords = "Installer";
10787 int msiVersion = 100; // lowest released version, really should be specified
10788 string packageAuthor = productAuthor;
10789 string packageCode = null;
10790 string packageLanguages = this.activeLanguage;
10791 string packageName = this.activeName;
10792 string platform = null;
10793 string platformValue = null;
10794 YesNoDefaultType security = YesNoDefaultType.Default;
10795 int sourceBits = (this.compilingModule ? 2 : 0);
10796 Row row;
10797 bool installPrivilegeSeen = false;
10798 bool installScopeSeen = false;
10799
10800 switch (this.CurrentPlatform)
10801 {
10802 case Platform.X86:
10803 platform = "Intel";
10804 break;
10805 case Platform.X64:
10806 platform = "x64";
10807 msiVersion = 200;
10808 break;
10809 case Platform.IA64:
10810 platform = "Intel64";
10811 msiVersion = 200;
10812 break;
10813 case Platform.ARM:
10814 platform = "Arm";
10815 msiVersion = 500;
10816 break;
10817 default:
10818 throw new ArgumentException(WixStrings.EXP_UnknownPlatformEnum, this.CurrentPlatform.ToString());
10819 }
10820
10821 foreach (XAttribute attrib in node.Attributes())
10822 {
10823 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
10824 {
10825 switch (attrib.Name.LocalName)
10826 {
10827 case "Id":
10828 packageCode = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, this.compilingProduct);
10829 break;
10830 case "AdminImage":
10831 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
10832 {
10833 sourceBits = sourceBits | 4;
10834 }
10835 break;
10836 case "Comments":
10837 comments = this.core.GetAttributeValue(sourceLineNumbers, attrib);
10838 break;
10839 case "Compressed":
10840 // merge modules must always be compressed, so this attribute is invalid
10841 if (this.compilingModule)
10842 {
10843 this.core.OnMessage(WixWarnings.DeprecatedPackageCompressedAttribute(sourceLineNumbers));
10844 // this.core.OnMessage(WixErrors.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "Compressed", "Module"));
10845 }
10846 else if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
10847 {
10848 sourceBits = sourceBits | 2;
10849 }
10850 break;
10851 case "Description":
10852 packageName = this.core.GetAttributeValue(sourceLineNumbers, attrib);
10853 break;
10854 case "InstallPrivileges":
10855 string installPrivileges = this.core.GetAttributeValue(sourceLineNumbers, attrib);
10856 if (0 < installPrivileges.Length)
10857 {
10858 installPrivilegeSeen = true;
10859 Wix.Package.InstallPrivilegesType installPrivilegesType = Wix.Package.ParseInstallPrivilegesType(installPrivileges);
10860 switch (installPrivilegesType)
10861 {
10862 case Wix.Package.InstallPrivilegesType.elevated:
10863 // this is the default setting
10864 break;
10865 case Wix.Package.InstallPrivilegesType.limited:
10866 sourceBits = sourceBits | 8;
10867 break;
10868 default:
10869 this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, installPrivileges, "elevated", "limited"));
10870 break;
10871 }
10872 }
10873 break;
10874 case "InstallScope":
10875 string installScope = this.core.GetAttributeValue(sourceLineNumbers, attrib);
10876 if (0 < installScope.Length)
10877 {
10878 installScopeSeen = true;
10879 Wix.Package.InstallScopeType installScopeType = Wix.Package.ParseInstallScopeType(installScope);
10880 switch (installScopeType)
10881 {
10882 case Wix.Package.InstallScopeType.perMachine:
10883 row = this.core.CreateRow(sourceLineNumbers, "Property");
10884 row[0] = "ALLUSERS";
10885 row[1] = "1";
10886 break;
10887 case Wix.Package.InstallScopeType.perUser:
10888 sourceBits = sourceBits | 8;
10889 break;
10890 default:
10891 this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, installScope, "perMachine", "perUser"));
10892 break;
10893 }
10894 }
10895 break;
10896 case "InstallerVersion":
10897 msiVersion = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, int.MaxValue);
10898 break;
10899 case "Keywords":
10900 keywords = this.core.GetAttributeValue(sourceLineNumbers, attrib);
10901 break;
10902 case "Languages":
10903 packageLanguages = this.core.GetAttributeValue(sourceLineNumbers, attrib);
10904 break;
10905 case "Manufacturer":
10906 packageAuthor = this.core.GetAttributeValue(sourceLineNumbers, attrib);
10907 if ("PUT-COMPANY-NAME-HERE" == packageAuthor)
10908 {
10909 this.core.OnMessage(WixWarnings.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, packageAuthor));
10910 }
10911 break;
10912 case "Platform":
10913 if (null != platformValue)
10914 {
10915 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Platforms"));
10916 }
10917
10918 platformValue = this.core.GetAttributeValue(sourceLineNumbers, attrib);
10919 Wix.Package.PlatformType platformType = Wix.Package.ParsePlatformType(platformValue);
10920 switch (platformType)
10921 {
10922 case Wix.Package.PlatformType.intel:
10923 this.core.OnMessage(WixWarnings.DeprecatedAttributeValue(sourceLineNumbers, platformValue, node.Name.LocalName, attrib.Name.LocalName, "x86"));
10924 goto case Wix.Package.PlatformType.x86;
10925 case Wix.Package.PlatformType.x86:
10926 platform = "Intel";
10927 break;
10928 case Wix.Package.PlatformType.x64:
10929 platform = "x64";
10930 break;
10931 case Wix.Package.PlatformType.intel64:
10932 this.core.OnMessage(WixWarnings.DeprecatedAttributeValue(sourceLineNumbers, platformValue, node.Name.LocalName, attrib.Name.LocalName, "ia64"));
10933 goto case Wix.Package.PlatformType.ia64;
10934 case Wix.Package.PlatformType.ia64:
10935 platform = "Intel64";
10936 break;
10937 case Wix.Package.PlatformType.arm:
10938 platform = "Arm";
10939 break;
10940 default:
10941 this.core.OnMessage(WixErrors.InvalidPlatformValue(sourceLineNumbers, platformValue));
10942 break;
10943 }
10944 break;
10945 case "Platforms":
10946 if (null != platformValue)
10947 {
10948 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Platform"));
10949 }
10950
10951 this.core.OnMessage(WixWarnings.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Platform"));
10952 platformValue = this.core.GetAttributeValue(sourceLineNumbers, attrib);
10953 platform = platformValue;
10954 break;
10955 case "ReadOnly":
10956 security = this.core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib);
10957 break;
10958 case "ShortNames":
10959 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
10960 {
10961 sourceBits = sourceBits | 1;
10962 this.useShortFileNames = true;
10963 }
10964 break;
10965 case "SummaryCodepage":
10966 codepage = this.core.GetAttributeLocalizableCodePageValue(sourceLineNumbers, attrib, true);
10967 break;
10968 default:
10969 this.core.UnexpectedAttribute(node, attrib);
10970 break;
10971 }
10972 }
10973 else
10974 {
10975 this.core.ParseExtensionAttribute(node, attrib);
10976 }
10977 }
10978
10979 if (installPrivilegeSeen && installScopeSeen)
10980 {
10981 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "InstallPrivileges", "InstallScope"));
10982 }
10983
10984 if ((0 != String.Compare(platform, "Intel", StringComparison.OrdinalIgnoreCase)) && 200 > msiVersion)
10985 {
10986 msiVersion = 200;
10987 this.core.OnMessage(WixWarnings.RequiresMsi200for64bitPackage(sourceLineNumbers));
10988 }
10989
10990 if ((0 == String.Compare(platform, "Arm", StringComparison.OrdinalIgnoreCase)) && 500 > msiVersion)
10991 {
10992 msiVersion = 500;
10993 this.core.OnMessage(WixWarnings.RequiresMsi500forArmPackage(sourceLineNumbers));
10994 }
10995
10996 if (null == packageAuthor)
10997 {
10998 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Manufacturer"));
10999 }
11000
11001 if (this.compilingModule)
11002 {
11003 if (null == packageCode)
11004 {
11005 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
11006 }
11007
11008 // merge modules use the modularization guid as the package code
11009 if (null != moduleId)
11010 {
11011 packageCode = moduleId;
11012 }
11013
11014 // merge modules are always compressed
11015 sourceBits = 2;
11016 }
11017 else // product
11018 {
11019 if (null == packageCode)
11020 {
11021 packageCode = "*";
11022 }
11023
11024 if ("*" != packageCode)
11025 {
11026 this.core.OnMessage(WixWarnings.PackageCodeSet(sourceLineNumbers));
11027 }
11028 }
11029
11030 this.core.ParseForExtensionElements(node);
11031
11032 if (!this.core.EncounteredError)
11033 {
11034 row = this.core.CreateRow(sourceLineNumbers, "_SummaryInformation");
11035 row[0] = 1;
11036 row[1] = codepage;
11037
11038 row = this.core.CreateRow(sourceLineNumbers, "_SummaryInformation");
11039 row[0] = 2;
11040 row[1] = "Installation Database";
11041
11042 row = this.core.CreateRow(sourceLineNumbers, "_SummaryInformation");
11043 row[0] = 3;
11044 row[1] = packageName;
11045
11046 row = this.core.CreateRow(sourceLineNumbers, "_SummaryInformation");
11047 row[0] = 4;
11048 row[1] = packageAuthor;
11049
11050 row = this.core.CreateRow(sourceLineNumbers, "_SummaryInformation");
11051 row[0] = 5;
11052 row[1] = keywords;
11053
11054 row = this.core.CreateRow(sourceLineNumbers, "_SummaryInformation");
11055 row[0] = 6;
11056 row[1] = comments;
11057
11058 row = this.core.CreateRow(sourceLineNumbers, "_SummaryInformation");
11059 row[0] = 7;
11060 row[1] = String.Format(CultureInfo.InvariantCulture, "{0};{1}", platform, packageLanguages);
11061
11062 row = this.core.CreateRow(sourceLineNumbers, "_SummaryInformation");
11063 row[0] = 9;
11064 row[1] = packageCode;
11065
11066 row = this.core.CreateRow(sourceLineNumbers, "_SummaryInformation");
11067 row[0] = 14;
11068 row[1] = msiVersion.ToString(CultureInfo.InvariantCulture);
11069
11070 row = this.core.CreateRow(sourceLineNumbers, "_SummaryInformation");
11071 row[0] = 15;
11072 row[1] = sourceBits.ToString(CultureInfo.InvariantCulture);
11073
11074 row = this.core.CreateRow(sourceLineNumbers, "_SummaryInformation");
11075 row[0] = 19;
11076 switch (security)
11077 {
11078 case YesNoDefaultType.No: // no restriction
11079 row[1] = "0";
11080 break;
11081 case YesNoDefaultType.Default: // read-only recommended
11082 row[1] = "2";
11083 break;
11084 case YesNoDefaultType.Yes: // read-only enforced
11085 row[1] = "4";
11086 break;
11087 }
11088 }
11089 }
11090
11091 /// <summary>
11092 /// Parses a patch metadata element.
11093 /// </summary>
11094 /// <param name="node">Element to parse.</param>
11095 private void ParsePatchMetadataElement(XElement node)
11096 {
11097 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
11098 YesNoType allowRemoval = YesNoType.NotSet;
11099 string classification = null;
11100 string creationTimeUtc = null;
11101 string description = null;
11102 string displayName = null;
11103 string manufacturerName = null;
11104 string minorUpdateTargetRTM = null;
11105 string moreInfoUrl = null;
11106 int optimizeCA = CompilerConstants.IntegerNotSet;
11107 YesNoType optimizedInstallMode = YesNoType.NotSet;
11108 string targetProductName = null;
11109
11110 foreach (XAttribute attrib in node.Attributes())
11111 {
11112 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
11113 {
11114 switch (attrib.Name.LocalName)
11115 {
11116 case "AllowRemoval":
11117 allowRemoval = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
11118 break;
11119 case "Classification":
11120 classification = this.core.GetAttributeValue(sourceLineNumbers, attrib);
11121 break;
11122 case "CreationTimeUTC":
11123 creationTimeUtc = this.core.GetAttributeValue(sourceLineNumbers, attrib);
11124 break;
11125 case "Description":
11126 description = this.core.GetAttributeValue(sourceLineNumbers, attrib);
11127 break;
11128 case "DisplayName":
11129 displayName = this.core.GetAttributeValue(sourceLineNumbers, attrib);
11130 break;
11131 case "ManufacturerName":
11132 manufacturerName = this.core.GetAttributeValue(sourceLineNumbers, attrib);
11133 break;
11134 case "MinorUpdateTargetRTM":
11135 minorUpdateTargetRTM = this.core.GetAttributeValue(sourceLineNumbers, attrib);
11136 break;
11137 case "MoreInfoURL":
11138 moreInfoUrl = this.core.GetAttributeValue(sourceLineNumbers, attrib);
11139 break;
11140 case "OptimizedInstallMode":
11141 optimizedInstallMode = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
11142 break;
11143 case "TargetProductName":
11144 targetProductName = this.core.GetAttributeValue(sourceLineNumbers, attrib);
11145 break;
11146 default:
11147 this.core.UnexpectedAttribute(node, attrib);
11148 break;
11149 }
11150 }
11151 else
11152 {
11153 this.core.ParseExtensionAttribute(node, attrib);
11154 }
11155 }
11156
11157 if (YesNoType.NotSet == allowRemoval)
11158 {
11159 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "AllowRemoval"));
11160 }
11161
11162 if (null == classification)
11163 {
11164 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Classification"));
11165 }
11166
11167 if (null == description)
11168 {
11169 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Description"));
11170 }
11171
11172 if (null == displayName)
11173 {
11174 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisplayName"));
11175 }
11176
11177 if (null == manufacturerName)
11178 {
11179 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ManufacturerName"));
11180 }
11181
11182 if (null == moreInfoUrl)
11183 {
11184 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "MoreInfoURL"));
11185 }
11186
11187 if (null == targetProductName)
11188 {
11189 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "TargetProductName"));
11190 }
11191
11192 foreach (XElement child in node.Elements())
11193 {
11194 if (CompilerCore.WixNamespace == child.Name.Namespace)
11195 {
11196 switch (child.Name.LocalName)
11197 {
11198 case "CustomProperty":
11199 this.ParseCustomPropertyElement(child);
11200 break;
11201 case "OptimizeCustomActions":
11202 optimizeCA = this.ParseOptimizeCustomActionsElement(child);
11203 break;
11204 default:
11205 this.core.UnexpectedElement(node, child);
11206 break;
11207 }
11208 }
11209 else
11210 {
11211 this.core.ParseExtensionElement(node, child);
11212 }
11213 }
11214
11215 if (!this.core.EncounteredError)
11216 {
11217 if (YesNoType.NotSet != allowRemoval)
11218 {
11219 Row row = this.core.CreateRow(sourceLineNumbers, "PatchMetadata");
11220 row[0] = null;
11221 row[1] = "AllowRemoval";
11222 row[2] = YesNoType.Yes == allowRemoval ? "1" : "0";
11223 }
11224
11225 if (null != classification)
11226 {
11227 Row row = this.core.CreateRow(sourceLineNumbers, "PatchMetadata");
11228 row[0] = null;
11229 row[1] = "Classification";
11230 row[2] = classification;
11231 }
11232
11233 if (null != creationTimeUtc)
11234 {
11235 Row row = this.core.CreateRow(sourceLineNumbers, "PatchMetadata");
11236 row[0] = null;
11237 row[1] = "CreationTimeUTC";
11238 row[2] = creationTimeUtc;
11239 }
11240
11241 if (null != description)
11242 {
11243 Row row = this.core.CreateRow(sourceLineNumbers, "PatchMetadata");
11244 row[0] = null;
11245 row[1] = "Description";
11246 row[2] = description;
11247 }
11248
11249 if (null != displayName)
11250 {
11251 Row row = this.core.CreateRow(sourceLineNumbers, "PatchMetadata");
11252 row[0] = null;
11253 row[1] = "DisplayName";
11254 row[2] = displayName;
11255 }
11256
11257 if (null != manufacturerName)
11258 {
11259 Row row = this.core.CreateRow(sourceLineNumbers, "PatchMetadata");
11260 row[0] = null;
11261 row[1] = "ManufacturerName";
11262 row[2] = manufacturerName;
11263 }
11264
11265 if (null != minorUpdateTargetRTM)
11266 {
11267 Row row = this.core.CreateRow(sourceLineNumbers, "PatchMetadata");
11268 row[0] = null;
11269 row[1] = "MinorUpdateTargetRTM";
11270 row[2] = minorUpdateTargetRTM;
11271 }
11272
11273 if (null != moreInfoUrl)
11274 {
11275 Row row = this.core.CreateRow(sourceLineNumbers, "PatchMetadata");
11276 row[0] = null;
11277 row[1] = "MoreInfoURL";
11278 row[2] = moreInfoUrl;
11279 }
11280
11281 if (CompilerConstants.IntegerNotSet != optimizeCA)
11282 {
11283 Row row = this.core.CreateRow(sourceLineNumbers, "PatchMetadata");
11284 row[0] = null;
11285 row[1] = "OptimizeCA";
11286 row[2] = optimizeCA.ToString(CultureInfo.InvariantCulture);
11287 }
11288
11289 if (YesNoType.NotSet != optimizedInstallMode)
11290 {
11291 Row row = this.core.CreateRow(sourceLineNumbers, "PatchMetadata");
11292 row[0] = null;
11293 row[1] = "OptimizedInstallMode";
11294 row[2] = YesNoType.Yes == optimizedInstallMode ? "1" : "0";
11295 }
11296
11297 if (null != targetProductName)
11298 {
11299 Row row = this.core.CreateRow(sourceLineNumbers, "PatchMetadata");
11300 row[0] = null;
11301 row[1] = "TargetProductName";
11302 row[2] = targetProductName;
11303 }
11304 }
11305 }
11306
11307 /// <summary>
11308 /// Parses a custom property element for the PatchMetadata table.
11309 /// </summary>
11310 /// <param name="node">Element to parse.</param>
11311 private void ParseCustomPropertyElement(XElement node)
11312 {
11313 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
11314 string company = null;
11315 string property = null;
11316 string value = null;
11317
11318 foreach (XAttribute attrib in node.Attributes())
11319 {
11320 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
11321 {
11322 switch (attrib.Name.LocalName)
11323 {
11324 case "Company":
11325 company = this.core.GetAttributeValue(sourceLineNumbers, attrib);
11326 break;
11327 case "Property":
11328 property = this.core.GetAttributeValue(sourceLineNumbers, attrib);
11329 break;
11330 case "Value":
11331 value = this.core.GetAttributeValue(sourceLineNumbers, attrib);
11332 break;
11333 default:
11334 this.core.UnexpectedAttribute(node, attrib);
11335 break;
11336 }
11337 }
11338 else
11339 {
11340 this.core.ParseExtensionAttribute(node, attrib);
11341 }
11342 }
11343
11344 if (null == company)
11345 {
11346 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Company"));
11347 }
11348
11349 if (null == property)
11350 {
11351 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property"));
11352 }
11353
11354 if (null == value)
11355 {
11356 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value"));
11357 }
11358
11359 this.core.ParseForExtensionElements(node);
11360
11361 if (!this.core.EncounteredError)
11362 {
11363 Row row = this.core.CreateRow(sourceLineNumbers, "PatchMetadata");
11364 row[0] = company;
11365 row[1] = property;
11366 row[2] = value;
11367 }
11368 }
11369
11370 /// <summary>
11371 /// Parses the OptimizeCustomActions element.
11372 /// </summary>
11373 /// <param name="node">Element to parse.</param>
11374 /// <returns>The combined integer value for callers to store as appropriate.</returns>
11375 private int ParseOptimizeCustomActionsElement(XElement node)
11376 {
11377 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
11378 OptimizeCA optimizeCA = OptimizeCA.None;
11379
11380 foreach (XAttribute attrib in node.Attributes())
11381 {
11382 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
11383 {
11384 switch (attrib.Name.LocalName)
11385 {
11386 case "SkipAssignment":
11387 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
11388 {
11389 optimizeCA |= OptimizeCA.SkipAssignment;
11390 }
11391 break;
11392 case "SkipImmediate":
11393 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
11394 {
11395 optimizeCA |= OptimizeCA.SkipImmediate;
11396 }
11397 break;
11398 case "SkipDeferred":
11399 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
11400 {
11401 optimizeCA |= OptimizeCA.SkipDeferred;
11402 }
11403 break;
11404 default:
11405 this.core.UnexpectedAttribute(node, attrib);
11406 break;
11407 }
11408 }
11409 else
11410 {
11411 this.core.ParseExtensionAttribute(node, attrib);
11412 }
11413 }
11414
11415 return (int)optimizeCA;
11416 }
11417
11418 /// <summary>
11419 /// Parses a patch information element.
11420 /// </summary>
11421 /// <param name="node">Element to parse.</param>
11422 private void ParsePatchInformationElement(XElement node)
11423 {
11424 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
11425 string codepage = "65001";
11426 string comments = null;
11427 string keywords = "Installer,Patching,PCP,Database";
11428 int msiVersion = 1; // Should always be 1 for patches
11429 string packageAuthor = null;
11430 string packageName = this.activeName;
11431 YesNoDefaultType security = YesNoDefaultType.Default;
11432
11433 foreach (XAttribute attrib in node.Attributes())
11434 {
11435 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
11436 {
11437 switch (attrib.Name.LocalName)
11438 {
11439 case "AdminImage":
11440 this.core.OnMessage(WixWarnings.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName));
11441 break;
11442 case "Comments":
11443 comments = this.core.GetAttributeValue(sourceLineNumbers, attrib);
11444 break;
11445 case "Compressed":
11446 this.core.OnMessage(WixWarnings.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName));
11447 break;
11448 case "Description":
11449 packageName = this.core.GetAttributeValue(sourceLineNumbers, attrib);
11450 break;
11451 case "Keywords":
11452 keywords = this.core.GetAttributeValue(sourceLineNumbers, attrib);
11453 break;
11454 case "Languages":
11455 this.core.OnMessage(WixWarnings.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName));
11456 break;
11457 case "Manufacturer":
11458 packageAuthor = this.core.GetAttributeValue(sourceLineNumbers, attrib);
11459 break;
11460 case "Platforms":
11461 this.core.OnMessage(WixWarnings.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName));
11462 break;
11463 case "ReadOnly":
11464 security = this.core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib);
11465 break;
11466 case "ShortNames":
11467 this.core.OnMessage(WixWarnings.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName));
11468 break;
11469 case "SummaryCodepage":
11470 codepage = this.core.GetAttributeLocalizableCodePageValue(sourceLineNumbers, attrib);
11471 break;
11472 default:
11473 this.core.UnexpectedAttribute(node, attrib);
11474 break;
11475 }
11476 }
11477 else
11478 {
11479 this.core.ParseExtensionAttribute(node, attrib);
11480 }
11481 }
11482
11483 this.core.ParseForExtensionElements(node);
11484
11485 if (!this.core.EncounteredError)
11486 {
11487 // PID_CODEPAGE
11488 Row row = this.core.CreateRow(sourceLineNumbers, "_SummaryInformation");
11489 row[0] = 1;
11490 row[1] = codepage;
11491
11492 // PID_TITLE
11493 row = this.core.CreateRow(sourceLineNumbers, "_SummaryInformation");
11494 row[0] = 2;
11495 row[1] = "Patch";
11496
11497 // PID_SUBJECT
11498 if (null != packageName)
11499 {
11500 row = this.core.CreateRow(sourceLineNumbers, "_SummaryInformation");
11501 row[0] = 3;
11502 row[1] = packageName;
11503 }
11504
11505 // PID_AUTHOR
11506 if (null != packageAuthor)
11507 {
11508 row = this.core.CreateRow(sourceLineNumbers, "_SummaryInformation");
11509 row[0] = 4;
11510 row[1] = packageAuthor;
11511 }
11512
11513 // PID_KEYWORDS
11514 if (null != keywords)
11515 {
11516 row = this.core.CreateRow(sourceLineNumbers, "_SummaryInformation");
11517 row[0] = 5;
11518 row[1] = keywords;
11519 }
11520
11521 // PID_COMMENTS
11522 if (null != comments)
11523 {
11524 row = this.core.CreateRow(sourceLineNumbers, "_SummaryInformation");
11525 row[0] = 6;
11526 row[1] = comments;
11527 }
11528
11529 // PID_PAGECOUNT
11530 row = this.core.CreateRow(sourceLineNumbers, "_SummaryInformation");
11531 row[0] = 14;
11532 row[1] = msiVersion.ToString(CultureInfo.InvariantCulture);
11533
11534 // PID_WORDCOUNT
11535 row = this.core.CreateRow(sourceLineNumbers, "_SummaryInformation");
11536 row[0] = 15;
11537 row[1] = "0";
11538
11539 // PID_SECURITY
11540 row = this.core.CreateRow(sourceLineNumbers, "_SummaryInformation");
11541 row[0] = 19;
11542 switch (security)
11543 {
11544 case YesNoDefaultType.No: // no restriction
11545 row[1] = "0";
11546 break;
11547 case YesNoDefaultType.Default: // read-only recommended
11548 row[1] = "2";
11549 break;
11550 case YesNoDefaultType.Yes: // read-only enforced
11551 row[1] = "4";
11552 break;
11553 }
11554 }
11555 }
11556
11557 /// <summary>
11558 /// Parses an ignore modularization element.
11559 /// </summary>
11560 /// <param name="node">XmlNode on an IgnoreModulatization element.</param>
11561 private void ParseIgnoreModularizationElement(XElement node)
11562 {
11563 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
11564 string name = null;
11565
11566 this.core.OnMessage(WixWarnings.DeprecatedIgnoreModularizationElement(sourceLineNumbers));
11567
11568 foreach (XAttribute attrib in node.Attributes())
11569 {
11570 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
11571 {
11572 switch (attrib.Name.LocalName)
11573 {
11574 case "Name":
11575 name = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
11576 break;
11577 case "Type":
11578 // this is actually not used
11579 break;
11580 default:
11581 this.core.UnexpectedAttribute(node, attrib);
11582 break;
11583 }
11584 }
11585 else
11586 {
11587 this.core.ParseExtensionAttribute(node, attrib);
11588 }
11589 }
11590
11591 if (null == name)
11592 {
11593 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
11594 }
11595
11596 this.core.ParseForExtensionElements(node);
11597
11598 if (!this.core.EncounteredError)
11599 {
11600 Row row = this.core.CreateRow(sourceLineNumbers, "WixSuppressModularization");
11601 row[0] = name;
11602 }
11603 }
11604
11605 /// <summary>
11606 /// Parses a permission element.
11607 /// </summary>
11608 /// <param name="node">Element to parse.</param>
11609 /// <param name="objectId">Identifier of object to be secured.</param>
11610 /// <param name="tableName">Name of table that contains objectId.</param>
11611 private void ParsePermissionElement(XElement node, string objectId, string tableName)
11612 {
11613 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
11614 BitArray bits = new BitArray(32);
11615 string domain = null;
11616 int permission = 0;
11617 string[] specialPermissions = null;
11618 string user = null;
11619
11620 switch (tableName)
11621 {
11622 case "CreateFolder":
11623 specialPermissions = Common.FolderPermissions;
11624 break;
11625 case "File":
11626 specialPermissions = Common.FilePermissions;
11627 break;
11628 case "Registry":
11629 specialPermissions = Common.RegistryPermissions;
11630 break;
11631 default:
11632 this.core.UnexpectedElement(node.Parent, node);
11633 return; // stop processing this element since no valid permissions are available
11634 }
11635
11636 foreach (XAttribute attrib in node.Attributes())
11637 {
11638 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
11639 {
11640 switch (attrib.Name.LocalName)
11641 {
11642 case "Domain":
11643 domain = this.core.GetAttributeValue(sourceLineNumbers, attrib);
11644 break;
11645 case "User":
11646 user = this.core.GetAttributeValue(sourceLineNumbers, attrib);
11647 break;
11648 case "FileAllRights":
11649 // match the WinNT.h mask FILE_ALL_ACCESS for value 0x001F01FF (aka 1 1111 0000 0001 1111 1111 or 2032127)
11650 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;
11651 break;
11652 case "SpecificRightsAll":
11653 // match the WinNT.h mask SPECIFIC_RIGHTS_ALL for value 0x0000FFFF (aka 1111 1111 1111 1111)
11654 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;
11655 break;
11656 default:
11657 YesNoType attribValue = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
11658 if (!this.core.TrySetBitFromName(Common.StandardPermissions, attrib.Name.LocalName, attribValue, bits, 16))
11659 {
11660 if (!this.core.TrySetBitFromName(Common.GenericPermissions, attrib.Name.LocalName, attribValue, bits, 28))
11661 {
11662 if (!this.core.TrySetBitFromName(specialPermissions, attrib.Name.LocalName, attribValue, bits, 0))
11663 {
11664 this.core.UnexpectedAttribute(node, attrib);
11665 break;
11666 }
11667 }
11668 }
11669 break;
11670 }
11671 }
11672 else
11673 {
11674 this.core.ParseExtensionAttribute(node, attrib);
11675 }
11676 }
11677
11678 permission = this.core.CreateIntegerFromBitArray(bits);
11679
11680 if (null == user)
11681 {
11682 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "User"));
11683 }
11684
11685 if (int.MinValue == permission) // just GENERIC_READ, which is MSI_NULL
11686 {
11687 this.core.OnMessage(WixErrors.GenericReadNotAllowed(sourceLineNumbers));
11688 }
11689
11690 this.core.ParseForExtensionElements(node);
11691
11692 if (!this.core.EncounteredError)
11693 {
11694 Row row = this.core.CreateRow(sourceLineNumbers, "LockPermissions");
11695 row[0] = objectId;
11696 row[1] = tableName;
11697 row[2] = domain;
11698 row[3] = user;
11699 row[4] = permission;
11700 }
11701 }
11702
11703 /// <summary>
11704 /// Parses an extended permission element.
11705 /// </summary>
11706 /// <param name="node">Element to parse.</param>
11707 /// <param name="objectId">Identifier of object to be secured.</param>
11708 /// <param name="tableName">Name of table that contains objectId.</param>
11709 private void ParsePermissionExElement(XElement node, string objectId, string tableName)
11710 {
11711 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
11712 string condition = null;
11713 Identifier id = null;
11714 string sddl = null;
11715
11716 switch (tableName)
11717 {
11718 case "CreateFolder":
11719 case "File":
11720 case "Registry":
11721 case "ServiceInstall":
11722 break;
11723 default:
11724 this.core.UnexpectedElement(node.Parent, node);
11725 return; // stop processing this element since nothing will be valid.
11726 }
11727
11728 foreach (XAttribute attrib in node.Attributes())
11729 {
11730 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
11731 {
11732 switch (attrib.Name.LocalName)
11733 {
11734 case "Id":
11735 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
11736 break;
11737 case "Sddl":
11738 sddl = this.core.GetAttributeValue(sourceLineNumbers, attrib);
11739 break;
11740 default:
11741 this.core.UnexpectedAttribute(node, attrib);
11742 break;
11743 }
11744 }
11745 else
11746 {
11747 this.core.ParseExtensionAttribute(node, attrib);
11748 }
11749 }
11750
11751 if (null == sddl)
11752 {
11753 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Sddl"));
11754 }
11755
11756 if (null == id)
11757 {
11758 id = this.core.CreateIdentifier("pme", objectId, tableName, sddl);
11759 }
11760
11761 foreach (XElement child in node.Elements())
11762 {
11763 if (CompilerCore.WixNamespace == child.Name.Namespace)
11764 {
11765 switch (child.Name.LocalName)
11766 {
11767 case "Condition":
11768 if (null != condition)
11769 {
11770 SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
11771 this.core.OnMessage(WixErrors.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, child.Name.LocalName));
11772 }
11773
11774 condition = this.ParseConditionElement(child, node.Name.LocalName, null, null);
11775 break;
11776 default:
11777 this.core.UnexpectedElement(node, child);
11778 break;
11779 }
11780 }
11781 else
11782 {
11783 this.core.ParseExtensionElement(node, child);
11784 }
11785 }
11786
11787 if (!this.core.EncounteredError)
11788 {
11789 Row row = this.core.CreateRow(sourceLineNumbers, "MsiLockPermissionsEx", id);
11790 row[1] = objectId;
11791 row[2] = tableName;
11792 row[3] = sddl;
11793 row[4] = condition;
11794 }
11795 }
11796
11797 /// <summary>
11798 /// Parses a product element.
11799 /// </summary>
11800 /// <param name="node">Element to parse.</param>
11801 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
11802 private void ParseProductElement(XElement node)
11803 {
11804 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
11805 int codepage = 0;
11806 string productCode = null;
11807 string upgradeCode = null;
11808 string manufacturer = null;
11809 string version = null;
11810 string symbols = null;
11811
11812 this.activeName = null;
11813 this.activeLanguage = null;
11814
11815 foreach (XAttribute attrib in node.Attributes())
11816 {
11817 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
11818 {
11819 switch (attrib.Name.LocalName)
11820 {
11821 case "Id":
11822 productCode = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, true);
11823 break;
11824 case "Codepage":
11825 codepage = this.core.GetAttributeCodePageValue(sourceLineNumbers, attrib);
11826 break;
11827 case "Language":
11828 this.activeLanguage = this.core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue);
11829 break;
11830 case "Manufacturer":
11831 manufacturer = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.MustHaveNonWhitespaceCharacters);
11832 if ("PUT-COMPANY-NAME-HERE" == manufacturer)
11833 {
11834 this.core.OnMessage(WixWarnings.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, manufacturer));
11835 }
11836 break;
11837 case "Name":
11838 this.activeName = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.MustHaveNonWhitespaceCharacters);
11839 if ("PUT-PRODUCT-NAME-HERE" == this.activeName)
11840 {
11841 this.core.OnMessage(WixWarnings.PlaceholderValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, this.activeName));
11842 }
11843 break;
11844 case "UpgradeCode":
11845 upgradeCode = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
11846 break;
11847 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").
11848 string verifiedVersion = this.core.GetAttributeVersionValue(sourceLineNumbers, attrib);
11849 if (!String.IsNullOrEmpty(verifiedVersion))
11850 {
11851 version = attrib.Value;
11852 }
11853 break;
11854 default:
11855 this.core.UnexpectedAttribute(node, attrib);
11856 break;
11857 }
11858 }
11859 else
11860 {
11861 this.core.ParseExtensionAttribute(node, attrib);
11862 }
11863 }
11864
11865 if (null == productCode)
11866 {
11867 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
11868 }
11869
11870 if (null == this.activeLanguage)
11871 {
11872 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Language"));
11873 }
11874
11875 if (null == manufacturer)
11876 {
11877 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Manufacturer"));
11878 }
11879
11880 if (null == this.activeName)
11881 {
11882 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
11883 }
11884
11885 if (null == upgradeCode)
11886 {
11887 this.core.OnMessage(WixWarnings.MissingUpgradeCode(sourceLineNumbers));
11888 }
11889
11890 if (null == version)
11891 {
11892 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version"));
11893 }
11894 else if (!CompilerCore.IsValidProductVersion(version))
11895 {
11896 this.core.OnMessage(WixErrors.InvalidProductVersion(sourceLineNumbers, version));
11897 }
11898
11899 if (this.core.EncounteredError)
11900 {
11901 return;
11902 }
11903
11904 try
11905 {
11906 this.compilingProduct = true;
11907 this.core.CreateActiveSection(productCode, SectionType.Product, codepage);
11908
11909 this.AddProperty(sourceLineNumbers, new Identifier("Manufacturer", AccessModifier.Public), manufacturer, false, false, false, true);
11910 this.AddProperty(sourceLineNumbers, new Identifier("ProductCode", AccessModifier.Public), productCode, false, false, false, true);
11911 this.AddProperty(sourceLineNumbers, new Identifier("ProductLanguage", AccessModifier.Public), this.activeLanguage, false, false, false, true);
11912 this.AddProperty(sourceLineNumbers, new Identifier("ProductName", AccessModifier.Public), this.activeName, false, false, false, true);
11913 this.AddProperty(sourceLineNumbers, new Identifier("ProductVersion", AccessModifier.Public), version, false, false, false, true);
11914 if (null != upgradeCode)
11915 {
11916 this.AddProperty(sourceLineNumbers, new Identifier("UpgradeCode", AccessModifier.Public), upgradeCode, false, false, false, true);
11917 }
11918
11919 Dictionary<string, string> contextValues = new Dictionary<string, string>();
11920 contextValues["ProductLanguage"] = this.activeLanguage;
11921 contextValues["ProductVersion"] = version;
11922 contextValues["UpgradeCode"] = upgradeCode;
11923
11924 int featureDisplay = 0;
11925 foreach (XElement child in node.Elements())
11926 {
11927 if (CompilerCore.WixNamespace == child.Name.Namespace)
11928 {
11929 switch (child.Name.LocalName)
11930 {
11931 case "_locDefinition":
11932 break;
11933 case "AdminExecuteSequence":
11934 case "AdminUISequence":
11935 case "AdvertiseExecuteSequence":
11936 case "InstallExecuteSequence":
11937 case "InstallUISequence":
11938 this.ParseSequenceElement(child, child.Name.LocalName);
11939 break;
11940 case "AppId":
11941 this.ParseAppIdElement(child, null, YesNoType.Yes, null, null, null);
11942 break;
11943 case "Binary":
11944 this.ParseBinaryElement(child);
11945 break;
11946 case "ComplianceCheck":
11947 this.ParseComplianceCheckElement(child);
11948 break;
11949 case "Component":
11950 this.ParseComponentElement(child, ComplexReferenceParentType.Unknown, null, null, CompilerConstants.IntegerNotSet, null, null);
11951 break;
11952 case "ComponentGroup":
11953 this.ParseComponentGroupElement(child, ComplexReferenceParentType.Unknown, null);
11954 break;
11955 case "Condition":
11956 this.ParseConditionElement(child, node.Name.LocalName, null, null);
11957 break;
11958 case "CustomAction":
11959 this.ParseCustomActionElement(child);
11960 break;
11961 case "CustomActionRef":
11962 this.ParseSimpleRefElement(child, "CustomAction");
11963 break;
11964 case "CustomTable":
11965 this.ParseCustomTableElement(child);
11966 break;
11967 case "Directory":
11968 this.ParseDirectoryElement(child, null, CompilerConstants.IntegerNotSet, String.Empty);
11969 break;
11970 case "DirectoryRef":
11971 this.ParseDirectoryRefElement(child);
11972 break;
11973 case "EmbeddedChainer":
11974 this.ParseEmbeddedChainerElement(child);
11975 break;
11976 case "EmbeddedChainerRef":
11977 this.ParseSimpleRefElement(child, "MsiEmbeddedChainer");
11978 break;
11979 case "EnsureTable":
11980 this.ParseEnsureTableElement(child);
11981 break;
11982 case "Feature":
11983 this.ParseFeatureElement(child, ComplexReferenceParentType.Product, productCode, ref featureDisplay);
11984 break;
11985 case "FeatureRef":
11986 this.ParseFeatureRefElement(child, ComplexReferenceParentType.Product, productCode);
11987 break;
11988 case "FeatureGroupRef":
11989 this.ParseFeatureGroupRefElement(child, ComplexReferenceParentType.Product, productCode);
11990 break;
11991 case "Icon":
11992 this.ParseIconElement(child);
11993 break;
11994 case "InstanceTransforms":
11995 this.ParseInstanceTransformsElement(child);
11996 break;
11997 case "MajorUpgrade":
11998 this.ParseMajorUpgradeElement(child, contextValues);
11999 break;
12000 case "Media":
12001 this.ParseMediaElement(child, null);
12002 break;
12003 case "MediaTemplate":
12004 this.ParseMediaTemplateElement(child, null);
12005 break;
12006 case "Package":
12007 this.ParsePackageElement(child, manufacturer, null);
12008 break;
12009 case "PackageCertificates":
12010 case "PatchCertificates":
12011 this.ParseCertificatesElement(child);
12012 break;
12013 case "Property":
12014 this.ParsePropertyElement(child);
12015 break;
12016 case "PropertyRef":
12017 this.ParseSimpleRefElement(child, "Property");
12018 break;
12019 case "SetDirectory":
12020 this.ParseSetDirectoryElement(child);
12021 break;
12022 case "SetProperty":
12023 this.ParseSetPropertyElement(child);
12024 break;
12025 case "SFPCatalog":
12026 string parentName = null;
12027 this.ParseSFPCatalogElement(child, ref parentName);
12028 break;
12029 case "SymbolPath":
12030 if (null != symbols)
12031 {
12032 symbols += ";" + this.ParseSymbolPathElement(child);
12033 }
12034 else
12035 {
12036 symbols = this.ParseSymbolPathElement(child);
12037 }
12038 break;
12039 case "UI":
12040 this.ParseUIElement(child);
12041 break;
12042 case "UIRef":
12043 this.ParseSimpleRefElement(child, "WixUI");
12044 break;
12045 case "Upgrade":
12046 this.ParseUpgradeElement(child);
12047 break;
12048 case "WixVariable":
12049 this.ParseWixVariableElement(child);
12050 break;
12051 default:
12052 this.core.UnexpectedElement(node, child);
12053 break;
12054 }
12055 }
12056 else
12057 {
12058 this.core.ParseExtensionElement(node, child);
12059 }
12060 }
12061
12062 if (!this.core.EncounteredError)
12063 {
12064 if (null != symbols)
12065 {
12066 WixDeltaPatchSymbolPathsRow symbolRow = (WixDeltaPatchSymbolPathsRow)this.core.CreateRow(sourceLineNumbers, "WixDeltaPatchSymbolPaths");
12067 symbolRow.Id = productCode;
12068 symbolRow.Type = SymbolPathType.Product;
12069 symbolRow.SymbolPaths = symbols;
12070 }
12071 }
12072 }
12073 finally
12074 {
12075 this.compilingProduct = false;
12076 }
12077 }
12078
12079 /// <summary>
12080 /// Parses a progid element
12081 /// </summary>
12082 /// <param name="node">Element to parse.</param>
12083 /// <param name="componentId">Identifier of parent component.</param>
12084 /// <param name="advertise">Flag if progid is advertised.</param>
12085 /// <param name="classId">CLSID related to ProgId.</param>
12086 /// <param name="description">Default description of ProgId</param>
12087 /// <param name="parent">Optional parent ProgId</param>
12088 /// <param name="foundExtension">Set to true if an extension is found; used for error-checking.</param>
12089 /// <param name="firstProgIdForClass">Whether or not this ProgId is the first one found in the parent class.</param>
12090 /// <returns>This element's Id.</returns>
12091 private string ParseProgIdElement(XElement node, string componentId, YesNoType advertise, string classId, string description, string parent, ref bool foundExtension, YesNoType firstProgIdForClass)
12092 {
12093 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
12094 string icon = null;
12095 int iconIndex = CompilerConstants.IntegerNotSet;
12096 string noOpen = null;
12097 string progId = null;
12098 YesNoType progIdAdvertise = YesNoType.NotSet;
12099
12100 foreach (XAttribute attrib in node.Attributes())
12101 {
12102 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
12103 {
12104 switch (attrib.Name.LocalName)
12105 {
12106 case "Id":
12107 progId = this.core.GetAttributeValue(sourceLineNumbers, attrib);
12108 break;
12109 case "Advertise":
12110 progIdAdvertise = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
12111 break;
12112 case "Description":
12113 description = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty);
12114 break;
12115 case "Icon":
12116 icon = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
12117 break;
12118 case "IconIndex":
12119 iconIndex = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, short.MinValue + 1, short.MaxValue);
12120 break;
12121 case "NoOpen":
12122 noOpen = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty);
12123 break;
12124 default:
12125 this.core.UnexpectedAttribute(node, attrib);
12126 break;
12127 }
12128 }
12129 else
12130 {
12131 this.core.ParseExtensionAttribute(node, attrib);
12132 }
12133 }
12134
12135 if ((YesNoType.No == advertise && YesNoType.Yes == progIdAdvertise) || (YesNoType.Yes == advertise && YesNoType.No == progIdAdvertise))
12136 {
12137 this.core.OnMessage(WixErrors.AdvertiseStateMustMatch(sourceLineNumbers, advertise.ToString(), progIdAdvertise.ToString()));
12138 }
12139 else
12140 {
12141 advertise = progIdAdvertise;
12142 }
12143
12144 if (YesNoType.NotSet == advertise)
12145 {
12146 advertise = YesNoType.No;
12147 }
12148
12149 if (null != parent && (null != icon || CompilerConstants.IntegerNotSet != iconIndex))
12150 {
12151 this.core.OnMessage(WixErrors.VersionIndependentProgIdsCannotHaveIcons(sourceLineNumbers));
12152 }
12153
12154 YesNoType firstProgIdForNestedClass = YesNoType.Yes;
12155 foreach (XElement child in node.Elements())
12156 {
12157 if (CompilerCore.WixNamespace == child.Name.Namespace)
12158 {
12159 switch (child.Name.LocalName)
12160 {
12161 case "Extension":
12162 this.ParseExtensionElement(child, componentId, advertise, progId);
12163 foundExtension = true;
12164 break;
12165 case "ProgId":
12166 // Only allow one nested ProgId. If we have a child, we should not have a parent.
12167 if (null == parent)
12168 {
12169 if (YesNoType.Yes == advertise)
12170 {
12171 this.ParseProgIdElement(child, componentId, advertise, null, description, progId, ref foundExtension, firstProgIdForNestedClass);
12172 }
12173 else if (YesNoType.No == advertise)
12174 {
12175 this.ParseProgIdElement(child, componentId, advertise, classId, description, progId, ref foundExtension, firstProgIdForNestedClass);
12176 }
12177
12178 firstProgIdForNestedClass = YesNoType.No; // any ProgId after this one is definitely not the first.
12179 }
12180 else
12181 {
12182 SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
12183 this.core.OnMessage(WixErrors.ProgIdNestedTooDeep(childSourceLineNumbers));
12184 }
12185 break;
12186 default:
12187 this.core.UnexpectedElement(node, child);
12188 break;
12189 }
12190 }
12191 else
12192 {
12193 this.core.ParseExtensionElement(node, child);
12194 }
12195 }
12196
12197 if (YesNoType.Yes == advertise)
12198 {
12199 if (!this.core.EncounteredError)
12200 {
12201 Row row = this.core.CreateRow(sourceLineNumbers, "ProgId");
12202 row[0] = progId;
12203 row[1] = parent;
12204 row[2] = classId;
12205 row[3] = description;
12206 if (null != icon)
12207 {
12208 row[4] = icon;
12209 this.core.CreateSimpleReference(sourceLineNumbers, "Icon", icon);
12210 }
12211
12212 if (CompilerConstants.IntegerNotSet != iconIndex)
12213 {
12214 row[5] = iconIndex;
12215 }
12216
12217 this.core.EnsureTable(sourceLineNumbers, "Class");
12218 }
12219 }
12220 else if (YesNoType.No == advertise)
12221 {
12222 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, progId, String.Empty, description, componentId);
12223 if (null != classId)
12224 {
12225 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat(progId, "\\CLSID"), String.Empty, classId, componentId);
12226 if (null != parent) // if this is a version independent ProgId
12227 {
12228 if (YesNoType.Yes == firstProgIdForClass)
12229 {
12230 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat("CLSID\\", classId, "\\VersionIndependentProgID"), String.Empty, progId, componentId);
12231 }
12232
12233 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat(progId, "\\CurVer"), String.Empty, parent, componentId);
12234 }
12235 else
12236 {
12237 if (YesNoType.Yes == firstProgIdForClass)
12238 {
12239 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat("CLSID\\", classId, "\\ProgID"), String.Empty, progId, componentId);
12240 }
12241 }
12242 }
12243
12244 if (null != icon) // ProgId's Default Icon
12245 {
12246 this.core.CreateSimpleReference(sourceLineNumbers, "File", icon);
12247
12248 icon = String.Format(CultureInfo.InvariantCulture, "\"[#{0}]\"", icon);
12249
12250 if (CompilerConstants.IntegerNotSet != iconIndex)
12251 {
12252 icon = String.Concat(icon, ",", iconIndex);
12253 }
12254
12255 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat(progId, "\\DefaultIcon"), String.Empty, icon, componentId);
12256 }
12257 }
12258
12259 if (null != noOpen)
12260 {
12261 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, progId, "NoOpen", noOpen, componentId); // ProgId NoOpen name
12262 }
12263
12264 // raise an error for an orphaned ProgId
12265 if (YesNoType.Yes == advertise && !foundExtension && null == parent && null == classId)
12266 {
12267 this.core.OnMessage(WixWarnings.OrphanedProgId(sourceLineNumbers, progId));
12268 }
12269
12270 return progId;
12271 }
12272
12273 /// <summary>
12274 /// Parses a property element.
12275 /// </summary>
12276 /// <param name="node">Element to parse.</param>
12277 private void ParsePropertyElement(XElement node)
12278 {
12279 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
12280 Identifier id = null;
12281 bool admin = false;
12282 bool complianceCheck = false;
12283 bool hidden = false;
12284 bool secure = false;
12285 YesNoType suppressModularization = YesNoType.NotSet;
12286 string value = null;
12287
12288 foreach (XAttribute attrib in node.Attributes())
12289 {
12290 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
12291 {
12292 switch (attrib.Name.LocalName)
12293 {
12294 case "Id":
12295 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
12296 break;
12297 case "Admin":
12298 admin = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
12299 break;
12300 case "ComplianceCheck":
12301 complianceCheck = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
12302 break;
12303 case "Hidden":
12304 hidden = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
12305 break;
12306 case "Secure":
12307 secure = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
12308 break;
12309 case "SuppressModularization":
12310 suppressModularization = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
12311 break;
12312 case "Value":
12313 value = this.core.GetAttributeValue(sourceLineNumbers, attrib);
12314 break;
12315 default:
12316 this.core.UnexpectedAttribute(node, attrib);
12317 break;
12318 }
12319 }
12320 else
12321 {
12322 this.core.ParseExtensionAttribute(node, attrib);
12323 }
12324 }
12325
12326 if (null == id)
12327 {
12328 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
12329 id = Identifier.Invalid;
12330 }
12331 else if ("ProductID" == id.Id)
12332 {
12333 this.core.OnMessage(WixWarnings.ProductIdAuthored(sourceLineNumbers));
12334 }
12335 else if ("SecureCustomProperties" == id.Id || "AdminProperties" == id.Id || "MsiHiddenProperties" == id.Id)
12336 {
12337 this.core.OnMessage(WixErrors.CannotAuthorSpecialProperties(sourceLineNumbers, id.Id));
12338 }
12339
12340 string innerText = this.core.GetTrimmedInnerText(node);
12341 if (null != value)
12342 {
12343 // cannot specify both the value attribute and inner text
12344 if (!String.IsNullOrEmpty(innerText))
12345 {
12346 this.core.OnMessage(WixErrors.IllegalAttributeWithInnerText(sourceLineNumbers, node.Name.LocalName, "Value"));
12347 }
12348 }
12349 else // value attribute not specified, use inner text if any.
12350 {
12351 value = innerText;
12352 }
12353
12354 if ("ErrorDialog" == id.Id)
12355 {
12356 this.core.CreateSimpleReference(sourceLineNumbers, "Dialog", value);
12357 }
12358
12359 foreach (XElement child in node.Elements())
12360 {
12361 if (CompilerCore.WixNamespace == child.Name.Namespace)
12362 {
12363 {
12364 switch (child.Name.LocalName)
12365 {
12366 case "ProductSearch":
12367 this.ParseProductSearchElement(child, id.Id);
12368 secure = true;
12369 break;
12370 default:
12371 // let ParseSearchSignatures handle standard AppSearch children and unknown elements
12372 break;
12373 }
12374 }
12375 }
12376 }
12377
12378 // see if this property is used for appSearch
12379 List<string> signatures = this.ParseSearchSignatures(node);
12380
12381 // If we're doing CCP then there must be a signature.
12382 if (complianceCheck && 0 == signatures.Count)
12383 {
12384 this.core.OnMessage(WixErrors.SearchElementRequiredWithAttribute(sourceLineNumbers, node.Name.LocalName, "ComplianceCheck", "yes"));
12385 }
12386
12387 foreach (string sig in signatures)
12388 {
12389 if (complianceCheck && !this.core.EncounteredError)
12390 {
12391 this.core.CreateRow(sourceLineNumbers, "CCPSearch", new Identifier(sig, AccessModifier.Private));
12392 }
12393
12394 this.AddAppSearch(sourceLineNumbers, id, sig);
12395 }
12396
12397 // If we're doing AppSearch get that setup.
12398 if (0 < signatures.Count)
12399 {
12400 this.AddProperty(sourceLineNumbers, id, value, admin, secure, hidden, false);
12401 }
12402 else // just a normal old property.
12403 {
12404 // If the property value is empty and none of the flags are set, print out a warning that we're ignoring
12405 // the element.
12406 if (String.IsNullOrEmpty(value) && !admin && !secure && !hidden)
12407 {
12408 this.core.OnMessage(WixWarnings.PropertyUseless(sourceLineNumbers, id.Id));
12409 }
12410 else // there is a value and/or a flag set, do that.
12411 {
12412 this.AddProperty(sourceLineNumbers, id, value, admin, secure, hidden, false);
12413 }
12414 }
12415
12416 if (!this.core.EncounteredError && YesNoType.Yes == suppressModularization)
12417 {
12418 this.core.OnMessage(WixWarnings.PropertyModularizationSuppressed(sourceLineNumbers));
12419
12420 this.core.CreateRow(sourceLineNumbers, "WixSuppressModularization", id);
12421 }
12422 }
12423
12424 /// <summary>
12425 /// Parses a RegistryKey element.
12426 /// </summary>
12427 /// <param name="node">Element to parse.</param>
12428 /// <param name="componentId">Identifier for parent component.</param>
12429 /// <param name="root">Root specified when element is nested under another Registry element, otherwise CompilerConstants.IntegerNotSet.</param>
12430 /// <param name="parentKey">Parent key for this Registry element when nested.</param>
12431 /// <param name="win64Component">true if the component is 64-bit.</param>
12432 /// <param name="possibleKeyPath">Identifier of this registry key since it could be the component's keypath.</param>
12433 /// <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>
12434 [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "Changing the way this string normalizes would result " +
12435 "in a change to the way the Registry table is generated, potentially causing extra churn in patches on an MSI built from an older version of WiX. " +
12436 "Furthermore, there is no security hole here, as the strings won't need to make a round trip")]
12437 private YesNoType ParseRegistryKeyElement(XElement node, string componentId, int root, string parentKey, bool win64Component, out string possibleKeyPath)
12438 {
12439 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
12440 Identifier id = null;
12441 string key = parentKey; // default to parent key path
12442 string action = null;
12443 bool forceCreateOnInstall = false;
12444 bool forceDeleteOnUninstall = false;
12445 Wix.RegistryKey.ActionType actionType = Wix.RegistryKey.ActionType.NotSet;
12446 YesNoType keyPath = YesNoType.NotSet;
12447
12448 possibleKeyPath = null;
12449
12450 foreach (XAttribute attrib in node.Attributes())
12451 {
12452 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
12453 {
12454 switch (attrib.Name.LocalName)
12455 {
12456 case "Id":
12457 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
12458 break;
12459 case "Action":
12460 this.core.OnMessage(WixWarnings.DeprecatedRegistryKeyActionAttribute(sourceLineNumbers));
12461 action = this.core.GetAttributeValue(sourceLineNumbers, attrib);
12462 if (0 < action.Length)
12463 {
12464 actionType = Wix.RegistryKey.ParseActionType(action);
12465 switch (actionType)
12466 {
12467 case Wix.RegistryKey.ActionType.create:
12468 forceCreateOnInstall = true;
12469 break;
12470 case Wix.RegistryKey.ActionType.createAndRemoveOnUninstall:
12471 forceCreateOnInstall = true;
12472 forceDeleteOnUninstall = true;
12473 break;
12474 case Wix.RegistryKey.ActionType.none:
12475 break;
12476 default:
12477 this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, action, "create", "createAndRemoveOnUninstall", "none"));
12478 break;
12479 }
12480 }
12481 break;
12482 case "ForceCreateOnInstall":
12483 forceCreateOnInstall = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
12484 break;
12485 case "ForceDeleteOnUninstall":
12486 forceDeleteOnUninstall = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
12487 break;
12488 case "Key":
12489 key = this.core.GetAttributeValue(sourceLineNumbers, attrib);
12490 if (null != parentKey)
12491 {
12492 key = Path.Combine(parentKey, key);
12493 }
12494 break;
12495 case "Root":
12496 if (CompilerConstants.IntegerNotSet != root)
12497 {
12498 this.core.OnMessage(WixErrors.RegistryRootInvalid(sourceLineNumbers));
12499 }
12500
12501 root = this.core.GetAttributeMsidbRegistryRootValue(sourceLineNumbers, attrib, true);
12502 break;
12503 default:
12504 this.core.UnexpectedAttribute(node, attrib);
12505 break;
12506 }
12507 }
12508 else
12509 {
12510 this.core.ParseExtensionAttribute(node, attrib);
12511 }
12512 }
12513
12514 string name = forceCreateOnInstall ? (forceDeleteOnUninstall ? "*" : "+") : (forceDeleteOnUninstall ? "-" : null);
12515
12516 if (forceCreateOnInstall || forceDeleteOnUninstall) // generates a Registry row, so an Id must be present
12517 {
12518 // generate the identifier if it wasn't provided
12519 if (null == id)
12520 {
12521 id = this.core.CreateIdentifier("reg", componentId, root.ToString(CultureInfo.InvariantCulture.NumberFormat), LowercaseOrNull(key), LowercaseOrNull(name));
12522 }
12523 }
12524 else // does not generate a Registry row, so no Id should be present
12525 {
12526 if (null != id)
12527 {
12528 this.core.OnMessage(WixErrors.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.LocalName, "Id", "ForceCreateOnInstall", "ForceDeleteOnUninstall", "yes", true));
12529 }
12530 }
12531
12532 if (CompilerConstants.IntegerNotSet == root)
12533 {
12534 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root"));
12535 root = CompilerConstants.IllegalInteger;
12536 }
12537
12538 if (null == key)
12539 {
12540 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key"));
12541 key = String.Empty; // set the key to something to prevent null reference exceptions
12542 }
12543
12544 foreach (XElement child in node.Elements())
12545 {
12546 if (CompilerCore.WixNamespace == child.Name.Namespace)
12547 {
12548 string possibleChildKeyPath = null;
12549
12550 switch (child.Name.LocalName)
12551 {
12552 case "RegistryKey":
12553 if (YesNoType.Yes == this.ParseRegistryKeyElement(child, componentId, root, key, win64Component, out possibleChildKeyPath))
12554 {
12555 if (YesNoType.Yes == keyPath)
12556 {
12557 this.core.OnMessage(WixErrors.ComponentMultipleKeyPaths(sourceLineNumbers, child.Name.LocalName, "KeyPath", "yes", "File", "RegistryValue", "ODBCDataSource"));
12558 }
12559
12560 possibleKeyPath = possibleChildKeyPath; // the child is the key path
12561 keyPath = YesNoType.Yes;
12562 }
12563 else if (null == possibleKeyPath && null != possibleChildKeyPath)
12564 {
12565 possibleKeyPath = possibleChildKeyPath;
12566 }
12567 break;
12568 case "RegistryValue":
12569 if (YesNoType.Yes == this.ParseRegistryValueElement(child, componentId, root, key, win64Component, out possibleChildKeyPath))
12570 {
12571 if (YesNoType.Yes == keyPath)
12572 {
12573 this.core.OnMessage(WixErrors.ComponentMultipleKeyPaths(sourceLineNumbers, child.Name.LocalName, "KeyPath", "yes", "File", "RegistryValue", "ODBCDataSource"));
12574 }
12575
12576 possibleKeyPath = possibleChildKeyPath; // the child is the key path
12577 keyPath = YesNoType.Yes;
12578 }
12579 else if (null == possibleKeyPath && null != possibleChildKeyPath)
12580 {
12581 possibleKeyPath = possibleChildKeyPath;
12582 }
12583 break;
12584 case "Permission":
12585 if (!forceCreateOnInstall)
12586 {
12587 this.core.OnMessage(WixErrors.UnexpectedElementWithAttributeValue(sourceLineNumbers, node.Name.LocalName, child.Name.LocalName, "ForceCreateOnInstall", "yes"));
12588 }
12589 this.ParsePermissionElement(child, id.Id, "Registry");
12590 break;
12591 case "PermissionEx":
12592 if (!forceCreateOnInstall)
12593 {
12594 this.core.OnMessage(WixErrors.UnexpectedElementWithAttributeValue(sourceLineNumbers, node.Name.LocalName, child.Name.LocalName, "ForceCreateOnInstall", "yes"));
12595 }
12596 this.ParsePermissionExElement(child, id.Id, "Registry");
12597 break;
12598 default:
12599 this.core.UnexpectedElement(node, child);
12600 break;
12601 }
12602 }
12603 else
12604 {
12605 Dictionary<string, string> context = new Dictionary<string, string>() { { "RegistryId", id.Id }, { "ComponentId", componentId }, { "Win64", win64Component.ToString() } };
12606 this.core.ParseExtensionElement(node, child, context);
12607 }
12608 }
12609
12610
12611 if (!this.core.EncounteredError && null != name)
12612 {
12613 Row row = this.core.CreateRow(sourceLineNumbers, "Registry", id);
12614 row[1] = root;
12615 row[2] = key;
12616 row[3] = name;
12617 row[4] = null;
12618 row[5] = componentId;
12619 }
12620
12621 return keyPath;
12622 }
12623
12624 /// <summary>
12625 /// Parses a RegistryValue element.
12626 /// </summary>
12627 /// <param name="node">Element to parse.</param>
12628 /// <param name="componentId">Identifier for parent component.</param>
12629 /// <param name="root">Root specified when element is nested under a RegistryKey element, otherwise CompilerConstants.IntegerNotSet.</param>
12630 /// <param name="parentKey">Root specified when element is nested under a RegistryKey element, otherwise CompilerConstants.IntegerNotSet.</param>
12631 /// <param name="win64Component">true if the component is 64-bit.</param>
12632 /// <param name="possibleKeyPath">Identifier of this registry key since it could be the component's keypath.</param>
12633 /// <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>
12634 [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "Changing the way this string normalizes would result " +
12635 "in a change to the way the Registry table is generated, potentially causing extra churn in patches on an MSI built from an older version of WiX. " +
12636 "Furthermore, there is no security hole here, as the strings won't need to make a round trip")]
12637 private YesNoType ParseRegistryValueElement(XElement node, string componentId, int root, string parentKey, bool win64Component, out string possibleKeyPath)
12638 {
12639 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
12640 Identifier id = null;
12641 string key = parentKey; // default to parent key path
12642 string name = null;
12643 string value = null;
12644 string type = null;
12645 Wix.RegistryValue.TypeType typeType = Wix.RegistryValue.TypeType.NotSet;
12646 string action = null;
12647 Wix.RegistryValue.ActionType actionType = Wix.RegistryValue.ActionType.NotSet;
12648 YesNoType keyPath = YesNoType.NotSet;
12649 bool couldBeKeyPath = true; // assume that this is a regular registry key that could become the key path
12650
12651 possibleKeyPath = null;
12652
12653 foreach (XAttribute attrib in node.Attributes())
12654 {
12655 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
12656 {
12657 switch (attrib.Name.LocalName)
12658 {
12659 case "Id":
12660 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
12661 break;
12662 case "Action":
12663 action = this.core.GetAttributeValue(sourceLineNumbers, attrib);
12664 if (0 < action.Length)
12665 {
12666 if (!Wix.RegistryValue.TryParseActionType(action, out actionType))
12667 {
12668 this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, action, "append", "prepend", "write"));
12669 }
12670 }
12671 break;
12672 case "Key":
12673 key = this.core.GetAttributeValue(sourceLineNumbers, attrib);
12674 if (null != parentKey)
12675 {
12676 if (parentKey.EndsWith("\\", StringComparison.Ordinal))
12677 {
12678 key = String.Concat(parentKey, key);
12679 }
12680 else
12681 {
12682 key = String.Concat(parentKey, "\\", key);
12683 }
12684 }
12685 break;
12686 case "KeyPath":
12687 keyPath = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
12688 break;
12689 case "Name":
12690 name = this.core.GetAttributeValue(sourceLineNumbers, attrib);
12691 break;
12692 case "Root":
12693 if (CompilerConstants.IntegerNotSet != root)
12694 {
12695 this.core.OnMessage(WixErrors.RegistryRootInvalid(sourceLineNumbers));
12696 }
12697
12698 root = this.core.GetAttributeMsidbRegistryRootValue(sourceLineNumbers, attrib, true);
12699 break;
12700 case "Type":
12701 type = this.core.GetAttributeValue(sourceLineNumbers, attrib);
12702 if (0 < type.Length)
12703 {
12704 if (!Wix.RegistryValue.TryParseTypeType(type, out typeType))
12705 {
12706 this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, type, "binary", "expandable", "integer", "multiString", "string"));
12707 }
12708 }
12709 break;
12710 case "Value":
12711 value = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty);
12712 break;
12713 default:
12714 this.core.UnexpectedAttribute(node, attrib);
12715 break;
12716 }
12717 }
12718 else
12719 {
12720 this.core.ParseExtensionAttribute(node, attrib);
12721 }
12722 }
12723
12724 // generate the identifier if it wasn't provided
12725 if (null == id)
12726 {
12727 id = this.core.CreateIdentifier("reg", componentId, root.ToString(CultureInfo.InvariantCulture.NumberFormat), LowercaseOrNull(key), LowercaseOrNull(name));
12728 }
12729
12730 if ((Wix.RegistryValue.ActionType.append == actionType || Wix.RegistryValue.ActionType.prepend == actionType) &&
12731 Wix.RegistryValue.TypeType.multiString != typeType)
12732 {
12733 this.core.OnMessage(WixErrors.IllegalAttributeValueWithoutOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Action", action, "Type", "multiString"));
12734 }
12735
12736 if (null == key)
12737 {
12738 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key"));
12739 }
12740
12741 if (CompilerConstants.IntegerNotSet == root)
12742 {
12743 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root"));
12744 }
12745
12746 if (null == type)
12747 {
12748 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Type"));
12749 }
12750
12751 foreach (XElement child in node.Elements())
12752 {
12753 if (CompilerCore.WixNamespace == child.Name.Namespace)
12754 {
12755 switch (child.Name.LocalName)
12756 {
12757 case "MultiStringValue":
12758 if (Wix.RegistryValue.TypeType.multiString != typeType && null != value)
12759 {
12760 this.core.OnMessage(WixErrors.RegistryMultipleValuesWithoutMultiString(sourceLineNumbers, node.Name.LocalName, "Value", child.Name.LocalName, "Type"));
12761 }
12762 else if (null == value)
12763 {
12764 value = Common.GetInnerText(child);
12765 }
12766 else
12767 {
12768 value = String.Concat(value, "[~]", Common.GetInnerText(child));
12769 }
12770 break;
12771 case "Permission":
12772 this.ParsePermissionElement(child, id.Id, "Registry");
12773 break;
12774 case "PermissionEx":
12775 this.ParsePermissionExElement(child, id.Id, "Registry");
12776 break;
12777 default:
12778 this.core.UnexpectedElement(node, child);
12779 break;
12780 }
12781 }
12782 else
12783 {
12784 Dictionary<string, string> context = new Dictionary<string, string>() { { "RegistryId", id.Id }, { "ComponentId", componentId }, { "Win64", win64Component.ToString() } };
12785 this.core.ParseExtensionElement(node, child, context);
12786 }
12787 }
12788
12789
12790 switch (typeType)
12791 {
12792 case Wix.RegistryValue.TypeType.binary:
12793 value = String.Concat("#x", value);
12794 break;
12795 case Wix.RegistryValue.TypeType.expandable:
12796 value = String.Concat("#%", value);
12797 break;
12798 case Wix.RegistryValue.TypeType.integer:
12799 value = String.Concat("#", value);
12800 break;
12801 case Wix.RegistryValue.TypeType.multiString:
12802 switch (actionType)
12803 {
12804 case Wix.RegistryValue.ActionType.append:
12805 value = String.Concat("[~]", value);
12806 break;
12807 case Wix.RegistryValue.ActionType.prepend:
12808 value = String.Concat(value, "[~]");
12809 break;
12810 case Wix.RegistryValue.ActionType.write:
12811 default:
12812 if (null != value && -1 == value.IndexOf("[~]", StringComparison.Ordinal))
12813 {
12814 value = String.Format(CultureInfo.InvariantCulture, "[~]{0}[~]", value);
12815 }
12816 break;
12817 }
12818 break;
12819 case Wix.RegistryValue.TypeType.@string:
12820 // escape the leading '#' character for string registry keys
12821 if (null != value && value.StartsWith("#", StringComparison.Ordinal))
12822 {
12823 value = String.Concat("#", value);
12824 }
12825 break;
12826 }
12827
12828 // value may be set by child MultiStringValue elements, so it must be checked here
12829 if (null == value)
12830 {
12831 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value"));
12832 }
12833 else if (0 == value.Length && ("+" == name || "-" == name || "*" == name)) // prevent accidental authoring of special name values
12834 {
12835 this.core.OnMessage(WixErrors.RegistryNameValueIncorrect(sourceLineNumbers, node.Name.LocalName, "Name", name));
12836 }
12837
12838 if (!this.core.EncounteredError)
12839 {
12840 Row row = this.core.CreateRow(sourceLineNumbers, "Registry", id);
12841 row[1] = root;
12842 row[2] = key;
12843 row[3] = name;
12844 row[4] = value;
12845 row[5] = componentId;
12846 }
12847
12848 // If this was just a regular registry key (that could be the key path)
12849 // and no child registry key set the possible key path, let's make this
12850 // Registry/@Id a possible key path.
12851 if (couldBeKeyPath && null == possibleKeyPath)
12852 {
12853 possibleKeyPath = id.Id;
12854 }
12855
12856 return keyPath;
12857 }
12858
12859 /// <summary>
12860 /// Parses a RemoveRegistryKey element.
12861 /// </summary>
12862 /// <param name="node">The element to parse.</param>
12863 /// <param name="componentId">The component identifier of the parent element.</param>
12864 [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "Changing the way this string normalizes would result " +
12865 "in a change to the way the Registry table is generated, potentially causing extra churn in patches on an MSI built from an older version of WiX. " +
12866 "Furthermore, there is no security hole here, as the strings won't need to make a round trip")]
12867 private void ParseRemoveRegistryKeyElement(XElement node, string componentId)
12868 {
12869 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
12870 Identifier id = null;
12871 string action = null;
12872 Wix.RemoveRegistryKey.ActionType actionType = Wix.RemoveRegistryKey.ActionType.NotSet;
12873 string key = null;
12874 string name = "-";
12875 int root = CompilerConstants.IntegerNotSet;
12876
12877 foreach (XAttribute attrib in node.Attributes())
12878 {
12879 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
12880 {
12881 switch (attrib.Name.LocalName)
12882 {
12883 case "Id":
12884 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
12885 break;
12886 case "Action":
12887 action = this.core.GetAttributeValue(sourceLineNumbers, attrib);
12888 if (0 < action.Length)
12889 {
12890 if (!Wix.RemoveRegistryKey.TryParseActionType(action, out actionType))
12891 {
12892 this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, action, "removeOnInstall", "removeOnUninstall"));
12893 }
12894 }
12895 break;
12896 case "Key":
12897 key = this.core.GetAttributeValue(sourceLineNumbers, attrib);
12898 break;
12899 case "Root":
12900 root = this.core.GetAttributeMsidbRegistryRootValue(sourceLineNumbers, attrib, true);
12901 break;
12902 default:
12903 this.core.UnexpectedAttribute(node, attrib);
12904 break;
12905 }
12906 }
12907 else
12908 {
12909 this.core.ParseExtensionAttribute(node, attrib);
12910 }
12911 }
12912
12913 // generate the identifier if it wasn't provided
12914 if (null == id)
12915 {
12916 id = this.core.CreateIdentifier("reg", componentId, root.ToString(CultureInfo.InvariantCulture.NumberFormat), LowercaseOrNull(key), LowercaseOrNull(name));
12917 }
12918
12919 if (null == action)
12920 {
12921 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Action"));
12922 }
12923
12924 if (null == key)
12925 {
12926 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key"));
12927 }
12928
12929 if (CompilerConstants.IntegerNotSet == root)
12930 {
12931 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root"));
12932 }
12933
12934 this.core.ParseForExtensionElements(node);
12935
12936 if (!this.core.EncounteredError)
12937 {
12938 Row row = this.core.CreateRow(sourceLineNumbers, (Wix.RemoveRegistryKey.ActionType.removeOnUninstall == actionType ? "Registry" : "RemoveRegistry"), id);
12939 row[1] = root;
12940 row[2] = key;
12941 row[3] = name;
12942 if (Wix.RemoveRegistryKey.ActionType.removeOnUninstall == actionType) // Registry table
12943 {
12944 row[4] = null;
12945 row[5] = componentId;
12946 }
12947 else // RemoveRegistry table
12948 {
12949 row[4] = componentId;
12950 }
12951 }
12952 }
12953
12954 /// <summary>
12955 /// Parses a RemoveRegistryValue element.
12956 /// </summary>
12957 /// <param name="node">The element to parse.</param>
12958 /// <param name="componentId">The component identifier of the parent element.</param>
12959 [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "Changing the way this string normalizes would result " +
12960 "in a change to the way the Registry table is generated, potentially causing extra churn in patches on an MSI built from an older version of WiX. " +
12961 "Furthermore, there is no security hole here, as the strings won't need to make a round trip")]
12962 private void ParseRemoveRegistryValueElement(XElement node, string componentId)
12963 {
12964 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
12965 Identifier id = null;
12966 string key = null;
12967 string name = null;
12968 int root = CompilerConstants.IntegerNotSet;
12969
12970 foreach (XAttribute attrib in node.Attributes())
12971 {
12972 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
12973 {
12974 switch (attrib.Name.LocalName)
12975 {
12976 case "Id":
12977 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
12978 break;
12979 case "Key":
12980 key = this.core.GetAttributeValue(sourceLineNumbers, attrib);
12981 break;
12982 case "Name":
12983 name = this.core.GetAttributeValue(sourceLineNumbers, attrib);
12984 break;
12985 case "Root":
12986 root = this.core.GetAttributeMsidbRegistryRootValue(sourceLineNumbers, attrib, true);
12987 break;
12988 default:
12989 this.core.UnexpectedAttribute(node, attrib);
12990 break;
12991 }
12992 }
12993 else
12994 {
12995 this.core.ParseExtensionAttribute(node, attrib);
12996 }
12997 }
12998
12999 // generate the identifier if it wasn't provided
13000 if (null == id)
13001 {
13002 id = this.core.CreateIdentifier("reg", componentId, root.ToString(CultureInfo.InvariantCulture.NumberFormat), LowercaseOrNull(key), LowercaseOrNull(name));
13003 }
13004
13005 if (null == key)
13006 {
13007 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key"));
13008 }
13009
13010 if (CompilerConstants.IntegerNotSet == root)
13011 {
13012 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Root"));
13013 }
13014
13015 this.core.ParseForExtensionElements(node);
13016
13017 if (!this.core.EncounteredError)
13018 {
13019 Row row = this.core.CreateRow(sourceLineNumbers, "RemoveRegistry", id);
13020 row[1] = root;
13021 row[2] = key;
13022 row[3] = name;
13023 row[4] = componentId;
13024 }
13025 }
13026
13027 /// <summary>
13028 /// Parses a remove file element.
13029 /// </summary>
13030 /// <param name="node">Element to parse.</param>
13031 /// <param name="componentId">Identifier of parent component.</param>
13032 /// <param name="parentDirectory">Identifier of the parent component's directory.</param>
13033 private void ParseRemoveFileElement(XElement node, string componentId, string parentDirectory)
13034 {
13035 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
13036 Identifier id = null;
13037 string directory = null;
13038 string name = null;
13039 int on = CompilerConstants.IntegerNotSet;
13040 string property = null;
13041 string shortName = null;
13042
13043 foreach (XAttribute attrib in node.Attributes())
13044 {
13045 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
13046 {
13047 switch (attrib.Name.LocalName)
13048 {
13049 case "Id":
13050 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
13051 break;
13052 case "Directory":
13053 directory = this.core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, parentDirectory);
13054 break;
13055 case "Name":
13056 name = this.core.GetAttributeLongFilename(sourceLineNumbers, attrib, true);
13057 break;
13058 case "On":
13059 Wix.InstallUninstallType onValue = this.core.GetAttributeInstallUninstallValue(sourceLineNumbers, attrib);
13060 switch (onValue)
13061 {
13062 case Wix.InstallUninstallType.install:
13063 on = 1;
13064 break;
13065 case Wix.InstallUninstallType.uninstall:
13066 on = 2;
13067 break;
13068 case Wix.InstallUninstallType.both:
13069 on = 3;
13070 break;
13071 default:
13072 on = CompilerConstants.IllegalInteger;
13073 break;
13074 }
13075 break;
13076 case "Property":
13077 property = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
13078 break;
13079 case "ShortName":
13080 shortName = this.core.GetAttributeShortFilename(sourceLineNumbers, attrib, true);
13081 break;
13082 default:
13083 this.core.UnexpectedAttribute(node, attrib);
13084 break;
13085 }
13086 }
13087 else
13088 {
13089 this.core.ParseExtensionAttribute(node, attrib);
13090 }
13091 }
13092
13093 if (null == name)
13094 {
13095 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
13096 }
13097 else if (0 < name.Length)
13098 {
13099 if (this.core.IsValidShortFilename(name, true))
13100 {
13101 if (null == shortName)
13102 {
13103 shortName = name;
13104 name = null;
13105 }
13106 else
13107 {
13108 this.core.OnMessage(WixErrors.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Name", name, "ShortName"));
13109 }
13110 }
13111 else if (null == shortName) // generate a short file name.
13112 {
13113 shortName = this.core.CreateShortName(name, true, true, node.Name.LocalName, componentId);
13114 }
13115 }
13116
13117 if (CompilerConstants.IntegerNotSet == on)
13118 {
13119 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "On"));
13120 on = CompilerConstants.IllegalInteger;
13121 }
13122
13123 if (null != directory && null != property)
13124 {
13125 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "Directory", directory));
13126 }
13127
13128 if (null == id)
13129 {
13130 id = this.core.CreateIdentifier("rmf", directory ?? property ?? parentDirectory, LowercaseOrNull(shortName), LowercaseOrNull(name), on.ToString());
13131 }
13132
13133 this.core.ParseForExtensionElements(node);
13134
13135 if (!this.core.EncounteredError)
13136 {
13137 Row row = this.core.CreateRow(sourceLineNumbers, "RemoveFile", id);
13138 row[1] = componentId;
13139 row[2] = GetMsiFilenameValue(shortName, name);
13140 if (null != directory)
13141 {
13142 row[3] = directory;
13143 }
13144 else if (null != property)
13145 {
13146 row[3] = property;
13147 }
13148 else
13149 {
13150 row[3] = parentDirectory;
13151 }
13152 row[4] = on;
13153 }
13154 }
13155
13156 /// <summary>
13157 /// Parses a RemoveFolder element.
13158 /// </summary>
13159 /// <param name="node">Element to parse.</param>
13160 /// <param name="componentId">Identifier of parent component.</param>
13161 /// <param name="parentDirectory">Identifier of parent component's directory.</param>
13162 private void ParseRemoveFolderElement(XElement node, string componentId, string parentDirectory)
13163 {
13164 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
13165 Identifier id = null;
13166 string directory = null;
13167 int on = CompilerConstants.IntegerNotSet;
13168 string property = null;
13169
13170 foreach (XAttribute attrib in node.Attributes())
13171 {
13172 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
13173 {
13174 switch (attrib.Name.LocalName)
13175 {
13176 case "Id":
13177 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
13178 break;
13179 case "Directory":
13180 directory = this.core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, parentDirectory);
13181 break;
13182 case "On":
13183 Wix.InstallUninstallType onValue = this.core.GetAttributeInstallUninstallValue(sourceLineNumbers, attrib);
13184 switch (onValue)
13185 {
13186 case Wix.InstallUninstallType.install:
13187 on = 1;
13188 break;
13189 case Wix.InstallUninstallType.uninstall:
13190 on = 2;
13191 break;
13192 case Wix.InstallUninstallType.both:
13193 on = 3;
13194 break;
13195 default:
13196 on = CompilerConstants.IllegalInteger;
13197 break;
13198 }
13199 break;
13200 case "Property":
13201 property = this.core.GetAttributeValue(sourceLineNumbers, attrib);
13202 break;
13203 default:
13204 this.core.UnexpectedAttribute(node, attrib);
13205 break;
13206 }
13207 }
13208 else
13209 {
13210 this.core.ParseExtensionAttribute(node, attrib);
13211 }
13212 }
13213
13214 if (CompilerConstants.IntegerNotSet == on)
13215 {
13216 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "On"));
13217 on = CompilerConstants.IllegalInteger;
13218 }
13219
13220 if (null != directory && null != property)
13221 {
13222 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "Directory", directory));
13223 }
13224
13225 if (null == id)
13226 {
13227 id = this.core.CreateIdentifier("rmf", directory ?? property ?? parentDirectory, on.ToString());
13228 }
13229
13230 this.core.ParseForExtensionElements(node);
13231
13232 if (!this.core.EncounteredError)
13233 {
13234 Row row = this.core.CreateRow(sourceLineNumbers, "RemoveFile", id);
13235 row[1] = componentId;
13236 row[2] = null;
13237 if (null != directory)
13238 {
13239 row[3] = directory;
13240 }
13241 else if (null != property)
13242 {
13243 row[3] = property;
13244 }
13245 else
13246 {
13247 row[3] = parentDirectory;
13248 }
13249 row[4] = on;
13250 }
13251 }
13252
13253 /// <summary>
13254 /// Parses a reserve cost element.
13255 /// </summary>
13256 /// <param name="node">Element to parse.</param>
13257 /// <param name="componentId">Identifier of parent component.</param>
13258 /// <param name="directoryId">Optional and default identifier of referenced directory.</param>
13259 private void ParseReserveCostElement(XElement node, string componentId, string directoryId)
13260 {
13261 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
13262 Identifier id = null;
13263 int runFromSource = CompilerConstants.IntegerNotSet;
13264 int runLocal = CompilerConstants.IntegerNotSet;
13265
13266 foreach (XAttribute attrib in node.Attributes())
13267 {
13268 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
13269 {
13270 switch (attrib.Name.LocalName)
13271 {
13272 case "Id":
13273 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
13274 break;
13275 case "Directory":
13276 directoryId = this.core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, directoryId);
13277 break;
13278 case "RunFromSource":
13279 runFromSource = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, int.MaxValue);
13280 break;
13281 case "RunLocal":
13282 runLocal = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, int.MaxValue);
13283 break;
13284 default:
13285 this.core.UnexpectedAttribute(node, attrib);
13286 break;
13287 }
13288 }
13289 else
13290 {
13291 this.core.ParseExtensionAttribute(node, attrib);
13292 }
13293 }
13294
13295 if (null == id)
13296 {
13297 id = this.core.CreateIdentifier("rc", componentId, directoryId);
13298 }
13299
13300 if (CompilerConstants.IntegerNotSet == runFromSource)
13301 {
13302 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "RunFromSource"));
13303 }
13304
13305 if (CompilerConstants.IntegerNotSet == runLocal)
13306 {
13307 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "RunLocal"));
13308 }
13309
13310 this.core.ParseForExtensionElements(node);
13311
13312 if (!this.core.EncounteredError)
13313 {
13314 Row row = this.core.CreateRow(sourceLineNumbers, "ReserveCost", id);
13315 row[1] = componentId;
13316 row[2] = directoryId;
13317 row[3] = runLocal;
13318 row[4] = runFromSource;
13319 }
13320 }
13321
13322 /// <summary>
13323 /// Parses a sequence element.
13324 /// </summary>
13325 /// <param name="node">Element to parse.</param>
13326 /// <param name="sequenceTable">Name of sequence table.</param>
13327 private void ParseSequenceElement(XElement node, string sequenceTable)
13328 {
13329 // use the proper table name internally
13330 if ("AdvertiseExecuteSequence" == sequenceTable)
13331 {
13332 sequenceTable = "AdvtExecuteSequence";
13333 }
13334
13335 // Parse each action in the sequence.
13336 foreach (XElement child in node.Elements())
13337 {
13338 SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
13339 string actionName = child.Name.LocalName;
13340 string afterAction = null;
13341 string beforeAction = null;
13342 string condition = null;
13343 bool customAction = "Custom" == actionName;
13344 bool overridable = false;
13345 int exitSequence = CompilerConstants.IntegerNotSet;
13346 int sequence = CompilerConstants.IntegerNotSet;
13347 bool showDialog = "Show" == actionName;
13348 bool specialAction = "InstallExecute" == actionName || "InstallExecuteAgain" == actionName || "RemoveExistingProducts" == actionName || "DisableRollback" == actionName || "ScheduleReboot" == actionName || "ForceReboot" == actionName || "ResolveSource" == actionName;
13349 bool specialStandardAction = "AppSearch" == actionName || "CCPSearch" == actionName || "RMCCPSearch" == actionName || "LaunchConditions" == actionName || "FindRelatedProducts" == actionName;
13350 bool suppress = false;
13351
13352 foreach (XAttribute attrib in child.Attributes())
13353 {
13354 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
13355 {
13356 switch (attrib.Name.LocalName)
13357 {
13358 case "Action":
13359 if (customAction)
13360 {
13361 actionName = this.core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib);
13362 this.core.CreateSimpleReference(childSourceLineNumbers, "CustomAction", actionName);
13363 }
13364 else
13365 {
13366 this.core.UnexpectedAttribute(child, attrib);
13367 }
13368 break;
13369 case "After":
13370 if (customAction || showDialog || specialAction || specialStandardAction)
13371 {
13372 afterAction = this.core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib);
13373 this.core.CreateSimpleReference(childSourceLineNumbers, "WixAction", sequenceTable, afterAction);
13374 }
13375 else
13376 {
13377 this.core.UnexpectedAttribute(child, attrib);
13378 }
13379 break;
13380 case "Before":
13381 if (customAction || showDialog || specialAction || specialStandardAction)
13382 {
13383 beforeAction = this.core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib);
13384 this.core.CreateSimpleReference(childSourceLineNumbers, "WixAction", sequenceTable, beforeAction);
13385 }
13386 else
13387 {
13388 this.core.UnexpectedAttribute(child, attrib);
13389 }
13390 break;
13391 case "Dialog":
13392 if (showDialog)
13393 {
13394 actionName = this.core.GetAttributeIdentifierValue(childSourceLineNumbers, attrib);
13395 this.core.CreateSimpleReference(childSourceLineNumbers, "Dialog", actionName);
13396 }
13397 else
13398 {
13399 this.core.UnexpectedAttribute(child, attrib);
13400 }
13401 break;
13402 case "OnExit":
13403 if (customAction || showDialog || specialAction)
13404 {
13405 Wix.ExitType exitValue = this.core.GetAttributeExitValue(childSourceLineNumbers, attrib);
13406 switch (exitValue)
13407 {
13408 case Wix.ExitType.success:
13409 exitSequence = -1;
13410 break;
13411 case Wix.ExitType.cancel:
13412 exitSequence = -2;
13413 break;
13414 case Wix.ExitType.error:
13415 exitSequence = -3;
13416 break;
13417 case Wix.ExitType.suspend:
13418 exitSequence = -4;
13419 break;
13420 }
13421 }
13422 else
13423 {
13424 this.core.UnexpectedAttribute(child, attrib);
13425 }
13426 break;
13427 case "Overridable":
13428 overridable = YesNoType.Yes == this.core.GetAttributeYesNoValue(childSourceLineNumbers, attrib);
13429 break;
13430 case "Sequence":
13431 sequence = this.core.GetAttributeIntegerValue(childSourceLineNumbers, attrib, 1, short.MaxValue);
13432 break;
13433 case "Suppress":
13434 suppress = YesNoType.Yes == this.core.GetAttributeYesNoValue(childSourceLineNumbers, attrib);
13435 break;
13436 default:
13437 this.core.UnexpectedAttribute(node, attrib);
13438 break;
13439 }
13440 }
13441 else
13442 {
13443 this.core.ParseExtensionAttribute(node, attrib);
13444 }
13445 }
13446
13447
13448 // Get the condition from the inner text of the element.
13449 condition = this.core.GetConditionInnerText(child);
13450
13451 if (customAction && "Custom" == actionName)
13452 {
13453 this.core.OnMessage(WixErrors.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Action"));
13454 }
13455 else if (showDialog && "Show" == actionName)
13456 {
13457 this.core.OnMessage(WixErrors.ExpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Dialog"));
13458 }
13459
13460 if (CompilerConstants.IntegerNotSet != sequence)
13461 {
13462 if (CompilerConstants.IntegerNotSet != exitSequence)
13463 {
13464 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(childSourceLineNumbers, child.Name.LocalName, "Sequence", "OnExit"));
13465 }
13466 else if (null != beforeAction || null != afterAction)
13467 {
13468 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(childSourceLineNumbers, child.Name.LocalName, "Sequence", "Before", "After"));
13469 }
13470 }
13471 else // sequence not specified use OnExit (which may also be not set).
13472 {
13473 sequence = exitSequence;
13474 }
13475
13476 if (null != beforeAction && null != afterAction)
13477 {
13478 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(childSourceLineNumbers, child.Name.LocalName, "After", "Before"));
13479 }
13480 else if ((customAction || showDialog || specialAction) && !suppress && CompilerConstants.IntegerNotSet == sequence && null == beforeAction && null == afterAction)
13481 {
13482 this.core.OnMessage(WixErrors.NeedSequenceBeforeOrAfter(childSourceLineNumbers, child.Name.LocalName));
13483 }
13484
13485 // action that is scheduled to occur before/after itself
13486 if (beforeAction == actionName)
13487 {
13488 this.core.OnMessage(WixErrors.ActionScheduledRelativeToItself(childSourceLineNumbers, child.Name.LocalName, "Before", beforeAction));
13489 }
13490 else if (afterAction == actionName)
13491 {
13492 this.core.OnMessage(WixErrors.ActionScheduledRelativeToItself(childSourceLineNumbers, child.Name.LocalName, "After", afterAction));
13493 }
13494
13495 // normal standard actions cannot be set overridable by the user (since they are overridable by default)
13496 if (overridable && WindowsInstallerStandard.IsStandardAction(actionName) && !specialAction)
13497 {
13498 this.core.OnMessage(WixErrors.UnexpectedAttribute(childSourceLineNumbers, child.Name.LocalName, "Overridable"));
13499 }
13500
13501 // suppress cannot be specified at the same time as Before, After, or Sequence
13502 if (suppress && (null != afterAction || null != beforeAction || CompilerConstants.IntegerNotSet != sequence || overridable))
13503 {
13504 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttributes(childSourceLineNumbers, child.Name.LocalName, "Suppress", "Before", "After", "Sequence", "Overridable"));
13505 }
13506
13507 this.core.ParseForExtensionElements(child);
13508
13509 // add the row and any references needed
13510 if (!this.core.EncounteredError)
13511 {
13512 if (suppress)
13513 {
13514 Row row = this.core.CreateRow(childSourceLineNumbers, "WixSuppressAction");
13515 row[0] = sequenceTable;
13516 row[1] = actionName;
13517 }
13518 else
13519 {
13520 Row row = this.core.CreateRow(childSourceLineNumbers, "WixAction");
13521 row[0] = sequenceTable;
13522 row[1] = actionName;
13523 row[2] = condition;
13524 if (CompilerConstants.IntegerNotSet != sequence)
13525 {
13526 row[3] = sequence;
13527 }
13528 row[4] = beforeAction;
13529 row[5] = afterAction;
13530 row[6] = overridable ? 1 : 0;
13531 }
13532 }
13533 }
13534 }
13535
13536
13537 /// <summary>
13538 /// Parses a service config element.
13539 /// </summary>
13540 /// <param name="node">Element to parse.</param>
13541 /// <param name="componentId">Identifier of parent component.</param>
13542 /// <param name="serviceName">Optional element containing parent's service name.</param>
13543 private void ParseServiceConfigElement(XElement node, string componentId, string serviceName)
13544 {
13545 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
13546 Identifier id = null;
13547 string delayedAutoStart = null;
13548 string failureActionsWhen = null;
13549 int events = 0;
13550 string name = serviceName;
13551 string preShutdownDelay = null;
13552 string requiredPrivileges = null;
13553 string sid = null;
13554
13555 this.core.OnMessage(WixWarnings.ServiceConfigFamilyNotSupported(sourceLineNumbers, node.Name.LocalName));
13556
13557 foreach (XAttribute attrib in node.Attributes())
13558 {
13559 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
13560 {
13561 switch (attrib.Name.LocalName)
13562 {
13563 case "Id":
13564 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
13565 break;
13566 case "DelayedAutoStart":
13567 delayedAutoStart = this.core.GetAttributeValue(sourceLineNumbers, attrib);
13568 if (0 < delayedAutoStart.Length)
13569 {
13570 switch (delayedAutoStart)
13571 {
13572 case "no":
13573 delayedAutoStart = "0";
13574 break;
13575 case "yes":
13576 delayedAutoStart = "1";
13577 break;
13578 default:
13579 // allow everything else to pass through that are hopefully "formatted" Properties.
13580 break;
13581 }
13582 }
13583 break;
13584 case "FailureActionsWhen":
13585 failureActionsWhen = this.core.GetAttributeValue(sourceLineNumbers, attrib);
13586 if (0 < failureActionsWhen.Length)
13587 {
13588 switch (failureActionsWhen)
13589 {
13590 case "failedToStop":
13591 failureActionsWhen = "0";
13592 break;
13593 case "failedToStopOrReturnedError":
13594 failureActionsWhen = "1";
13595 break;
13596 default:
13597 // allow everything else to pass through that are hopefully "formatted" Properties.
13598 break;
13599 }
13600 }
13601 break;
13602 case "OnInstall":
13603 YesNoType install = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
13604 if (YesNoType.Yes == install)
13605 {
13606 events |= MsiInterop.MsidbServiceConfigEventInstall;
13607 }
13608 break;
13609 case "OnReinstall":
13610 YesNoType reinstall = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
13611 if (YesNoType.Yes == reinstall)
13612 {
13613 events |= MsiInterop.MsidbServiceConfigEventReinstall;
13614 }
13615 break;
13616 case "OnUninstall":
13617 YesNoType uninstall = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
13618 if (YesNoType.Yes == uninstall)
13619 {
13620 events |= MsiInterop.MsidbServiceConfigEventUninstall;
13621 }
13622 break;
13623 default:
13624 this.core.UnexpectedAttribute(node, attrib);
13625 break;
13626 case "PreShutdownDelay":
13627 preShutdownDelay = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty);
13628 break;
13629 case "ServiceName":
13630 if (!String.IsNullOrEmpty(serviceName))
13631 {
13632 this.core.OnMessage(WixErrors.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "ServiceInstall"));
13633 }
13634
13635 name = this.core.GetAttributeValue(sourceLineNumbers, attrib);
13636 break;
13637 case "ServiceSid":
13638 sid = this.core.GetAttributeValue(sourceLineNumbers, attrib);
13639 if (0 < sid.Length)
13640 {
13641 switch (sid)
13642 {
13643 case "none":
13644 sid = "0";
13645 break;
13646 case "restricted":
13647 sid = "3";
13648 break;
13649 case "unrestricted":
13650 sid = "1";
13651 break;
13652 default:
13653 // allow everything else to pass through that are hopefully "formatted" Properties.
13654 break;
13655 }
13656 }
13657 break;
13658 }
13659 }
13660 else
13661 {
13662 this.core.ParseExtensionAttribute(node, attrib);
13663 }
13664 }
13665
13666 // Get the ServiceConfig required privilegs.
13667 foreach (XElement child in node.Elements())
13668 {
13669 if (CompilerCore.WixNamespace == child.Name.Namespace)
13670 {
13671 switch (child.Name.LocalName)
13672 {
13673 case "RequiredPrivilege":
13674 string privilege = this.core.GetTrimmedInnerText(child);
13675 switch (privilege)
13676 {
13677 case "assignPrimaryToken":
13678 privilege = "SeAssignPrimaryTokenPrivilege";
13679 break;
13680 case "audit":
13681 privilege = "SeAuditPrivilege";
13682 break;
13683 case "backup":
13684 privilege = "SeBackupPrivilege";
13685 break;
13686 case "changeNotify":
13687 privilege = "SeChangeNotifyPrivilege";
13688 break;
13689 case "createGlobal":
13690 privilege = "SeCreateGlobalPrivilege";
13691 break;
13692 case "createPagefile":
13693 privilege = "SeCreatePagefilePrivilege";
13694 break;
13695 case "createPermanent":
13696 privilege = "SeCreatePermanentPrivilege";
13697 break;
13698 case "createSymbolicLink":
13699 privilege = "SeCreateSymbolicLinkPrivilege";
13700 break;
13701 case "createToken":
13702 privilege = "SeCreateTokenPrivilege";
13703 break;
13704 case "debug":
13705 privilege = "SeDebugPrivilege";
13706 break;
13707 case "enableDelegation":
13708 privilege = "SeEnableDelegationPrivilege";
13709 break;
13710 case "impersonate":
13711 privilege = "SeImpersonatePrivilege";
13712 break;
13713 case "increaseBasePriority":
13714 privilege = "SeIncreaseBasePriorityPrivilege";
13715 break;
13716 case "increaseQuota":
13717 privilege = "SeIncreaseQuotaPrivilege";
13718 break;
13719 case "increaseWorkingSet":
13720 privilege = "SeIncreaseWorkingSetPrivilege";
13721 break;
13722 case "loadDriver":
13723 privilege = "SeLoadDriverPrivilege";
13724 break;
13725 case "lockMemory":
13726 privilege = "SeLockMemoryPrivilege";
13727 break;
13728 case "machineAccount":
13729 privilege = "SeMachineAccountPrivilege";
13730 break;
13731 case "manageVolume":
13732 privilege = "SeManageVolumePrivilege";
13733 break;
13734 case "profileSingleProcess":
13735 privilege = "SeProfileSingleProcessPrivilege";
13736 break;
13737 case "relabel":
13738 privilege = "SeRelabelPrivilege";
13739 break;
13740 case "remoteShutdown":
13741 privilege = "SeRemoteShutdownPrivilege";
13742 break;
13743 case "restore":
13744 privilege = "SeRestorePrivilege";
13745 break;
13746 case "security":
13747 privilege = "SeSecurityPrivilege";
13748 break;
13749 case "shutdown":
13750 privilege = "SeShutdownPrivilege";
13751 break;
13752 case "syncAgent":
13753 privilege = "SeSyncAgentPrivilege";
13754 break;
13755 case "systemEnvironment":
13756 privilege = "SeSystemEnvironmentPrivilege";
13757 break;
13758 case "systemProfile":
13759 privilege = "SeSystemProfilePrivilege";
13760 break;
13761 case "systemTime":
13762 case "modifySystemTime":
13763 privilege = "SeSystemtimePrivilege";
13764 break;
13765 case "takeOwnership":
13766 privilege = "SeTakeOwnershipPrivilege";
13767 break;
13768 case "tcb":
13769 case "trustedComputerBase":
13770 privilege = "SeTcbPrivilege";
13771 break;
13772 case "timeZone":
13773 case "modifyTimeZone":
13774 privilege = "SeTimeZonePrivilege";
13775 break;
13776 case "trustedCredManAccess":
13777 case "trustedCredentialManagerAccess":
13778 privilege = "SeTrustedCredManAccessPrivilege";
13779 break;
13780 case "undock":
13781 privilege = "SeUndockPrivilege";
13782 break;
13783 case "unsolicitedInput":
13784 privilege = "SeUnsolicitedInputPrivilege";
13785 break;
13786 default:
13787 // allow everything else to pass through that are hopefully "formatted" Properties.
13788 break;
13789 }
13790
13791 if (null != requiredPrivileges)
13792 {
13793 requiredPrivileges = String.Concat(requiredPrivileges, "[~]");
13794 }
13795 requiredPrivileges = String.Concat(requiredPrivileges, privilege);
13796 break;
13797 default:
13798 this.core.UnexpectedElement(node, child);
13799 break;
13800 }
13801 }
13802 else
13803 {
13804 this.core.ParseExtensionElement(node, child);
13805 }
13806 }
13807
13808 if (String.IsNullOrEmpty(name))
13809 {
13810 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ServiceName"));
13811 }
13812 else if (null == id)
13813 {
13814 id = this.core.CreateIdentifierFromFilename(name);
13815 }
13816
13817 if (0 == events)
13818 {
13819 this.core.OnMessage(WixErrors.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "OnInstall", "OnReinstall", "OnUninstall"));
13820 }
13821
13822 if (String.IsNullOrEmpty(delayedAutoStart) && String.IsNullOrEmpty(failureActionsWhen) && String.IsNullOrEmpty(preShutdownDelay) && String.IsNullOrEmpty(requiredPrivileges) && String.IsNullOrEmpty(sid))
13823 {
13824 this.core.OnMessage(WixErrors.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "DelayedAutoStart", "FailureActionsWhen", "PreShutdownDelay", "ServiceSid", "RequiredPrivilege"));
13825 }
13826
13827 if (!this.core.EncounteredError)
13828 {
13829 if (!String.IsNullOrEmpty(delayedAutoStart))
13830 {
13831 Row row = this.core.CreateRow(sourceLineNumbers, "MsiServiceConfig", new Identifier(String.Concat(id.Id, ".DS"), id.Access));
13832 row[1] = name;
13833 row[2] = events;
13834 row[3] = 3;
13835 row[4] = delayedAutoStart;
13836 row[5] = componentId;
13837 }
13838
13839 if (!String.IsNullOrEmpty(failureActionsWhen))
13840 {
13841 Row row = this.core.CreateRow(sourceLineNumbers, "MsiServiceConfig", new Identifier(String.Concat(id.Id, ".FA"), id.Access));
13842 row[1] = name;
13843 row[2] = events;
13844 row[3] = 4;
13845 row[4] = failureActionsWhen;
13846 row[5] = componentId;
13847 }
13848
13849 if (!String.IsNullOrEmpty(sid))
13850 {
13851 Row row = this.core.CreateRow(sourceLineNumbers, "MsiServiceConfig", new Identifier(String.Concat(id.Id, ".SS"), id.Access));
13852 row[1] = name;
13853 row[2] = events;
13854 row[3] = 5;
13855 row[4] = sid;
13856 row[5] = componentId;
13857 }
13858
13859 if (!String.IsNullOrEmpty(requiredPrivileges))
13860 {
13861 Row row = this.core.CreateRow(sourceLineNumbers, "MsiServiceConfig", new Identifier(String.Concat(id.Id, ".RP"), id.Access));
13862 row[1] = name;
13863 row[2] = events;
13864 row[3] = 6;
13865 row[4] = requiredPrivileges;
13866 row[5] = componentId;
13867 }
13868
13869 if (!String.IsNullOrEmpty(preShutdownDelay))
13870 {
13871 Row row = this.core.CreateRow(sourceLineNumbers, "MsiServiceConfig", new Identifier(String.Concat(id.Id, ".PD"), id.Access));
13872 row[1] = name;
13873 row[2] = events;
13874 row[3] = 7;
13875 row[4] = preShutdownDelay;
13876 row[5] = componentId;
13877 }
13878 }
13879 }
13880
13881 /// <summary>
13882 /// Parses a service config failure actions element.
13883 /// </summary>
13884 /// <param name="node">Element to parse.</param>
13885 /// <param name="componentId">Identifier of parent component.</param>
13886 /// <param name="serviceName">Optional element containing parent's service name.</param>
13887 private void ParseServiceConfigFailureActionsElement(XElement node, string componentId, string serviceName)
13888 {
13889 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
13890 Identifier id = null;
13891 int events = 0;
13892 string name = serviceName;
13893 int resetPeriod = CompilerConstants.IntegerNotSet;
13894 string rebootMessage = null;
13895 string command = null;
13896 string actions = null;
13897 string actionsDelays = null;
13898
13899 this.core.OnMessage(WixWarnings.ServiceConfigFamilyNotSupported(sourceLineNumbers, node.Name.LocalName));
13900
13901 foreach (XAttribute attrib in node.Attributes())
13902 {
13903 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
13904 {
13905 switch (attrib.Name.LocalName)
13906 {
13907 case "Id":
13908 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
13909 break;
13910 case "Command":
13911 command = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty);
13912 break;
13913 case "OnInstall":
13914 YesNoType install = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
13915 if (YesNoType.Yes == install)
13916 {
13917 events |= MsiInterop.MsidbServiceConfigEventInstall;
13918 }
13919 break;
13920 case "OnReinstall":
13921 YesNoType reinstall = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
13922 if (YesNoType.Yes == reinstall)
13923 {
13924 events |= MsiInterop.MsidbServiceConfigEventReinstall;
13925 }
13926 break;
13927 case "OnUninstall":
13928 YesNoType uninstall = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
13929 if (YesNoType.Yes == uninstall)
13930 {
13931 events |= MsiInterop.MsidbServiceConfigEventUninstall;
13932 }
13933 break;
13934 case "RebootMessage":
13935 rebootMessage = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty);
13936 break;
13937 case "ResetPeriod":
13938 resetPeriod = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, int.MaxValue);
13939 break;
13940 case "ServiceName":
13941 if (!String.IsNullOrEmpty(serviceName))
13942 {
13943 this.core.OnMessage(WixErrors.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "ServiceInstall"));
13944 }
13945
13946 name = this.core.GetAttributeValue(sourceLineNumbers, attrib);
13947 break;
13948 default:
13949 this.core.UnexpectedAttribute(node, attrib);
13950 break;
13951 }
13952 }
13953 else
13954 {
13955 this.core.ParseExtensionAttribute(node, attrib);
13956 }
13957 }
13958
13959 // Get the ServiceConfigFailureActions actions.
13960 foreach (XElement child in node.Elements())
13961 {
13962 if (CompilerCore.WixNamespace == child.Name.Namespace)
13963 {
13964 switch (child.Name.LocalName)
13965 {
13966 case "Failure":
13967 string action = null;
13968 string delay = null;
13969 SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
13970
13971 foreach (XAttribute childAttrib in child.Attributes())
13972 {
13973 if (String.IsNullOrEmpty(childAttrib.Name.NamespaceName) || CompilerCore.WixNamespace == childAttrib.Name.Namespace)
13974 {
13975 switch (childAttrib.Name.LocalName)
13976 {
13977 case "Action":
13978 action = this.core.GetAttributeValue(childSourceLineNumbers, childAttrib);
13979 switch (action)
13980 {
13981 case "none":
13982 action = "0";
13983 break;
13984 case "restartComputer":
13985 action = "2";
13986 break;
13987 case "restartService":
13988 action = "1";
13989 break;
13990 case "runCommand":
13991 action = "3";
13992 break;
13993 default:
13994 // allow everything else to pass through that are hopefully "formatted" Properties.
13995 break;
13996 }
13997 break;
13998 case "Delay":
13999 delay = this.core.GetAttributeValue(childSourceLineNumbers, childAttrib);
14000 break;
14001 default:
14002 this.core.UnexpectedAttribute(child, childAttrib);
14003 break;
14004 }
14005 }
14006 }
14007
14008 if (String.IsNullOrEmpty(action))
14009 {
14010 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, child.Name.LocalName, "Action"));
14011 }
14012
14013 if (String.IsNullOrEmpty(delay))
14014 {
14015 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, child.Name.LocalName, "Delay"));
14016 }
14017
14018 if (!String.IsNullOrEmpty(actions))
14019 {
14020 actions = String.Concat(actions, "[~]");
14021 }
14022 actions = String.Concat(actions, action);
14023
14024 if (!String.IsNullOrEmpty(actionsDelays))
14025 {
14026 actionsDelays = String.Concat(actionsDelays, "[~]");
14027 }
14028 actionsDelays = String.Concat(actionsDelays, delay);
14029 break;
14030 default:
14031 this.core.UnexpectedElement(node, child);
14032 break;
14033 }
14034 }
14035 else
14036 {
14037 this.core.ParseExtensionElement(node, child);
14038 }
14039 }
14040
14041 if (String.IsNullOrEmpty(name))
14042 {
14043 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ServiceName"));
14044 }
14045 else if (null == id)
14046 {
14047 id = this.core.CreateIdentifierFromFilename(name);
14048 }
14049
14050 if (0 == events)
14051 {
14052 this.core.OnMessage(WixErrors.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "OnInstall", "OnReinstall", "OnUninstall"));
14053 }
14054
14055 if (!this.core.EncounteredError)
14056 {
14057 Row row = this.core.CreateRow(sourceLineNumbers, "MsiServiceConfigFailureActions", id);
14058 row[1] = name;
14059 row[2] = events;
14060 if (CompilerConstants.IntegerNotSet != resetPeriod)
14061 {
14062 row[3] = resetPeriod;
14063 }
14064 row[4] = rebootMessage ?? "[~]";
14065 row[5] = command ?? "[~]";
14066 row[6] = actions;
14067 row[7] = actionsDelays;
14068 row[8] = componentId;
14069 }
14070 }
14071
14072 /// <summary>
14073 /// Parses a service control element.
14074 /// </summary>
14075 /// <param name="node">Element to parse.</param>
14076 /// <param name="componentId">Identifier of parent component.</param>
14077 private void ParseServiceControlElement(XElement node, string componentId)
14078 {
14079 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
14080 string arguments = null;
14081 int events = 0; // default is to do nothing
14082 Identifier id = null;
14083 string name = null;
14084 YesNoType wait = YesNoType.NotSet;
14085
14086 foreach (XAttribute attrib in node.Attributes())
14087 {
14088 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
14089 {
14090 switch (attrib.Name.LocalName)
14091 {
14092 case "Id":
14093 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
14094 break;
14095 case "Name":
14096 name = this.core.GetAttributeValue(sourceLineNumbers, attrib);
14097 break;
14098 case "Remove":
14099 Wix.InstallUninstallType removeValue = this.core.GetAttributeInstallUninstallValue(sourceLineNumbers, attrib);
14100 switch (removeValue)
14101 {
14102 case Wix.InstallUninstallType.install:
14103 events |= MsiInterop.MsidbServiceControlEventDelete;
14104 break;
14105 case Wix.InstallUninstallType.uninstall:
14106 events |= MsiInterop.MsidbServiceControlEventUninstallDelete;
14107 break;
14108 case Wix.InstallUninstallType.both:
14109 events |= MsiInterop.MsidbServiceControlEventDelete | MsiInterop.MsidbServiceControlEventUninstallDelete;
14110 break;
14111 }
14112 break;
14113 case "Start":
14114 Wix.InstallUninstallType startValue = this.core.GetAttributeInstallUninstallValue(sourceLineNumbers, attrib);
14115 switch (startValue)
14116 {
14117 case Wix.InstallUninstallType.install:
14118 events |= MsiInterop.MsidbServiceControlEventStart;
14119 break;
14120 case Wix.InstallUninstallType.uninstall:
14121 events |= MsiInterop.MsidbServiceControlEventUninstallStart;
14122 break;
14123 case Wix.InstallUninstallType.both:
14124 events |= MsiInterop.MsidbServiceControlEventStart | MsiInterop.MsidbServiceControlEventUninstallStart;
14125 break;
14126 }
14127 break;
14128 case "Stop":
14129 Wix.InstallUninstallType stopValue = this.core.GetAttributeInstallUninstallValue(sourceLineNumbers, attrib);
14130 switch (stopValue)
14131 {
14132 case Wix.InstallUninstallType.install:
14133 events |= MsiInterop.MsidbServiceControlEventStop;
14134 break;
14135 case Wix.InstallUninstallType.uninstall:
14136 events |= MsiInterop.MsidbServiceControlEventUninstallStop;
14137 break;
14138 case Wix.InstallUninstallType.both:
14139 events |= MsiInterop.MsidbServiceControlEventStop | MsiInterop.MsidbServiceControlEventUninstallStop;
14140 break;
14141 }
14142 break;
14143 case "Wait":
14144 wait = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
14145 break;
14146 default:
14147 this.core.UnexpectedAttribute(node, attrib);
14148 break;
14149 }
14150 }
14151 else
14152 {
14153 this.core.ParseExtensionAttribute(node, attrib);
14154 }
14155 }
14156
14157 if (null == id)
14158 {
14159 id = this.core.CreateIdentifierFromFilename(name);
14160 }
14161
14162 if (null == name)
14163 {
14164 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
14165 }
14166
14167 // get the ServiceControl arguments
14168 foreach (XElement child in node.Elements())
14169 {
14170 if (CompilerCore.WixNamespace == child.Name.Namespace)
14171 {
14172 switch (child.Name.LocalName)
14173 {
14174 case "ServiceArgument":
14175 if (null != arguments)
14176 {
14177 arguments = String.Concat(arguments, "[~]");
14178 }
14179 arguments = String.Concat(arguments, this.core.GetTrimmedInnerText(child));
14180 break;
14181 default:
14182 this.core.UnexpectedElement(node, child);
14183 break;
14184 }
14185 }
14186 else
14187 {
14188 this.core.ParseExtensionElement(node, child);
14189 }
14190 }
14191
14192 if (!this.core.EncounteredError)
14193 {
14194 Row row = this.core.CreateRow(sourceLineNumbers, "ServiceControl", id);
14195 row[1] = name;
14196 row[2] = events;
14197 row[3] = arguments;
14198 if (YesNoType.NotSet != wait)
14199 {
14200 row[4] = YesNoType.Yes == wait ? 1 : 0;
14201 }
14202 row[5] = componentId;
14203 }
14204 }
14205
14206 /// <summary>
14207 /// Parses a service dependency element.
14208 /// </summary>
14209 /// <param name="node">Element to parse.</param>
14210 /// <returns>Parsed sevice dependency name.</returns>
14211 private string ParseServiceDependencyElement(XElement node)
14212 {
14213 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
14214 string dependency = null;
14215 bool group = false;
14216
14217 foreach (XAttribute attrib in node.Attributes())
14218 {
14219 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
14220 {
14221 switch (attrib.Name.LocalName)
14222 {
14223 case "Id":
14224 dependency = this.core.GetAttributeValue(sourceLineNumbers, attrib);
14225 break;
14226 case "Group":
14227 group = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
14228 break;
14229 default:
14230 this.core.UnexpectedAttribute(node, attrib);
14231 break;
14232 }
14233 }
14234 else
14235 {
14236 this.core.ParseExtensionAttribute(node, attrib);
14237 }
14238 }
14239
14240 if (null == dependency)
14241 {
14242 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
14243 }
14244
14245 this.core.ParseForExtensionElements(node);
14246
14247 return group ? String.Concat("+", dependency) : dependency;
14248 }
14249
14250 /// <summary>
14251 /// Parses a service install element.
14252 /// </summary>
14253 /// <param name="node">Element to parse.</param>
14254 /// <param name="componentId">Identifier of parent component.</param>
14255 private void ParseServiceInstallElement(XElement node, string componentId, bool win64Component)
14256 {
14257 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
14258 Identifier id = null;
14259 string account = null;
14260 string arguments = null;
14261 string dependencies = null;
14262 string description = null;
14263 string displayName = null;
14264 bool eraseDescription = false;
14265 int errorbits = 0;
14266 string loadOrderGroup = null;
14267 string name = null;
14268 string password = null;
14269 int startType = 0;
14270 int typebits = 0;
14271
14272 foreach (XAttribute attrib in node.Attributes())
14273 {
14274 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
14275 {
14276 switch (attrib.Name.LocalName)
14277 {
14278 case "Id":
14279 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
14280 break;
14281 case "Account":
14282 account = this.core.GetAttributeValue(sourceLineNumbers, attrib);
14283 break;
14284 case "Arguments":
14285 arguments = this.core.GetAttributeValue(sourceLineNumbers, attrib);
14286 break;
14287 case "Description":
14288 description = this.core.GetAttributeValue(sourceLineNumbers, attrib);
14289 break;
14290 case "DisplayName":
14291 displayName = this.core.GetAttributeValue(sourceLineNumbers, attrib);
14292 break;
14293 case "EraseDescription":
14294 eraseDescription = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
14295 break;
14296 case "ErrorControl":
14297 string errorControlValue = this.core.GetAttributeValue(sourceLineNumbers, attrib);
14298 if (0 < errorControlValue.Length)
14299 {
14300 Wix.ServiceInstall.ErrorControlType errorControlType = Wix.ServiceInstall.ParseErrorControlType(errorControlValue);
14301 switch (errorControlType)
14302 {
14303 case Wix.ServiceInstall.ErrorControlType.ignore:
14304 errorbits |= MsiInterop.MsidbServiceInstallErrorIgnore;
14305 break;
14306 case Wix.ServiceInstall.ErrorControlType.normal:
14307 errorbits |= MsiInterop.MsidbServiceInstallErrorNormal;
14308 break;
14309 case Wix.ServiceInstall.ErrorControlType.critical:
14310 errorbits |= MsiInterop.MsidbServiceInstallErrorCritical;
14311 break;
14312 default:
14313 this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, errorControlValue, "ignore", "normal", "critical"));
14314 break;
14315 }
14316 }
14317 break;
14318 case "Interactive":
14319 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
14320 {
14321 typebits |= MsiInterop.MsidbServiceInstallInteractive;
14322 }
14323 break;
14324 case "LoadOrderGroup":
14325 loadOrderGroup = this.core.GetAttributeValue(sourceLineNumbers, attrib);
14326 break;
14327 case "Name":
14328 name = this.core.GetAttributeValue(sourceLineNumbers, attrib);
14329 break;
14330 case "Password":
14331 password = this.core.GetAttributeValue(sourceLineNumbers, attrib);
14332 break;
14333 case "Start":
14334 string startValue = this.core.GetAttributeValue(sourceLineNumbers, attrib);
14335 if (0 < startValue.Length)
14336 {
14337 Wix.ServiceInstall.StartType start = Wix.ServiceInstall.ParseStartType(startValue);
14338 switch (start)
14339 {
14340 case Wix.ServiceInstall.StartType.auto:
14341 startType = MsiInterop.MsidbServiceInstallAutoStart;
14342 break;
14343 case Wix.ServiceInstall.StartType.demand:
14344 startType = MsiInterop.MsidbServiceInstallDemandStart;
14345 break;
14346 case Wix.ServiceInstall.StartType.disabled:
14347 startType = MsiInterop.MsidbServiceInstallDisabled;
14348 break;
14349 case Wix.ServiceInstall.StartType.boot:
14350 case Wix.ServiceInstall.StartType.system:
14351 this.core.OnMessage(WixErrors.ValueNotSupported(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, startValue));
14352 break;
14353 default:
14354 this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, startValue, "auto", "demand", "disabled"));
14355 break;
14356 }
14357 }
14358 break;
14359 case "Type":
14360 string typeValue = this.core.GetAttributeValue(sourceLineNumbers, attrib);
14361 if (0 < typeValue.Length)
14362 {
14363 Wix.ServiceInstall.TypeType typeType = Wix.ServiceInstall.ParseTypeType(typeValue);
14364 switch (typeType)
14365 {
14366 case Wix.ServiceInstall.TypeType.ownProcess:
14367 typebits |= MsiInterop.MsidbServiceInstallOwnProcess;
14368 break;
14369 case Wix.ServiceInstall.TypeType.shareProcess:
14370 typebits |= MsiInterop.MsidbServiceInstallShareProcess;
14371 break;
14372 case Wix.ServiceInstall.TypeType.kernelDriver:
14373 case Wix.ServiceInstall.TypeType.systemDriver:
14374 this.core.OnMessage(WixErrors.ValueNotSupported(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, typeValue));
14375 break;
14376 default:
14377 this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, node.Name.LocalName, typeValue, "ownProcess", "shareProcess"));
14378 break;
14379 }
14380 }
14381 break;
14382 case "Vital":
14383 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
14384 {
14385 errorbits |= MsiInterop.MsidbServiceInstallErrorControlVital;
14386 }
14387 break;
14388 default:
14389 this.core.UnexpectedAttribute(node, attrib);
14390 break;
14391 }
14392 }
14393 else
14394 {
14395 this.core.ParseExtensionAttribute(node, attrib);
14396 }
14397 }
14398
14399 if (String.IsNullOrEmpty(name))
14400 {
14401 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
14402 }
14403 else if (null == id)
14404 {
14405 id = this.core.CreateIdentifierFromFilename(name);
14406 }
14407
14408 if (0 == startType)
14409 {
14410 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Start"));
14411 }
14412
14413 if (eraseDescription)
14414 {
14415 description = "[~]";
14416 }
14417
14418 // get the ServiceInstall dependencies and config
14419 foreach (XElement child in node.Elements())
14420 {
14421 if (CompilerCore.WixNamespace == child.Name.Namespace)
14422 {
14423 switch (child.Name.LocalName)
14424 {
14425 case "PermissionEx":
14426 this.ParsePermissionExElement(child, id.Id, "ServiceInstall");
14427 break;
14428 case "ServiceConfig":
14429 this.ParseServiceConfigElement(child, componentId, name);
14430 break;
14431 case "ServiceConfigFailureActions":
14432 this.ParseServiceConfigFailureActionsElement(child, componentId, name);
14433 break;
14434 case "ServiceDependency":
14435 dependencies = String.Concat(dependencies, this.ParseServiceDependencyElement(child), "[~]");
14436 break;
14437 default:
14438 this.core.UnexpectedElement(node, child);
14439 break;
14440 }
14441 }
14442 else
14443 {
14444 Dictionary<string, string> context = new Dictionary<string, string>() { { "ServiceInstallId", id.Id }, { "ServiceInstallName", name }, { "ServiceInstallComponentId", componentId }, { "Win64", win64Component.ToString() } };
14445 this.core.ParseExtensionElement(node, child, context);
14446 }
14447 }
14448
14449 if (null != dependencies)
14450 {
14451 dependencies = String.Concat(dependencies, "[~]");
14452 }
14453
14454 if (!this.core.EncounteredError)
14455 {
14456 Row row = this.core.CreateRow(sourceLineNumbers, "ServiceInstall", id);
14457 row[1] = name;
14458 row[2] = displayName;
14459 row[3] = typebits;
14460 row[4] = startType;
14461 row[5] = errorbits;
14462 row[6] = loadOrderGroup;
14463 row[7] = dependencies;
14464 row[8] = account;
14465 row[9] = password;
14466 row[10] = arguments;
14467 row[11] = componentId;
14468 row[12] = description;
14469 }
14470 }
14471
14472 /// <summary>
14473 /// Parses a SetDirectory element.
14474 /// </summary>
14475 /// <param name="node">Element to parse.</param>
14476 private void ParseSetDirectoryElement(XElement node)
14477 {
14478 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
14479 string actionName = null;
14480 string id = null;
14481 string condition = null;
14482 string[] sequences = new string[] { "InstallUISequence", "InstallExecuteSequence" }; // default to "both"
14483 int extraBits = 0;
14484 string value = null;
14485
14486 foreach (XAttribute attrib in node.Attributes())
14487 {
14488 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
14489 {
14490 switch (attrib.Name.LocalName)
14491 {
14492 case "Action":
14493 actionName = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
14494 break;
14495 case "Id":
14496 id = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
14497 this.core.CreateSimpleReference(sourceLineNumbers, "Directory", id);
14498 break;
14499 case "Sequence":
14500 string sequenceValue = this.core.GetAttributeValue(sourceLineNumbers, attrib);
14501 if (0 < sequenceValue.Length)
14502 {
14503 Wix.SequenceType sequenceType = Wix.Enums.ParseSequenceType(sequenceValue);
14504 switch (sequenceType)
14505 {
14506 case Wix.SequenceType.execute:
14507 sequences = new string[] { "InstallExecuteSequence" };
14508 break;
14509 case Wix.SequenceType.ui:
14510 sequences = new string[] { "InstallUISequence" };
14511 break;
14512 case Wix.SequenceType.first:
14513 extraBits = MsiInterop.MsidbCustomActionTypeFirstSequence;
14514 // default puts it in both sequence which is what we want
14515 break;
14516 case Wix.SequenceType.both:
14517 // default so no work necessary.
14518 break;
14519 default:
14520 this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, sequenceValue, "execute", "ui", "both"));
14521 break;
14522 }
14523 }
14524 break;
14525 case "Value":
14526 value = this.core.GetAttributeValue(sourceLineNumbers, attrib);
14527 break;
14528 default:
14529 this.core.UnexpectedAttribute(node, attrib);
14530 break;
14531 }
14532 }
14533 else
14534 {
14535 this.core.ParseExtensionAttribute(node, attrib);
14536 }
14537 }
14538
14539 condition = this.core.GetConditionInnerText(node);
14540
14541 if (null == id)
14542 {
14543 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
14544 }
14545 else if (String.IsNullOrEmpty(actionName))
14546 {
14547 actionName = String.Concat("Set", id);
14548 }
14549
14550 if (null == value)
14551 {
14552 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value"));
14553 }
14554
14555 this.core.ParseForExtensionElements(node);
14556
14557 // add the row and any references needed
14558 if (!this.core.EncounteredError)
14559 {
14560 Row row = this.core.CreateRow(sourceLineNumbers, "CustomAction");
14561 row[0] = actionName;
14562 row[1] = MsiInterop.MsidbCustomActionTypeProperty | MsiInterop.MsidbCustomActionTypeTextData | extraBits;
14563 row[2] = id;
14564 row[3] = value;
14565
14566 foreach (string sequence in sequences)
14567 {
14568 Row sequenceRow = this.core.CreateRow(sourceLineNumbers, "WixAction");
14569 sequenceRow[0] = sequence;
14570 sequenceRow[1] = actionName;
14571 sequenceRow[2] = condition;
14572 // no explicit sequence
14573 // no before action
14574 sequenceRow[5] = "CostInitialize";
14575 sequenceRow[6] = 0; // not overridable
14576 }
14577 }
14578 }
14579
14580 /// <summary>
14581 /// Parses a SetProperty element.
14582 /// </summary>
14583 /// <param name="node">Element to parse.</param>
14584 private void ParseSetPropertyElement(XElement node)
14585 {
14586 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
14587 string actionName = null;
14588 string id = null;
14589 string afterAction = null;
14590 string beforeAction = null;
14591 string condition = null;
14592 string[] sequences = new string[] { "InstallUISequence", "InstallExecuteSequence" }; // default to "both"
14593 int extraBits = 0;
14594 string value = null;
14595
14596 foreach (XAttribute attrib in node.Attributes())
14597 {
14598 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
14599 {
14600 switch (attrib.Name.LocalName)
14601 {
14602 case "Action":
14603 actionName = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
14604 break;
14605 case "Id":
14606 id = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
14607 break;
14608 case "After":
14609 afterAction = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
14610 break;
14611 case "Before":
14612 beforeAction = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
14613 break;
14614 case "Sequence":
14615 string sequenceValue = this.core.GetAttributeValue(sourceLineNumbers, attrib);
14616 if (0 < sequenceValue.Length)
14617 {
14618 Wix.SequenceType sequenceType = Wix.Enums.ParseSequenceType(sequenceValue);
14619 switch (sequenceType)
14620 {
14621 case Wix.SequenceType.execute:
14622 sequences = new string[] { "InstallExecuteSequence" };
14623 break;
14624 case Wix.SequenceType.ui:
14625 sequences = new string[] { "InstallUISequence" };
14626 break;
14627 case Wix.SequenceType.first:
14628 extraBits = MsiInterop.MsidbCustomActionTypeFirstSequence;
14629 // default puts it in both sequence which is what we want
14630 break;
14631 case Wix.SequenceType.both:
14632 // default so no work necessary.
14633 break;
14634 default:
14635 this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, sequenceValue, "execute", "ui", "both"));
14636 break;
14637 }
14638 }
14639 break;
14640 case "Value":
14641 value = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty);
14642 break;
14643 default:
14644 this.core.UnexpectedAttribute(node, attrib);
14645 break;
14646 }
14647 }
14648 else
14649 {
14650 this.core.ParseExtensionAttribute(node, attrib);
14651 }
14652 }
14653
14654 condition = this.core.GetConditionInnerText(node);
14655
14656 if (null == id)
14657 {
14658 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
14659 }
14660 else if (String.IsNullOrEmpty(actionName))
14661 {
14662 actionName = String.Concat("Set", id);
14663 }
14664
14665 if (null == value)
14666 {
14667 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value"));
14668 }
14669
14670 if (null != beforeAction && null != afterAction)
14671 {
14672 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "After", "Before"));
14673 }
14674 else if (null == beforeAction && null == afterAction)
14675 {
14676 this.core.OnMessage(WixErrors.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "After", "Before", "Id"));
14677 }
14678
14679 this.core.ParseForExtensionElements(node);
14680
14681 // add the row and any references needed
14682 if (!this.core.EncounteredError)
14683 {
14684 // action that is scheduled to occur before/after itself
14685 if (beforeAction == actionName)
14686 {
14687 this.core.OnMessage(WixErrors.ActionScheduledRelativeToItself(sourceLineNumbers, node.Name.LocalName, "Before", beforeAction));
14688 }
14689 else if (afterAction == actionName)
14690 {
14691 this.core.OnMessage(WixErrors.ActionScheduledRelativeToItself(sourceLineNumbers, node.Name.LocalName, "After", afterAction));
14692 }
14693
14694 Row row = this.core.CreateRow(sourceLineNumbers, "CustomAction");
14695 row[0] = actionName;
14696 row[1] = MsiInterop.MsidbCustomActionTypeProperty | MsiInterop.MsidbCustomActionTypeTextData | extraBits;
14697 row[2] = id;
14698 row[3] = value;
14699
14700 foreach (string sequence in sequences)
14701 {
14702 Row sequenceRow = this.core.CreateRow(sourceLineNumbers, "WixAction");
14703 sequenceRow[0] = sequence;
14704 sequenceRow[1] = actionName;
14705 sequenceRow[2] = condition;
14706 // no explicit sequence
14707 sequenceRow[4] = beforeAction;
14708 sequenceRow[5] = afterAction;
14709 sequenceRow[6] = 0; // not overridable
14710
14711 if (null != beforeAction)
14712 {
14713 if (WindowsInstallerStandard.IsStandardAction(beforeAction))
14714 {
14715 this.core.CreateSimpleReference(sourceLineNumbers, "WixAction", sequence, beforeAction);
14716 }
14717 else
14718 {
14719 this.core.CreateSimpleReference(sourceLineNumbers, "CustomAction", beforeAction);
14720 }
14721 }
14722
14723 if (null != afterAction)
14724 {
14725 if (WindowsInstallerStandard.IsStandardAction(afterAction))
14726 {
14727 this.core.CreateSimpleReference(sourceLineNumbers, "WixAction", sequence, afterAction);
14728 }
14729 else
14730 {
14731 this.core.CreateSimpleReference(sourceLineNumbers, "CustomAction", afterAction);
14732 }
14733 }
14734 }
14735 }
14736 }
14737
14738 /// <summary>
14739 /// Parses a SFP catalog element.
14740 /// </summary>
14741 /// <param name="node">Element to parse.</param>
14742 /// <param name="parentSFPCatalog">Parent SFPCatalog.</param>
14743 private void ParseSFPFileElement(XElement node, string parentSFPCatalog)
14744 {
14745 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
14746 string id = null;
14747
14748 foreach (XAttribute attrib in node.Attributes())
14749 {
14750 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
14751 {
14752 switch (attrib.Name.LocalName)
14753 {
14754 case "Id":
14755 id = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
14756 break;
14757 default:
14758 this.core.UnexpectedAttribute(node, attrib);
14759 break;
14760 }
14761 }
14762 else
14763 {
14764 this.core.ParseExtensionAttribute(node, attrib);
14765 }
14766 }
14767
14768 if (null == id)
14769 {
14770 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
14771 }
14772
14773 this.core.ParseForExtensionElements(node);
14774
14775 if (!this.core.EncounteredError)
14776 {
14777 Row row = this.core.CreateRow(sourceLineNumbers, "FileSFPCatalog");
14778 row[0] = id;
14779 row[1] = parentSFPCatalog;
14780 }
14781 }
14782
14783 /// <summary>
14784 /// Parses a SFP catalog element.
14785 /// </summary>
14786 /// <param name="node">Element to parse.</param>
14787 /// <param name="parentSFPCatalog">Parent SFPCatalog.</param>
14788 private void ParseSFPCatalogElement(XElement node, ref string parentSFPCatalog)
14789 {
14790 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
14791 string parentName = null;
14792 string dependency = null;
14793 string name = null;
14794 string sourceFile = null;
14795
14796 foreach (XAttribute attrib in node.Attributes())
14797 {
14798 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
14799 {
14800 switch (attrib.Name.LocalName)
14801 {
14802 case "Dependency":
14803 dependency = this.core.GetAttributeValue(sourceLineNumbers, attrib);
14804 break;
14805 case "Name":
14806 name = this.core.GetAttributeShortFilename(sourceLineNumbers, attrib, false);
14807 parentSFPCatalog = name;
14808 break;
14809 case "SourceFile":
14810 sourceFile = this.core.GetAttributeValue(sourceLineNumbers, attrib);
14811 break;
14812 default:
14813 this.core.UnexpectedAttribute(node, attrib);
14814 break;
14815 }
14816 }
14817 else
14818 {
14819 this.core.ParseExtensionAttribute(node, attrib);
14820 }
14821 }
14822
14823 if (null == name)
14824 {
14825 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
14826 }
14827
14828 if (null == sourceFile)
14829 {
14830 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile"));
14831 }
14832
14833 foreach (XElement child in node.Elements())
14834 {
14835 if (CompilerCore.WixNamespace == child.Name.Namespace)
14836 {
14837 switch (child.Name.LocalName)
14838 {
14839 case "SFPCatalog":
14840 this.ParseSFPCatalogElement(child, ref parentName);
14841 if (null != dependency && parentName == dependency)
14842 {
14843 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Dependency"));
14844 }
14845 dependency = parentName;
14846 break;
14847 case "SFPFile":
14848 this.ParseSFPFileElement(child, name);
14849 break;
14850 default:
14851 this.core.UnexpectedElement(node, child);
14852 break;
14853 }
14854 }
14855 else
14856 {
14857 this.core.ParseExtensionElement(node, child);
14858 }
14859 }
14860
14861 if (null == dependency)
14862 {
14863 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Dependency"));
14864 }
14865
14866 if (!this.core.EncounteredError)
14867 {
14868 Row row = this.core.CreateRow(sourceLineNumbers, "SFPCatalog");
14869 row[0] = name;
14870 row[1] = sourceFile;
14871 row[2] = dependency;
14872 }
14873 }
14874
14875 /// <summary>
14876 /// Parses a shortcut element.
14877 /// </summary>
14878 /// <param name="node">Element to parse.</param>
14879 /// <param name="componentId">Identifer for parent component.</param>
14880 /// <param name="parentElementLocalName">Local name of parent element.</param>
14881 /// <param name="defaultTarget">Default identifier of parent (which is usually the target).</param>
14882 /// <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>
14883 private void ParseShortcutElement(XElement node, string componentId, string parentElementLocalName, string defaultTarget, YesNoType parentKeyPath)
14884 {
14885 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
14886 Identifier id = null;
14887 bool advertise = false;
14888 string arguments = null;
14889 string description = null;
14890 string descriptionResourceDll = null;
14891 int descriptionResourceId = CompilerConstants.IntegerNotSet;
14892 string directory = null;
14893 string displayResourceDll = null;
14894 int displayResourceId = CompilerConstants.IntegerNotSet;
14895 int hotkey = CompilerConstants.IntegerNotSet;
14896 string icon = null;
14897 int iconIndex = CompilerConstants.IntegerNotSet;
14898 string name = null;
14899 string shortName = null;
14900 int show = CompilerConstants.IntegerNotSet;
14901 string target = null;
14902 string workingDirectory = null;
14903
14904 foreach (XAttribute attrib in node.Attributes())
14905 {
14906 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
14907 {
14908 switch (attrib.Name.LocalName)
14909 {
14910 case "Id":
14911 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
14912 break;
14913 case "Advertise":
14914 advertise = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
14915 break;
14916 case "Arguments":
14917 arguments = this.core.GetAttributeValue(sourceLineNumbers, attrib);
14918 break;
14919 case "Description":
14920 description = this.core.GetAttributeValue(sourceLineNumbers, attrib);
14921 break;
14922 case "DescriptionResourceDll":
14923 descriptionResourceDll = this.core.GetAttributeValue(sourceLineNumbers, attrib);
14924 break;
14925 case "DescriptionResourceId":
14926 descriptionResourceId = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue);
14927 break;
14928 case "Directory":
14929 directory = this.core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, null);
14930 break;
14931 case "DisplayResourceDll":
14932 displayResourceDll = this.core.GetAttributeValue(sourceLineNumbers, attrib);
14933 break;
14934 case "DisplayResourceId":
14935 displayResourceId = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue);
14936 break;
14937 case "Hotkey":
14938 hotkey = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue);
14939 break;
14940 case "Icon":
14941 icon = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
14942 this.core.CreateSimpleReference(sourceLineNumbers, "Icon", icon);
14943 break;
14944 case "IconIndex":
14945 iconIndex = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, short.MinValue + 1, short.MaxValue);
14946 break;
14947 case "Name":
14948 name = this.core.GetAttributeLongFilename(sourceLineNumbers, attrib, false);
14949 break;
14950 case "ShortName":
14951 shortName = this.core.GetAttributeShortFilename(sourceLineNumbers, attrib, false);
14952 break;
14953 case "Show":
14954 string showValue = this.core.GetAttributeValue(sourceLineNumbers, attrib);
14955 if (showValue.Length == 0)
14956 {
14957 show = CompilerConstants.IllegalInteger;
14958 }
14959 else
14960 {
14961 Wix.Shortcut.ShowType showType = Wix.Shortcut.ParseShowType(showValue);
14962 switch (showType)
14963 {
14964 case Wix.Shortcut.ShowType.normal:
14965 show = 1;
14966 break;
14967 case Wix.Shortcut.ShowType.maximized:
14968 show = 3;
14969 break;
14970 case Wix.Shortcut.ShowType.minimized:
14971 show = 7;
14972 break;
14973 default:
14974 this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Show", showValue, "normal", "maximized", "minimized"));
14975 show = CompilerConstants.IllegalInteger;
14976 break;
14977 }
14978 }
14979 break;
14980 case "Target":
14981 target = this.core.GetAttributeValue(sourceLineNumbers, attrib);
14982 break;
14983 case "WorkingDirectory":
14984 workingDirectory = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
14985 break;
14986 default:
14987 this.core.UnexpectedAttribute(node, attrib);
14988 break;
14989 }
14990 }
14991 else
14992 {
14993 this.core.ParseExtensionAttribute(node, attrib);
14994 }
14995 }
14996
14997 if (advertise && null != target)
14998 {
14999 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Target", "Advertise", "yes"));
15000 }
15001
15002 if (null == directory)
15003 {
15004 if ("Component" == parentElementLocalName)
15005 {
15006 directory = defaultTarget;
15007 }
15008 else
15009 {
15010 this.core.OnMessage(WixErrors.ExpectedAttributeWhenElementNotUnderElement(sourceLineNumbers, node.Name.LocalName, "Directory", "Component"));
15011 }
15012 }
15013
15014 if (null != descriptionResourceDll)
15015 {
15016 if (CompilerConstants.IntegerNotSet == descriptionResourceId)
15017 {
15018 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DescriptionResourceDll", "DescriptionResourceId"));
15019 }
15020 }
15021 else
15022 {
15023 if (CompilerConstants.IntegerNotSet != descriptionResourceId)
15024 {
15025 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DescriptionResourceId", "DescriptionResourceDll"));
15026 }
15027 }
15028
15029 if (null != displayResourceDll)
15030 {
15031 if (CompilerConstants.IntegerNotSet == displayResourceId)
15032 {
15033 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisplayResourceDll", "DisplayResourceId"));
15034 }
15035 }
15036 else
15037 {
15038 if (CompilerConstants.IntegerNotSet != displayResourceId)
15039 {
15040 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "DisplayResourceId", "DisplayResourceDll"));
15041 }
15042 }
15043
15044 if (null == name)
15045 {
15046 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
15047 }
15048 else if (0 < name.Length)
15049 {
15050 if (this.core.IsValidShortFilename(name, false))
15051 {
15052 if (null == shortName)
15053 {
15054 shortName = name;
15055 name = null;
15056 }
15057 else
15058 {
15059 this.core.OnMessage(WixErrors.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Name", name, "ShortName"));
15060 }
15061 }
15062 else if (null == shortName) // generate a short file name.
15063 {
15064 shortName = this.core.CreateShortName(name, true, false, node.Name.LocalName, componentId, directory);
15065 }
15066 }
15067
15068 if ("Component" != parentElementLocalName && null != target)
15069 {
15070 this.core.OnMessage(WixErrors.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "Target", parentElementLocalName));
15071 }
15072
15073 if (null == id)
15074 {
15075 id = this.core.CreateIdentifier("sct", directory, LowercaseOrNull(name) ?? LowercaseOrNull(shortName));
15076 }
15077
15078 foreach (XElement child in node.Elements())
15079 {
15080 if (CompilerCore.WixNamespace == child.Name.Namespace)
15081 {
15082 switch (child.Name.LocalName)
15083 {
15084 case "Icon":
15085 icon = this.ParseIconElement(child);
15086 break;
15087 case "ShortcutProperty":
15088 this.ParseShortcutPropertyElement(child, id.Id);
15089 break;
15090 default:
15091 this.core.UnexpectedElement(node, child);
15092 break;
15093 }
15094 }
15095 else
15096 {
15097 this.core.ParseExtensionElement(node, child);
15098 }
15099 }
15100
15101 if (!this.core.EncounteredError)
15102 {
15103 Row row = this.core.CreateRow(sourceLineNumbers, "Shortcut", id);
15104 row[1] = directory;
15105 row[2] = GetMsiFilenameValue(shortName, name);
15106 row[3] = componentId;
15107 if (advertise)
15108 {
15109 if (YesNoType.Yes != parentKeyPath && "Component" != parentElementLocalName)
15110 {
15111 this.core.OnMessage(WixWarnings.UnclearShortcut(sourceLineNumbers, id.Id, componentId, defaultTarget));
15112 }
15113 row[4] = Guid.Empty.ToString("B");
15114 }
15115 else if (null != target)
15116 {
15117 row[4] = target;
15118 }
15119 else if ("Component" == parentElementLocalName || "CreateFolder" == parentElementLocalName)
15120 {
15121 row[4] = String.Format(CultureInfo.InvariantCulture, "[{0}]", defaultTarget);
15122 }
15123 else if ("File" == parentElementLocalName)
15124 {
15125 row[4] = String.Format(CultureInfo.InvariantCulture, "[#{0}]", defaultTarget);
15126 }
15127 row[5] = arguments;
15128 row[6] = description;
15129 if (CompilerConstants.IntegerNotSet != hotkey)
15130 {
15131 row[7] = hotkey;
15132 }
15133 row[8] = icon;
15134 if (CompilerConstants.IntegerNotSet != iconIndex)
15135 {
15136 row[9] = iconIndex;
15137 }
15138
15139 if (CompilerConstants.IntegerNotSet != show)
15140 {
15141 row[10] = show;
15142 }
15143 row[11] = workingDirectory;
15144 row[12] = displayResourceDll;
15145 if (CompilerConstants.IntegerNotSet != displayResourceId)
15146 {
15147 row[13] = displayResourceId;
15148 }
15149 row[14] = descriptionResourceDll;
15150 if (CompilerConstants.IntegerNotSet != descriptionResourceId)
15151 {
15152 row[15] = descriptionResourceId;
15153 }
15154 }
15155 }
15156
15157 /// <summary>
15158 /// Parses a shortcut property element.
15159 /// </summary>
15160 /// <param name="node">Element to parse.</param>
15161 private void ParseShortcutPropertyElement(XElement node, string shortcutId)
15162 {
15163 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
15164 Identifier id = null;
15165 string key = null;
15166 string value = null;
15167
15168 foreach (XAttribute attrib in node.Attributes())
15169 {
15170 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
15171 {
15172 switch (attrib.Name.LocalName)
15173 {
15174 case "Id":
15175 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
15176 break;
15177 case "Key":
15178 key = this.core.GetAttributeValue(sourceLineNumbers, attrib);
15179 break;
15180 case "Value":
15181 value = this.core.GetAttributeValue(sourceLineNumbers, attrib);
15182 break;
15183 default:
15184 this.core.UnexpectedAttribute(node, attrib);
15185 break;
15186 }
15187 }
15188 else
15189 {
15190 this.core.ParseExtensionAttribute(node, attrib);
15191 }
15192 }
15193
15194 if (String.IsNullOrEmpty(key))
15195 {
15196 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key"));
15197 }
15198 else if (null == id)
15199 {
15200 id = this.core.CreateIdentifier("scp", shortcutId, key.ToUpperInvariant());
15201 }
15202
15203 string innerText = this.core.GetTrimmedInnerText(node);
15204 if (!String.IsNullOrEmpty(innerText))
15205 {
15206 if (String.IsNullOrEmpty(value))
15207 {
15208 value = innerText;
15209 }
15210 else // cannot specify both the value attribute and inner text
15211 {
15212 this.core.OnMessage(WixErrors.IllegalAttributeWithInnerText(sourceLineNumbers, node.Name.LocalName, "Value"));
15213 }
15214 }
15215
15216 if (String.IsNullOrEmpty(value))
15217 {
15218 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value"));
15219 }
15220
15221 this.core.ParseForExtensionElements(node);
15222
15223 if (!this.core.EncounteredError)
15224 {
15225 Row row = this.core.CreateRow(sourceLineNumbers, "MsiShortcutProperty", id);
15226 row[1] = shortcutId;
15227 row[2] = key;
15228 row[3] = value;
15229 }
15230 }
15231
15232 /// <summary>
15233 /// Parses a typelib element.
15234 /// </summary>
15235 /// <param name="node">Element to parse.</param>
15236 /// <param name="componentId">Identifier of parent component.</param>
15237 /// <param name="fileServer">Identifier of file that acts as typelib server.</param>
15238 /// <param name="win64Component">true if the component is 64-bit.</param>
15239 private void ParseTypeLibElement(XElement node, string componentId, string fileServer, bool win64Component)
15240 {
15241 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
15242 string id = null;
15243 YesNoType advertise = YesNoType.NotSet;
15244 int cost = CompilerConstants.IntegerNotSet;
15245 string description = null;
15246 int flags = 0;
15247 string helpDirectory = null;
15248 int language = CompilerConstants.IntegerNotSet;
15249 int majorVersion = CompilerConstants.IntegerNotSet;
15250 int minorVersion = CompilerConstants.IntegerNotSet;
15251 long resourceId = CompilerConstants.LongNotSet;
15252
15253 foreach (XAttribute attrib in node.Attributes())
15254 {
15255 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
15256 {
15257 switch (attrib.Name.LocalName)
15258 {
15259 case "Id":
15260 id = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
15261 break;
15262 case "Advertise":
15263 advertise = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
15264 break;
15265 case "Control":
15266 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
15267 {
15268 flags |= 2;
15269 }
15270 break;
15271 case "Cost":
15272 cost = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, int.MaxValue);
15273 break;
15274 case "Description":
15275 description = this.core.GetAttributeValue(sourceLineNumbers, attrib);
15276 break;
15277 case "HasDiskImage":
15278 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
15279 {
15280 flags |= 8;
15281 }
15282 break;
15283 case "HelpDirectory":
15284 helpDirectory = this.core.CreateDirectoryReferenceFromInlineSyntax(sourceLineNumbers, attrib, null);
15285 break;
15286 case "Hidden":
15287 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
15288 {
15289 flags |= 4;
15290 }
15291 break;
15292 case "Language":
15293 language = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue);
15294 break;
15295 case "MajorVersion":
15296 majorVersion = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, ushort.MaxValue);
15297 break;
15298 case "MinorVersion":
15299 minorVersion = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, byte.MaxValue);
15300 break;
15301 case "ResourceId":
15302 resourceId = this.core.GetAttributeLongValue(sourceLineNumbers, attrib, int.MinValue, int.MaxValue);
15303 break;
15304 case "Restricted":
15305 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
15306 {
15307 flags |= 1;
15308 }
15309 break;
15310 default:
15311 this.core.UnexpectedAttribute(node, attrib);
15312 break;
15313 }
15314 }
15315 else
15316 {
15317 this.core.ParseExtensionAttribute(node, attrib);
15318 }
15319 }
15320
15321 if (null == id)
15322 {
15323 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
15324 }
15325
15326 if (CompilerConstants.IntegerNotSet == language)
15327 {
15328 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Language"));
15329 language = CompilerConstants.IllegalInteger;
15330 }
15331
15332 // build up the typelib version string for the registry if the major or minor version was specified
15333 string registryVersion = null;
15334 if (CompilerConstants.IntegerNotSet != majorVersion || CompilerConstants.IntegerNotSet != minorVersion)
15335 {
15336 if (CompilerConstants.IntegerNotSet != majorVersion)
15337 {
15338 registryVersion = majorVersion.ToString("x", CultureInfo.InvariantCulture.NumberFormat);
15339 }
15340 else
15341 {
15342 registryVersion = "0";
15343 }
15344
15345 if (CompilerConstants.IntegerNotSet != minorVersion)
15346 {
15347 registryVersion = String.Concat(registryVersion, ".", minorVersion.ToString("x", CultureInfo.InvariantCulture.NumberFormat));
15348 }
15349 else
15350 {
15351 registryVersion = String.Concat(registryVersion, ".0");
15352 }
15353 }
15354
15355 // if the advertise state has not been set, default to non-advertised
15356 if (YesNoType.NotSet == advertise)
15357 {
15358 advertise = YesNoType.No;
15359 }
15360
15361 foreach (XElement child in node.Elements())
15362 {
15363 if (CompilerCore.WixNamespace == child.Name.Namespace)
15364 {
15365 switch (child.Name.LocalName)
15366 {
15367 case "AppId":
15368 this.ParseAppIdElement(child, componentId, YesNoType.NotSet, fileServer, id, registryVersion);
15369 break;
15370 case "Class":
15371 this.ParseClassElement(child, componentId, YesNoType.NotSet, fileServer, id, registryVersion, null);
15372 break;
15373 case "Interface":
15374 this.ParseInterfaceElement(child, componentId, null, null, id, registryVersion);
15375 break;
15376 default:
15377 this.core.UnexpectedElement(node, child);
15378 break;
15379 }
15380 }
15381 else
15382 {
15383 this.core.ParseExtensionElement(node, child);
15384 }
15385 }
15386
15387
15388 if (YesNoType.Yes == advertise)
15389 {
15390 if (CompilerConstants.LongNotSet != resourceId)
15391 {
15392 this.core.OnMessage(WixErrors.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "ResourceId"));
15393 }
15394
15395 if (0 != flags)
15396 {
15397 if (0x1 == (flags & 0x1))
15398 {
15399 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Restricted", "Advertise", "yes"));
15400 }
15401
15402 if (0x2 == (flags & 0x2))
15403 {
15404 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Control", "Advertise", "yes"));
15405 }
15406
15407 if (0x4 == (flags & 0x4))
15408 {
15409 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Hidden", "Advertise", "yes"));
15410 }
15411
15412 if (0x8 == (flags & 0x8))
15413 {
15414 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "HasDiskImage", "Advertise", "yes"));
15415 }
15416 }
15417
15418 if (!this.core.EncounteredError)
15419 {
15420 Row row = this.core.CreateRow(sourceLineNumbers, "TypeLib");
15421 row[0] = id;
15422 row[1] = language;
15423 row[2] = componentId;
15424 if (CompilerConstants.IntegerNotSet != majorVersion || CompilerConstants.IntegerNotSet != minorVersion)
15425 {
15426 row[3] = (CompilerConstants.IntegerNotSet != majorVersion ? majorVersion * 256 : 0) + (CompilerConstants.IntegerNotSet != minorVersion ? minorVersion : 0);
15427 }
15428 row[4] = description;
15429 row[5] = helpDirectory;
15430 row[6] = Guid.Empty.ToString("B");
15431 if (CompilerConstants.IntegerNotSet != cost)
15432 {
15433 row[7] = cost;
15434 }
15435 }
15436 }
15437 else if (YesNoType.No == advertise)
15438 {
15439 if (CompilerConstants.IntegerNotSet != cost && CompilerConstants.IllegalInteger != cost)
15440 {
15441 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Cost", "Advertise", "no"));
15442 }
15443
15444 if (null == fileServer)
15445 {
15446 this.core.OnMessage(WixErrors.MissingTypeLibFile(sourceLineNumbers, node.Name.LocalName, "File"));
15447 }
15448
15449 if (null == registryVersion)
15450 {
15451 this.core.OnMessage(WixErrors.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "MajorVersion", "MinorVersion", "Advertise", "no"));
15452 }
15453
15454 // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion], (Default) = [Description]
15455 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}", id, registryVersion), null, description, componentId);
15456
15457 // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion]\[Language]\[win16|win32|win64], (Default) = [TypeLibPath]\[ResourceId]
15458 string path = String.Concat("[#", fileServer, "]");
15459 if (CompilerConstants.LongNotSet != resourceId)
15460 {
15461 path = String.Concat(path, Path.DirectorySeparatorChar, resourceId.ToString(CultureInfo.InvariantCulture.NumberFormat));
15462 }
15463 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}\{2}\{3}", id, registryVersion, language, (win64Component ? "win64" : "win32")), null, path, componentId);
15464
15465 // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion]\FLAGS, (Default) = [TypeLibFlags]
15466 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}\FLAGS", id, registryVersion), null, flags.ToString(CultureInfo.InvariantCulture.NumberFormat), componentId);
15467
15468 if (null != helpDirectory)
15469 {
15470 // HKCR\TypeLib\[ID]\[MajorVersion].[MinorVersion]\HELPDIR, (Default) = [HelpDirectory]
15471 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Format(CultureInfo.InvariantCulture, @"TypeLib\{0}\{1}\HELPDIR", id, registryVersion), null, String.Concat("[", helpDirectory, "]"), componentId);
15472 }
15473 }
15474 }
15475
15476 /// <summary>
15477 /// Parses an EmbeddedChaniner element.
15478 /// </summary>
15479 /// <param name="node">Element to parse.</param>
15480 private void ParseEmbeddedChainerElement(XElement node)
15481 {
15482 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
15483 Identifier id = null;
15484 string commandLine = null;
15485 string condition = null;
15486 string source = null;
15487 int type = 0;
15488
15489 foreach (XAttribute attrib in node.Attributes())
15490 {
15491 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
15492 {
15493 switch (attrib.Name.LocalName)
15494 {
15495 case "Id":
15496 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
15497 break;
15498 case "BinarySource":
15499 if (null != source)
15500 {
15501 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "FileSource", "PropertySource"));
15502 }
15503 source = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
15504 type = MsiInterop.MsidbCustomActionTypeExe + MsiInterop.MsidbCustomActionTypeBinaryData;
15505 this.core.CreateSimpleReference(sourceLineNumbers, "Binary", source); // add a reference to the appropriate Binary
15506 break;
15507 case "CommandLine":
15508 commandLine = this.core.GetAttributeValue(sourceLineNumbers, attrib);
15509 break;
15510 case "FileSource":
15511 if (null != source)
15512 {
15513 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinarySource", "PropertySource"));
15514 }
15515 source = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
15516 type = MsiInterop.MsidbCustomActionTypeExe + MsiInterop.MsidbCustomActionTypeSourceFile;
15517 this.core.CreateSimpleReference(sourceLineNumbers, "File", source); // add a reference to the appropriate File
15518 break;
15519 case "PropertySource":
15520 if (null != source)
15521 {
15522 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "BinarySource", "FileSource"));
15523 }
15524 source = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
15525 type = MsiInterop.MsidbCustomActionTypeExe + MsiInterop.MsidbCustomActionTypeProperty;
15526 // cannot add a reference to a Property because it may be created at runtime.
15527 break;
15528 default:
15529 this.core.UnexpectedAttribute(node, attrib);
15530 break;
15531 }
15532 }
15533 else
15534 {
15535 this.core.ParseExtensionAttribute(node, attrib);
15536 }
15537 }
15538
15539 // Get the condition from the inner text of the element.
15540 condition = this.core.GetConditionInnerText(node);
15541
15542 if (null == id)
15543 {
15544 id = this.core.CreateIdentifier("mec", source, type.ToString());
15545 }
15546
15547 if (null == source)
15548 {
15549 this.core.OnMessage(WixErrors.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "BinarySource", "FileSource", "PropertySource"));
15550 }
15551
15552 if (!this.core.EncounteredError)
15553 {
15554 Row row = this.core.CreateRow(sourceLineNumbers, "MsiEmbeddedChainer", id);
15555 row[1] = condition;
15556 row[2] = commandLine;
15557 row[3] = source;
15558 row[4] = type;
15559 }
15560 }
15561
15562 /// <summary>
15563 /// Parses UI elements.
15564 /// </summary>
15565 /// <param name="node">Element to parse.</param>
15566 private void ParseUIElement(XElement node)
15567 {
15568 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
15569 Identifier id = null;
15570 int embeddedUICount = 0;
15571
15572 foreach (XAttribute attrib in node.Attributes())
15573 {
15574 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
15575 {
15576 switch (attrib.Name.LocalName)
15577 {
15578 case "Id":
15579 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
15580 break;
15581 default:
15582 this.core.UnexpectedAttribute(node, attrib);
15583 break;
15584 }
15585 }
15586 else
15587 {
15588 this.core.ParseExtensionAttribute(node, attrib);
15589 }
15590 }
15591
15592 foreach (XElement child in node.Elements())
15593 {
15594 if (CompilerCore.WixNamespace == child.Name.Namespace)
15595 {
15596 switch (child.Name.LocalName)
15597 {
15598 case "BillboardAction":
15599 this.ParseBillboardActionElement(child);
15600 break;
15601 case "ComboBox":
15602 this.ParseControlGroupElement(child, this.tableDefinitions["ComboBox"], "ListItem");
15603 break;
15604 case "Dialog":
15605 this.ParseDialogElement(child);
15606 break;
15607 case "DialogRef":
15608 this.ParseSimpleRefElement(child, "Dialog");
15609 break;
15610 case "EmbeddedUI":
15611 if (0 < embeddedUICount) // there can be only one embedded UI
15612 {
15613 SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
15614 this.core.OnMessage(WixErrors.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, child.Name.LocalName));
15615 }
15616 this.ParseEmbeddedUIElement(child);
15617 ++embeddedUICount;
15618 break;
15619 case "Error":
15620 this.ParseErrorElement(child);
15621 break;
15622 case "ListBox":
15623 this.ParseControlGroupElement(child, this.tableDefinitions["ListBox"], "ListItem");
15624 break;
15625 case "ListView":
15626 this.ParseControlGroupElement(child, this.tableDefinitions["ListView"], "ListItem");
15627 break;
15628 case "ProgressText":
15629 this.ParseActionTextElement(child);
15630 break;
15631 case "Publish":
15632 int order = 0;
15633 this.ParsePublishElement(child, null, null, ref order);
15634 break;
15635 case "RadioButtonGroup":
15636 RadioButtonType radioButtonType = this.ParseRadioButtonGroupElement(child, null, RadioButtonType.NotSet);
15637 if (RadioButtonType.Bitmap == radioButtonType || RadioButtonType.Icon == radioButtonType)
15638 {
15639 SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
15640 this.core.OnMessage(WixErrors.RadioButtonBitmapAndIconDisallowed(childSourceLineNumbers));
15641 }
15642 break;
15643 case "TextStyle":
15644 this.ParseTextStyleElement(child);
15645 break;
15646 case "UIText":
15647 this.ParseUITextElement(child);
15648 break;
15649
15650 // the following are available indentically under the UI and Product elements for document organization use only
15651 case "AdminUISequence":
15652 case "InstallUISequence":
15653 this.ParseSequenceElement(child, child.Name.LocalName);
15654 break;
15655 case "Binary":
15656 this.ParseBinaryElement(child);
15657 break;
15658 case "Property":
15659 this.ParsePropertyElement(child);
15660 break;
15661 case "PropertyRef":
15662 this.ParseSimpleRefElement(child, "Property");
15663 break;
15664 case "UIRef":
15665 this.ParseSimpleRefElement(child, "WixUI");
15666 break;
15667
15668 default:
15669 this.core.UnexpectedElement(node, child);
15670 break;
15671 }
15672 }
15673 else
15674 {
15675 this.core.ParseExtensionElement(node, child);
15676 }
15677 }
15678
15679 if (null != id && !this.core.EncounteredError)
15680 {
15681 this.core.CreateRow(sourceLineNumbers, "WixUI", id);
15682 }
15683 }
15684
15685 /// <summary>
15686 /// Parses a list item element.
15687 /// </summary>
15688 /// <param name="node">Element to parse.</param>
15689 /// <param name="table">Table to add row to.</param>
15690 /// <param name="property">Identifier of property referred to by list item.</param>
15691 /// <param name="order">Relative order of list items.</param>
15692 private void ParseListItemElement(XElement node, TableDefinition table, string property, ref int order)
15693 {
15694 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
15695 string icon = null;
15696 string text = null;
15697 string value = null;
15698
15699 foreach (XAttribute attrib in node.Attributes())
15700 {
15701 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
15702 {
15703 switch (attrib.Name.LocalName)
15704 {
15705 case "Icon":
15706 if ("ListView" == table.Name)
15707 {
15708 icon = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
15709 this.core.CreateSimpleReference(sourceLineNumbers, "Binary", icon);
15710 }
15711 else
15712 {
15713 this.core.OnMessage(WixErrors.IllegalAttributeExceptOnElement(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "ListView"));
15714 }
15715 break;
15716 case "Text":
15717 text = this.core.GetAttributeValue(sourceLineNumbers, attrib);
15718 break;
15719 case "Value":
15720 value = this.core.GetAttributeValue(sourceLineNumbers, attrib);
15721 break;
15722 default:
15723 this.core.UnexpectedAttribute(node, attrib);
15724 break;
15725 }
15726 }
15727 else
15728 {
15729 this.core.ParseExtensionAttribute(node, attrib);
15730 }
15731 }
15732
15733 if (null == value)
15734 {
15735 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value"));
15736 }
15737
15738 this.core.ParseForExtensionElements(node);
15739
15740 if (!this.core.EncounteredError)
15741 {
15742 Row row = this.core.CreateRow(sourceLineNumbers, table.Name);
15743 row[0] = property;
15744 row[1] = ++order;
15745 row[2] = value;
15746 row[3] = text;
15747 if (null != icon)
15748 {
15749 row[4] = icon;
15750 }
15751 }
15752 }
15753
15754 /// <summary>
15755 /// Parses a radio button element.
15756 /// </summary>
15757 /// <param name="node">Element to parse.</param>
15758 /// <param name="property">Identifier of property referred to by radio button.</param>
15759 /// <param name="order">Relative order of radio buttons.</param>
15760 /// <returns>Type of this radio button.</returns>
15761 private RadioButtonType ParseRadioButtonElement(XElement node, string property, ref int order)
15762 {
15763 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
15764 RadioButtonType type = RadioButtonType.NotSet;
15765 string value = null;
15766 string x = null;
15767 string y = null;
15768 string width = null;
15769 string height = null;
15770 string text = null;
15771 string tooltip = null;
15772 string help = null;
15773
15774 foreach (XAttribute attrib in node.Attributes())
15775 {
15776 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
15777 {
15778 switch (attrib.Name.LocalName)
15779 {
15780 case "Bitmap":
15781 if (RadioButtonType.NotSet != type)
15782 {
15783 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Icon", "Text"));
15784 }
15785 text = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
15786 this.core.CreateSimpleReference(sourceLineNumbers, "Binary", text);
15787 type = RadioButtonType.Bitmap;
15788 break;
15789 case "Height":
15790 height = this.core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue);
15791 break;
15792 case "Help":
15793 help = this.core.GetAttributeValue(sourceLineNumbers, attrib);
15794 break;
15795 case "Icon":
15796 if (RadioButtonType.NotSet != type)
15797 {
15798 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttributes(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Bitmap", "Text"));
15799 }
15800 text = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
15801 this.core.CreateSimpleReference(sourceLineNumbers, "Binary", text);
15802 type = RadioButtonType.Icon;
15803 break;
15804 case "Text":
15805 if (RadioButtonType.NotSet != type)
15806 {
15807 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "Bitmap", "Icon"));
15808 }
15809 text = this.core.GetAttributeValue(sourceLineNumbers, attrib);
15810 type = RadioButtonType.Text;
15811 break;
15812 case "ToolTip":
15813 tooltip = this.core.GetAttributeValue(sourceLineNumbers, attrib);
15814 break;
15815 case "Value":
15816 value = this.core.GetAttributeValue(sourceLineNumbers, attrib);
15817 break;
15818 case "Width":
15819 width = this.core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue);
15820 break;
15821 case "X":
15822 x = this.core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue);
15823 break;
15824 case "Y":
15825 y = this.core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue);
15826 break;
15827 default:
15828 this.core.UnexpectedAttribute(node, attrib);
15829 break;
15830 }
15831 }
15832 else
15833 {
15834 this.core.ParseExtensionAttribute(node, attrib);
15835 }
15836 }
15837
15838 if (null == value)
15839 {
15840 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value"));
15841 }
15842
15843 if (null == x)
15844 {
15845 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "X"));
15846 }
15847
15848 if (null == y)
15849 {
15850 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Y"));
15851 }
15852
15853 if (null == width)
15854 {
15855 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Width"));
15856 }
15857
15858 if (null == height)
15859 {
15860 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Height"));
15861 }
15862
15863 this.core.ParseForExtensionElements(node);
15864
15865 if (!this.core.EncounteredError)
15866 {
15867 Row row = this.core.CreateRow(sourceLineNumbers, "RadioButton");
15868 row[0] = property;
15869 row[1] = ++order;
15870 row[2] = value;
15871 row[3] = x;
15872 row[4] = y;
15873 row[5] = width;
15874 row[6] = height;
15875 row[7] = text;
15876 if (null != tooltip || null != help)
15877 {
15878 row[8] = String.Concat(tooltip, "|", help);
15879 }
15880 }
15881
15882 return type;
15883 }
15884
15885 /// <summary>
15886 /// Parses a billboard element.
15887 /// </summary>
15888 /// <param name="node">Element to parse.</param>
15889 private void ParseBillboardActionElement(XElement node)
15890 {
15891 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
15892 string action = null;
15893 int order = 0;
15894
15895 foreach (XAttribute attrib in node.Attributes())
15896 {
15897 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
15898 {
15899 switch (attrib.Name.LocalName)
15900 {
15901 case "Id":
15902 action = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
15903 this.core.CreateSimpleReference(sourceLineNumbers, "WixAction", "InstallExecuteSequence", action);
15904 break;
15905 default:
15906 this.core.UnexpectedAttribute(node, attrib);
15907 break;
15908 }
15909 }
15910 else
15911 {
15912 this.core.ParseExtensionAttribute(node, attrib);
15913 }
15914 }
15915
15916 if (null == action)
15917 {
15918 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
15919 }
15920
15921 foreach (XElement child in node.Elements())
15922 {
15923 if (CompilerCore.WixNamespace == child.Name.Namespace)
15924 {
15925 switch (child.Name.LocalName)
15926 {
15927 case "Billboard":
15928 order = order + 1;
15929 this.ParseBillboardElement(child, action, order);
15930 break;
15931 default:
15932 this.core.UnexpectedElement(node, child);
15933 break;
15934 }
15935 }
15936 else
15937 {
15938 this.core.ParseExtensionElement(node, child);
15939 }
15940 }
15941 }
15942
15943 /// <summary>
15944 /// Parses a billboard element.
15945 /// </summary>
15946 /// <param name="node">Element to parse.</param>
15947 /// <param name="action">Action for the billboard.</param>
15948 /// <param name="order">Order of the billboard.</param>
15949 private void ParseBillboardElement(XElement node, string action, int order)
15950 {
15951 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
15952 Identifier id = null;
15953 string feature = null;
15954
15955 foreach (XAttribute attrib in node.Attributes())
15956 {
15957 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
15958 {
15959 switch (attrib.Name.LocalName)
15960 {
15961 case "Id":
15962 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
15963 break;
15964 case "Feature":
15965 feature = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
15966 this.core.CreateSimpleReference(sourceLineNumbers, "Feature", feature);
15967 break;
15968 default:
15969 this.core.UnexpectedAttribute(node, attrib);
15970 break;
15971 }
15972 }
15973 else
15974 {
15975 this.core.ParseExtensionAttribute(node, attrib);
15976 }
15977 }
15978
15979 if (null == id)
15980 {
15981 id = this.core.CreateIdentifier("bil", action, order.ToString(), feature);
15982 }
15983
15984 foreach (XElement child in node.Elements())
15985 {
15986 if (CompilerCore.WixNamespace == child.Name.Namespace)
15987 {
15988 switch (child.Name.LocalName)
15989 {
15990 case "Control":
15991 // These are all thrown away.
15992 Row lastTabRow = null;
15993 string firstControl = null;
15994 string defaultControl = null;
15995 string cancelControl = null;
15996
15997 this.ParseControlElement(child, id.Id, this.tableDefinitions["BBControl"], ref lastTabRow, ref firstControl, ref defaultControl, ref cancelControl, false);
15998 break;
15999 default:
16000 this.core.UnexpectedElement(node, child);
16001 break;
16002 }
16003 }
16004 else
16005 {
16006 this.core.ParseExtensionElement(node, child);
16007 }
16008 }
16009
16010
16011 if (!this.core.EncounteredError)
16012 {
16013 Row row = this.core.CreateRow(sourceLineNumbers, "Billboard", id);
16014 row[1] = feature;
16015 row[2] = action;
16016 row[3] = order;
16017 }
16018 }
16019
16020 /// <summary>
16021 /// Parses a control group element.
16022 /// </summary>
16023 /// <param name="node">Element to parse.</param>
16024 /// <param name="table">Table referred to by control group.</param>
16025 /// <param name="childTag">Expected child elements.</param>
16026 private void ParseControlGroupElement(XElement node, TableDefinition table, string childTag)
16027 {
16028 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
16029 int order = 0;
16030 string property = null;
16031
16032 foreach (XAttribute attrib in node.Attributes())
16033 {
16034 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
16035 {
16036 switch (attrib.Name.LocalName)
16037 {
16038 case "Property":
16039 property = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
16040 break;
16041 default:
16042 this.core.UnexpectedAttribute(node, attrib);
16043 break;
16044 }
16045 }
16046 else
16047 {
16048 this.core.ParseExtensionAttribute(node, attrib);
16049 }
16050 }
16051
16052 if (null == property)
16053 {
16054 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property"));
16055 }
16056
16057 foreach (XElement child in node.Elements())
16058 {
16059 if (CompilerCore.WixNamespace == child.Name.Namespace)
16060 {
16061 if (childTag != child.Name.LocalName)
16062 {
16063 this.core.UnexpectedElement(node, child);
16064 }
16065
16066 switch (child.Name.LocalName)
16067 {
16068 case "ListItem":
16069 this.ParseListItemElement(child, table, property, ref order);
16070 break;
16071 case "Property":
16072 this.ParsePropertyElement(child);
16073 break;
16074 default:
16075 this.core.UnexpectedElement(node, child);
16076 break;
16077 }
16078 }
16079 else
16080 {
16081 this.core.ParseExtensionElement(node, child);
16082 }
16083 }
16084
16085 }
16086
16087 /// <summary>
16088 /// Parses a radio button control group element.
16089 /// </summary>
16090 /// <param name="node">Element to parse.</param>
16091 /// <param name="property">Property associated with this radio button group.</param>
16092 /// <param name="groupType">Specifies the current type of radio buttons in the group.</param>
16093 /// <returns>The current type of radio buttons in the group.</returns>
16094 private RadioButtonType ParseRadioButtonGroupElement(XElement node, string property, RadioButtonType groupType)
16095 {
16096 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
16097 int order = 0;
16098
16099 foreach (XAttribute attrib in node.Attributes())
16100 {
16101 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
16102 {
16103 switch (attrib.Name.LocalName)
16104 {
16105 case "Property":
16106 property = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
16107 this.core.CreateSimpleReference(sourceLineNumbers, "Property", property);
16108 break;
16109 default:
16110 this.core.UnexpectedAttribute(node, attrib);
16111 break;
16112 }
16113 }
16114 else
16115 {
16116 this.core.ParseExtensionAttribute(node, attrib);
16117 }
16118 }
16119
16120 if (null == property)
16121 {
16122 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property"));
16123 }
16124
16125 foreach (XElement child in node.Elements())
16126 {
16127 if (CompilerCore.WixNamespace == child.Name.Namespace)
16128 {
16129 switch (child.Name.LocalName)
16130 {
16131 case "RadioButton":
16132 RadioButtonType type = this.ParseRadioButtonElement(child, property, ref order);
16133 if (RadioButtonType.NotSet == groupType)
16134 {
16135 groupType = type;
16136 }
16137 else if (groupType != type)
16138 {
16139 SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
16140 this.core.OnMessage(WixErrors.RadioButtonTypeInconsistent(childSourceLineNumbers));
16141 }
16142 break;
16143 default:
16144 this.core.UnexpectedElement(node, child);
16145 break;
16146 }
16147 }
16148 else
16149 {
16150 this.core.ParseExtensionElement(node, child);
16151 }
16152 }
16153
16154
16155 return groupType;
16156 }
16157
16158 /// <summary>
16159 /// Parses an action text element.
16160 /// </summary>
16161 /// <param name="node">Element to parse.</param>
16162 private void ParseActionTextElement(XElement node)
16163 {
16164 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
16165 string action = null;
16166 string template = null;
16167
16168 foreach (XAttribute attrib in node.Attributes())
16169 {
16170 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
16171 {
16172 switch (attrib.Name.LocalName)
16173 {
16174 case "Action":
16175 action = this.core.GetAttributeValue(sourceLineNumbers, attrib);
16176 break;
16177 case "Template":
16178 template = this.core.GetAttributeValue(sourceLineNumbers, attrib);
16179 break;
16180 default:
16181 this.core.UnexpectedAttribute(node, attrib);
16182 break;
16183 }
16184 }
16185 else
16186 {
16187 this.core.ParseExtensionAttribute(node, attrib);
16188 }
16189 }
16190
16191 if (null == action)
16192 {
16193 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Action"));
16194 }
16195
16196 this.core.ParseForExtensionElements(node);
16197
16198 if (!this.core.EncounteredError)
16199 {
16200 Row row = this.core.CreateRow(sourceLineNumbers, "ActionText");
16201 row[0] = action;
16202 row[1] = Common.GetInnerText(node);
16203 row[2] = template;
16204 }
16205 }
16206
16207 /// <summary>
16208 /// Parses an ui text element.
16209 /// </summary>
16210 /// <param name="node">Element to parse.</param>
16211 private void ParseUITextElement(XElement node)
16212 {
16213 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
16214 Identifier id = null;
16215 string text = null;
16216
16217 foreach (XAttribute attrib in node.Attributes())
16218 {
16219 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
16220 {
16221 switch (attrib.Name.LocalName)
16222 {
16223 case "Id":
16224 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
16225 break;
16226 default:
16227 this.core.UnexpectedAttribute(node, attrib);
16228 break;
16229 }
16230 }
16231 else
16232 {
16233 this.core.ParseExtensionAttribute(node, attrib);
16234 }
16235 }
16236
16237 text = Common.GetInnerText(node);
16238
16239 if (null == id)
16240 {
16241 id = this.core.CreateIdentifier("txt", text);
16242 }
16243
16244 this.core.ParseForExtensionElements(node);
16245
16246 if (!this.core.EncounteredError)
16247 {
16248 Row row = this.core.CreateRow(sourceLineNumbers, "UIText", id);
16249 row[1] = text;
16250 }
16251 }
16252
16253 /// <summary>
16254 /// Parses a text style element.
16255 /// </summary>
16256 /// <param name="node">Element to parse.</param>
16257 private void ParseTextStyleElement(XElement node)
16258 {
16259 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
16260 Identifier id = null;
16261 int bits = 0;
16262 int color = CompilerConstants.IntegerNotSet;
16263 string faceName = null;
16264 string size = "0";
16265
16266 foreach (XAttribute attrib in node.Attributes())
16267 {
16268 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
16269 {
16270 switch (attrib.Name.LocalName)
16271 {
16272 case "Id":
16273 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
16274 break;
16275
16276 // RGB Values
16277 case "Red":
16278 int redColor = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, byte.MaxValue);
16279 if (CompilerConstants.IllegalInteger != redColor)
16280 {
16281 if (CompilerConstants.IntegerNotSet == color)
16282 {
16283 color = redColor;
16284 }
16285 else
16286 {
16287 color += redColor;
16288 }
16289 }
16290 break;
16291 case "Green":
16292 int greenColor = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, byte.MaxValue);
16293 if (CompilerConstants.IllegalInteger != greenColor)
16294 {
16295 if (CompilerConstants.IntegerNotSet == color)
16296 {
16297 color = greenColor * 256;
16298 }
16299 else
16300 {
16301 color += greenColor * 256;
16302 }
16303 }
16304 break;
16305 case "Blue":
16306 int blueColor = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, byte.MaxValue);
16307 if (CompilerConstants.IllegalInteger != blueColor)
16308 {
16309 if (CompilerConstants.IntegerNotSet == color)
16310 {
16311 color = blueColor * 65536;
16312 }
16313 else
16314 {
16315 color += blueColor * 65536;
16316 }
16317 }
16318 break;
16319
16320 // Style values
16321 case "Bold":
16322 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
16323 {
16324 bits |= MsiInterop.MsidbTextStyleStyleBitsBold;
16325 }
16326 break;
16327 case "Italic":
16328 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
16329 {
16330 bits |= MsiInterop.MsidbTextStyleStyleBitsItalic;
16331 }
16332 break;
16333 case "Strike":
16334 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
16335 {
16336 bits |= MsiInterop.MsidbTextStyleStyleBitsStrike;
16337 }
16338 break;
16339 case "Underline":
16340 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
16341 {
16342 bits |= MsiInterop.MsidbTextStyleStyleBitsUnderline;
16343 }
16344 break;
16345
16346 // Font values
16347 case "FaceName":
16348 faceName = this.core.GetAttributeValue(sourceLineNumbers, attrib);
16349 break;
16350 case "Size":
16351 size = this.core.GetAttributeValue(sourceLineNumbers, attrib);
16352 break;
16353
16354 default:
16355 this.core.UnexpectedAttribute(node, attrib);
16356 break;
16357 }
16358 }
16359 else
16360 {
16361 this.core.ParseExtensionAttribute(node, attrib);
16362 }
16363 }
16364
16365 if (null == id)
16366 {
16367 this.core.CreateIdentifier("txs", faceName, size.ToString(), color.ToString(), bits.ToString());
16368 }
16369
16370 if (null == faceName)
16371 {
16372 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "FaceName"));
16373 }
16374
16375 this.core.ParseForExtensionElements(node);
16376
16377 if (!this.core.EncounteredError)
16378 {
16379 Row row = this.core.CreateRow(sourceLineNumbers, "TextStyle", id);
16380 row[1] = faceName;
16381 row[2] = size;
16382 if (0 <= color)
16383 {
16384 row[3] = color;
16385 }
16386
16387 if (0 < bits)
16388 {
16389 row[4] = bits;
16390 }
16391 }
16392 }
16393
16394 /// <summary>
16395 /// Parses a dialog element.
16396 /// </summary>
16397 /// <param name="node">Element to parse.</param>
16398 private void ParseDialogElement(XElement node)
16399 {
16400 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
16401 Identifier id = null;
16402 int bits = MsiInterop.MsidbDialogAttributesVisible | MsiInterop.MsidbDialogAttributesModal | MsiInterop.MsidbDialogAttributesMinimize;
16403 int height = 0;
16404 string title = null;
16405 bool trackDiskSpace = false;
16406 int width = 0;
16407 int x = 50;
16408 int y = 50;
16409
16410 foreach (XAttribute attrib in node.Attributes())
16411 {
16412 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
16413 {
16414 switch (attrib.Name.LocalName)
16415 {
16416 case "Id":
16417 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
16418 break;
16419 case "Height":
16420 height = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue);
16421 break;
16422 case "Title":
16423 title = this.core.GetAttributeValue(sourceLineNumbers, attrib);
16424 break;
16425 case "Width":
16426 width = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue);
16427 break;
16428 case "X":
16429 x = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, 100);
16430 break;
16431 case "Y":
16432 y = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, 100);
16433 break;
16434
16435 case "CustomPalette":
16436 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
16437 {
16438 bits ^= MsiInterop.MsidbDialogAttributesUseCustomPalette;
16439 }
16440 break;
16441 case "ErrorDialog":
16442 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
16443 {
16444 bits ^= MsiInterop.MsidbDialogAttributesError;
16445 }
16446 break;
16447 case "Hidden":
16448 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
16449 {
16450 bits ^= MsiInterop.MsidbDialogAttributesVisible;
16451 }
16452 break;
16453 case "KeepModeless":
16454 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
16455 {
16456 bits ^= MsiInterop.MsidbDialogAttributesKeepModeless;
16457 }
16458 break;
16459 case "LeftScroll":
16460 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
16461 {
16462 bits ^= MsiInterop.MsidbDialogAttributesLeftScroll;
16463 }
16464 break;
16465 case "Modeless":
16466 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
16467 {
16468 bits ^= MsiInterop.MsidbDialogAttributesModal;
16469 }
16470 break;
16471 case "NoMinimize":
16472 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
16473 {
16474 bits ^= MsiInterop.MsidbDialogAttributesMinimize;
16475 }
16476 break;
16477 case "RightAligned":
16478 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
16479 {
16480 bits ^= MsiInterop.MsidbDialogAttributesRightAligned;
16481 }
16482 break;
16483 case "RightToLeft":
16484 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
16485 {
16486 bits ^= MsiInterop.MsidbDialogAttributesRTLRO;
16487 }
16488 break;
16489 case "SystemModal":
16490 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
16491 {
16492 bits ^= MsiInterop.MsidbDialogAttributesSysModal;
16493 }
16494 break;
16495 case "TrackDiskSpace":
16496 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
16497 {
16498 bits ^= MsiInterop.MsidbDialogAttributesTrackDiskSpace;
16499 trackDiskSpace = true;
16500 }
16501 break;
16502
16503 default:
16504 this.core.UnexpectedAttribute(node, attrib);
16505 break;
16506 }
16507 }
16508 else
16509 {
16510 this.core.ParseExtensionAttribute(node, attrib);
16511 }
16512 }
16513
16514 if (null == id)
16515 {
16516 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
16517 id = Identifier.Invalid;
16518 }
16519
16520 Row lastTabRow = null;
16521 string cancelControl = null;
16522 string defaultControl = null;
16523 string firstControl = null;
16524
16525 foreach (XElement child in node.Elements())
16526 {
16527 if (CompilerCore.WixNamespace == child.Name.Namespace)
16528 {
16529 switch (child.Name.LocalName)
16530 {
16531 case "Control":
16532 this.ParseControlElement(child, id.Id, this.tableDefinitions["Control"], ref lastTabRow, ref firstControl, ref defaultControl, ref cancelControl, trackDiskSpace);
16533 break;
16534 default:
16535 this.core.UnexpectedElement(node, child);
16536 break;
16537 }
16538 }
16539 else
16540 {
16541 this.core.ParseExtensionElement(node, child);
16542 }
16543 }
16544
16545
16546 if (null != lastTabRow && null != lastTabRow[1])
16547 {
16548 if (firstControl != lastTabRow[1].ToString())
16549 {
16550 lastTabRow[10] = firstControl;
16551 }
16552 }
16553
16554 if (null == firstControl)
16555 {
16556 this.core.OnMessage(WixErrors.NoFirstControlSpecified(sourceLineNumbers, id.Id));
16557 }
16558
16559 if (!this.core.EncounteredError)
16560 {
16561 Row row = this.core.CreateRow(sourceLineNumbers, "Dialog", id);
16562 row[1] = x;
16563 row[2] = y;
16564 row[3] = width;
16565 row[4] = height;
16566 row[5] = bits;
16567 row[6] = title;
16568 row[7] = firstControl;
16569 row[8] = defaultControl;
16570 row[9] = cancelControl;
16571 }
16572 }
16573
16574 /// <summary>
16575 /// Parses an EmbeddedUI element.
16576 /// </summary>
16577 /// <param name="node">Element to parse.</param>
16578 private void ParseEmbeddedUIElement(XElement node)
16579 {
16580 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
16581 Identifier id = null;
16582 string name = null;
16583 int attributes = MsiInterop.MsidbEmbeddedUI; // by default this is the primary DLL that does not support basic UI.
16584 int messageFilter = MsiInterop.INSTALLLOGMODE_FATALEXIT | MsiInterop.INSTALLLOGMODE_ERROR | MsiInterop.INSTALLLOGMODE_WARNING | MsiInterop.INSTALLLOGMODE_USER
16585 | MsiInterop.INSTALLLOGMODE_INFO | MsiInterop.INSTALLLOGMODE_FILESINUSE | MsiInterop.INSTALLLOGMODE_RESOLVESOURCE
16586 | MsiInterop.INSTALLLOGMODE_OUTOFDISKSPACE | MsiInterop.INSTALLLOGMODE_ACTIONSTART | MsiInterop.INSTALLLOGMODE_ACTIONDATA
16587 | MsiInterop.INSTALLLOGMODE_PROGRESS | MsiInterop.INSTALLLOGMODE_COMMONDATA | MsiInterop.INSTALLLOGMODE_INITIALIZE
16588 | MsiInterop.INSTALLLOGMODE_TERMINATE | MsiInterop.INSTALLLOGMODE_SHOWDIALOG | MsiInterop.INSTALLLOGMODE_RMFILESINUSE
16589 | MsiInterop.INSTALLLOGMODE_INSTALLSTART | MsiInterop.INSTALLLOGMODE_INSTALLEND;
16590 string sourceFile = null;
16591
16592 foreach (XAttribute attrib in node.Attributes())
16593 {
16594 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
16595 {
16596 switch (attrib.Name.LocalName)
16597 {
16598 case "Id":
16599 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
16600 break;
16601 case "Name":
16602 name = this.core.GetAttributeLongFilename(sourceLineNumbers, attrib, false);
16603 break;
16604 case "IgnoreFatalExit":
16605 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
16606 {
16607 messageFilter ^= MsiInterop.INSTALLLOGMODE_FATALEXIT;
16608 }
16609 break;
16610 case "IgnoreError":
16611 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
16612 {
16613 messageFilter ^= MsiInterop.INSTALLLOGMODE_ERROR;
16614 }
16615 break;
16616 case "IgnoreWarning":
16617 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
16618 {
16619 messageFilter ^= MsiInterop.INSTALLLOGMODE_WARNING;
16620 }
16621 break;
16622 case "IgnoreUser":
16623 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
16624 {
16625 messageFilter ^= MsiInterop.INSTALLLOGMODE_USER;
16626 }
16627 break;
16628 case "IgnoreInfo":
16629 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
16630 {
16631 messageFilter ^= MsiInterop.INSTALLLOGMODE_INFO;
16632 }
16633 break;
16634 case "IgnoreFilesInUse":
16635 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
16636 {
16637 messageFilter ^= MsiInterop.INSTALLLOGMODE_FILESINUSE;
16638 }
16639 break;
16640 case "IgnoreResolveSource":
16641 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
16642 {
16643 messageFilter ^= MsiInterop.INSTALLLOGMODE_RESOLVESOURCE;
16644 }
16645 break;
16646 case "IgnoreOutOfDiskSpace":
16647 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
16648 {
16649 messageFilter ^= MsiInterop.INSTALLLOGMODE_OUTOFDISKSPACE;
16650 }
16651 break;
16652 case "IgnoreActionStart":
16653 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
16654 {
16655 messageFilter ^= MsiInterop.INSTALLLOGMODE_ACTIONSTART;
16656 }
16657 break;
16658 case "IgnoreActionData":
16659 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
16660 {
16661 messageFilter ^= MsiInterop.INSTALLLOGMODE_ACTIONDATA;
16662 }
16663 break;
16664 case "IgnoreProgress":
16665 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
16666 {
16667 messageFilter ^= MsiInterop.INSTALLLOGMODE_PROGRESS;
16668 }
16669 break;
16670 case "IgnoreCommonData":
16671 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
16672 {
16673 messageFilter ^= MsiInterop.INSTALLLOGMODE_COMMONDATA;
16674 }
16675 break;
16676 case "IgnoreInitialize":
16677 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
16678 {
16679 messageFilter ^= MsiInterop.INSTALLLOGMODE_INITIALIZE;
16680 }
16681 break;
16682 case "IgnoreTerminate":
16683 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
16684 {
16685 messageFilter ^= MsiInterop.INSTALLLOGMODE_TERMINATE;
16686 }
16687 break;
16688 case "IgnoreShowDialog":
16689 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
16690 {
16691 messageFilter ^= MsiInterop.INSTALLLOGMODE_SHOWDIALOG;
16692 }
16693 break;
16694 case "IgnoreRMFilesInUse":
16695 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
16696 {
16697 messageFilter ^= MsiInterop.INSTALLLOGMODE_RMFILESINUSE;
16698 }
16699 break;
16700 case "IgnoreInstallStart":
16701 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
16702 {
16703 messageFilter ^= MsiInterop.INSTALLLOGMODE_INSTALLSTART;
16704 }
16705 break;
16706 case "IgnoreInstallEnd":
16707 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
16708 {
16709 messageFilter ^= MsiInterop.INSTALLLOGMODE_INSTALLEND;
16710 }
16711 break;
16712 case "SourceFile":
16713 sourceFile = this.core.GetAttributeValue(sourceLineNumbers, attrib);
16714 break;
16715 case "SupportBasicUI":
16716 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
16717 {
16718 attributes |= MsiInterop.MsidbEmbeddedHandlesBasic;
16719 }
16720 break;
16721 default:
16722 this.core.UnexpectedAttribute(node, attrib);
16723 break;
16724 }
16725 }
16726 else
16727 {
16728 this.core.ParseExtensionAttribute(node, attrib);
16729 }
16730 }
16731
16732 if (String.IsNullOrEmpty(sourceFile))
16733 {
16734 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile"));
16735 }
16736 else if (String.IsNullOrEmpty(name))
16737 {
16738 name = Path.GetFileName(sourceFile);
16739 if (!this.core.IsValidLongFilename(name, false))
16740 {
16741 this.core.OnMessage(WixErrors.IllegalLongFilename(sourceLineNumbers, node.Name.LocalName, "Source", name));
16742 }
16743 }
16744
16745 if (null == id)
16746 {
16747 if (!String.IsNullOrEmpty(name))
16748 {
16749 id = this.core.CreateIdentifierFromFilename(name);
16750 }
16751
16752 if (null == id)
16753 {
16754 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
16755 }
16756 else if (!Common.IsIdentifier(id.Id))
16757 {
16758 this.core.OnMessage(WixErrors.IllegalIdentifier(sourceLineNumbers, node.Name.LocalName, "Id", id.Id));
16759 }
16760 }
16761 else if (String.IsNullOrEmpty(name))
16762 {
16763 name = id.Id;
16764 }
16765
16766 if (!name.Contains("."))
16767 {
16768 this.core.OnMessage(WixErrors.InvalidEmbeddedUIFileName(sourceLineNumbers, name));
16769 }
16770
16771 foreach (XElement child in node.Elements())
16772 {
16773 if (CompilerCore.WixNamespace == child.Name.Namespace)
16774 {
16775 switch (child.Name.LocalName)
16776 {
16777 case "EmbeddedUIResource":
16778 this.ParseEmbeddedUIResourceElement(child);
16779 break;
16780 default:
16781 this.core.UnexpectedElement(node, child);
16782 break;
16783 }
16784 }
16785 else
16786 {
16787 this.core.ParseExtensionElement(node, child);
16788 }
16789 }
16790
16791 if (!this.core.EncounteredError)
16792 {
16793 Row row = this.core.CreateRow(sourceLineNumbers, "MsiEmbeddedUI", id);
16794 row[1] = name;
16795 row[2] = attributes;
16796 row[3] = messageFilter;
16797 row[4] = sourceFile;
16798 }
16799 }
16800
16801 /// <summary>
16802 /// Parses a embedded UI resource element.
16803 /// </summary>
16804 /// <param name="node">Element to parse.</param>
16805 /// <param name="parentId">Identifier of parent EmbeddedUI element.</param>
16806 private void ParseEmbeddedUIResourceElement(XElement node)
16807 {
16808 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
16809 Identifier id = null;
16810 string name = null;
16811 string sourceFile = null;
16812
16813 foreach (XAttribute attrib in node.Attributes())
16814 {
16815 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
16816 {
16817 switch (attrib.Name.LocalName)
16818 {
16819 case "Id":
16820 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
16821 break;
16822 case "Name":
16823 name = this.core.GetAttributeLongFilename(sourceLineNumbers, attrib, false);
16824 break;
16825 case "SourceFile":
16826 sourceFile = this.core.GetAttributeValue(sourceLineNumbers, attrib);
16827 break;
16828 default:
16829 this.core.UnexpectedAttribute(node, attrib);
16830 break;
16831 }
16832 }
16833 else
16834 {
16835 this.core.ParseExtensionAttribute(node, attrib);
16836 }
16837 }
16838
16839 if (String.IsNullOrEmpty(sourceFile))
16840 {
16841 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile"));
16842 }
16843 else if (String.IsNullOrEmpty(name))
16844 {
16845 name = Path.GetFileName(sourceFile);
16846 if (!this.core.IsValidLongFilename(name, false))
16847 {
16848 this.core.OnMessage(WixErrors.IllegalLongFilename(sourceLineNumbers, node.Name.LocalName, "Source", name));
16849 }
16850 }
16851
16852 if (null == id)
16853 {
16854 if (!String.IsNullOrEmpty(name))
16855 {
16856 id = this.core.CreateIdentifierFromFilename(name);
16857 }
16858
16859 if (null == id)
16860 {
16861 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
16862 }
16863 else if (!Common.IsIdentifier(id.Id))
16864 {
16865 this.core.OnMessage(WixErrors.IllegalIdentifier(sourceLineNumbers, node.Name.LocalName, "Id", id.Id));
16866 }
16867 }
16868 else if (String.IsNullOrEmpty(name))
16869 {
16870 name = id.Id;
16871 }
16872
16873 this.core.ParseForExtensionElements(node);
16874
16875 if (!this.core.EncounteredError)
16876 {
16877 Row row = this.core.CreateRow(sourceLineNumbers, "MsiEmbeddedUI", id);
16878 row[1] = name;
16879 row[2] = 0; // embedded UI resources always set this to 0
16880 row[3] = null;
16881 row[4] = sourceFile;
16882 }
16883 }
16884
16885 /// <summary>
16886 /// Parses a control element.
16887 /// </summary>
16888 /// <param name="node">Element to parse.</param>
16889 /// <param name="dialog">Identifier for parent dialog.</param>
16890 /// <param name="table">Table control belongs in.</param>
16891 /// <param name="lastTabRow">Last row in the tab order.</param>
16892 /// <param name="firstControl">Name of the first control in the tab order.</param>
16893 /// <param name="defaultControl">Name of the default control.</param>
16894 /// <param name="cancelControl">Name of the candle control.</param>
16895 /// <param name="trackDiskSpace">True if the containing dialog tracks disk space.</param>
16896 private void ParseControlElement(XElement node, string dialog, TableDefinition table, ref Row lastTabRow, ref string firstControl, ref string defaultControl, ref string cancelControl, bool trackDiskSpace)
16897 {
16898 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
16899 Identifier id = null;
16900 BitArray bits = new BitArray(32);
16901 int attributes = 0;
16902 string checkBoxPropertyRef = null;
16903 string checkboxValue = null;
16904 string controlType = null;
16905 bool disabled = false;
16906 string height = null;
16907 string help = null;
16908 bool isCancel = false;
16909 bool isDefault = false;
16910 bool notTabbable = false;
16911 string property = null;
16912 int publishOrder = 0;
16913 string[] specialAttributes = null;
16914 string sourceFile = null;
16915 string text = null;
16916 string tooltip = null;
16917 RadioButtonType radioButtonsType = RadioButtonType.NotSet;
16918 string width = null;
16919 string x = null;
16920 string y = null;
16921
16922 // The rest of the method relies on the control's Type, so we have to get that first.
16923 XAttribute typeAttribute = node.Attribute("Type");
16924 if (null == typeAttribute)
16925 {
16926 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Type"));
16927 }
16928 else
16929 {
16930 controlType = this.core.GetAttributeValue(sourceLineNumbers, typeAttribute);
16931 }
16932
16933 switch (controlType)
16934 {
16935 case "Billboard":
16936 specialAttributes = null;
16937 notTabbable = true;
16938 disabled = true;
16939
16940 this.core.EnsureTable(sourceLineNumbers, "Billboard");
16941 break;
16942 case "Bitmap":
16943 specialAttributes = MsiInterop.BitmapControlAttributes;
16944 notTabbable = true;
16945 disabled = true;
16946 break;
16947 case "CheckBox":
16948 specialAttributes = MsiInterop.CheckboxControlAttributes;
16949 break;
16950 case "ComboBox":
16951 specialAttributes = MsiInterop.ComboboxControlAttributes;
16952 break;
16953 case "DirectoryCombo":
16954 specialAttributes = MsiInterop.VolumeControlAttributes;
16955 break;
16956 case "DirectoryList":
16957 specialAttributes = null;
16958 break;
16959 case "Edit":
16960 specialAttributes = MsiInterop.EditControlAttributes;
16961 break;
16962 case "GroupBox":
16963 specialAttributes = null;
16964 notTabbable = true;
16965 break;
16966 case "Hyperlink":
16967 specialAttributes = MsiInterop.HyperlinkControlAttributes;
16968 break;
16969 case "Icon":
16970 specialAttributes = MsiInterop.IconControlAttributes;
16971 notTabbable = true;
16972 disabled = true;
16973 break;
16974 case "Line":
16975 specialAttributes = null;
16976 notTabbable = true;
16977 disabled = true;
16978 break;
16979 case "ListBox":
16980 specialAttributes = MsiInterop.ListboxControlAttributes;
16981 break;
16982 case "ListView":
16983 specialAttributes = MsiInterop.ListviewControlAttributes;
16984 break;
16985 case "MaskedEdit":
16986 specialAttributes = MsiInterop.EditControlAttributes;
16987 break;
16988 case "PathEdit":
16989 specialAttributes = MsiInterop.EditControlAttributes;
16990 break;
16991 case "ProgressBar":
16992 specialAttributes = MsiInterop.ProgressControlAttributes;
16993 notTabbable = true;
16994 disabled = true;
16995 break;
16996 case "PushButton":
16997 specialAttributes = MsiInterop.ButtonControlAttributes;
16998 break;
16999 case "RadioButtonGroup":
17000 specialAttributes = MsiInterop.RadioControlAttributes;
17001 break;
17002 case "ScrollableText":
17003 specialAttributes = null;
17004 break;
17005 case "SelectionTree":
17006 specialAttributes = null;
17007 break;
17008 case "Text":
17009 specialAttributes = MsiInterop.TextControlAttributes;
17010 notTabbable = true;
17011 break;
17012 case "VolumeCostList":
17013 specialAttributes = MsiInterop.VolumeControlAttributes;
17014 notTabbable = true;
17015 break;
17016 case "VolumeSelectCombo":
17017 specialAttributes = MsiInterop.VolumeControlAttributes;
17018 break;
17019 default:
17020 specialAttributes = null;
17021 notTabbable = true;
17022 break;
17023 }
17024
17025 foreach (XAttribute attrib in node.Attributes())
17026 {
17027 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
17028 {
17029 switch (attrib.Name.LocalName)
17030 {
17031 case "Id":
17032 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
17033 break;
17034 case "Type": // already processed
17035 break;
17036 case "Cancel":
17037 isCancel = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
17038 break;
17039 case "CheckBoxPropertyRef":
17040 checkBoxPropertyRef = this.core.GetAttributeValue(sourceLineNumbers, attrib);
17041 break;
17042 case "CheckBoxValue":
17043 checkboxValue = this.core.GetAttributeValue(sourceLineNumbers, attrib);
17044 break;
17045 case "Default":
17046 isDefault = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
17047 break;
17048 case "Height":
17049 height = this.core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue);
17050 break;
17051 case "Help":
17052 help = this.core.GetAttributeValue(sourceLineNumbers, attrib);
17053 break;
17054 case "IconSize":
17055 string iconSizeValue = this.core.GetAttributeValue(sourceLineNumbers, attrib);
17056 if (null != specialAttributes)
17057 {
17058 if (0 < iconSizeValue.Length)
17059 {
17060 Wix.Control.IconSizeType iconsSizeType = Wix.Control.ParseIconSizeType(iconSizeValue);
17061 switch (iconsSizeType)
17062 {
17063 case Wix.Control.IconSizeType.Item16:
17064 this.core.TrySetBitFromName(specialAttributes, "Icon16", YesNoType.Yes, bits, 16);
17065 break;
17066 case Wix.Control.IconSizeType.Item32:
17067 this.core.TrySetBitFromName(specialAttributes, "Icon32", YesNoType.Yes, bits, 16);
17068 break;
17069 case Wix.Control.IconSizeType.Item48:
17070 this.core.TrySetBitFromName(specialAttributes, "Icon16", YesNoType.Yes, bits, 16);
17071 this.core.TrySetBitFromName(specialAttributes, "Icon32", YesNoType.Yes, bits, 16);
17072 break;
17073 default:
17074 this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, iconSizeValue, "16", "32", "48"));
17075 break;
17076 }
17077 }
17078 }
17079 else
17080 {
17081 this.core.OnMessage(WixErrors.IllegalAttributeValueWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, iconSizeValue, "Type"));
17082 }
17083 break;
17084 case "Property":
17085 property = this.core.GetAttributeValue(sourceLineNumbers, attrib);
17086 break;
17087 case "TabSkip":
17088 notTabbable = YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
17089 break;
17090 case "Text":
17091 text = this.core.GetAttributeValue(sourceLineNumbers, attrib);
17092 break;
17093 case "ToolTip":
17094 tooltip = this.core.GetAttributeValue(sourceLineNumbers, attrib);
17095 break;
17096 case "Width":
17097 width = this.core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue);
17098 break;
17099 case "X":
17100 x = this.core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue);
17101 break;
17102 case "Y":
17103 y = this.core.GetAttributeLocalizableIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue);
17104 break;
17105 default:
17106 YesNoType attribValue = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
17107 if (!this.core.TrySetBitFromName(MsiInterop.CommonControlAttributes, attrib.Name.LocalName, attribValue, bits, 0))
17108 {
17109 if (null == specialAttributes || !this.core.TrySetBitFromName(specialAttributes, attrib.Name.LocalName, attribValue, bits, 16))
17110 {
17111 this.core.UnexpectedAttribute(node, attrib);
17112 }
17113 }
17114 break;
17115 }
17116 }
17117 else
17118 {
17119 this.core.ParseExtensionAttribute(node, attrib);
17120 }
17121 }
17122
17123 attributes = this.core.CreateIntegerFromBitArray(bits);
17124
17125 if (disabled)
17126 {
17127 attributes |= MsiInterop.MsidbControlAttributesEnabled; // bit will be inverted when stored
17128 }
17129
17130 if (null == height)
17131 {
17132 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Height"));
17133 }
17134
17135 if (null == width)
17136 {
17137 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Width"));
17138 }
17139
17140 if (null == x)
17141 {
17142 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "X"));
17143 }
17144
17145 if (null == y)
17146 {
17147 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Y"));
17148 }
17149
17150 if (null == id)
17151 {
17152 id = this.core.CreateIdentifier("ctl", dialog, x, y, height, width);
17153 }
17154
17155 if (isCancel)
17156 {
17157 cancelControl = id.Id;
17158 }
17159
17160 if (isDefault)
17161 {
17162 defaultControl = id.Id;
17163 }
17164
17165 foreach (XElement child in node.Elements())
17166 {
17167 if (CompilerCore.WixNamespace == child.Name.Namespace)
17168 {
17169 SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
17170 switch (child.Name.LocalName)
17171 {
17172 case "Binary":
17173 this.ParseBinaryElement(child);
17174 break;
17175 case "ComboBox":
17176 this.ParseControlGroupElement(child, this.tableDefinitions["ComboBox"], "ListItem");
17177 break;
17178 case "Condition":
17179 this.ParseConditionElement(child, node.Name.LocalName, id.Id, dialog);
17180 break;
17181 case "ListBox":
17182 this.ParseControlGroupElement(child, this.tableDefinitions["ListBox"], "ListItem");
17183 break;
17184 case "ListView":
17185 this.ParseControlGroupElement(child, this.tableDefinitions["ListView"], "ListItem");
17186 break;
17187 case "Property":
17188 this.ParsePropertyElement(child);
17189 break;
17190 case "Publish":
17191 this.ParsePublishElement(child, dialog ?? String.Empty, id.Id, ref publishOrder);
17192 break;
17193 case "RadioButtonGroup":
17194 radioButtonsType = this.ParseRadioButtonGroupElement(child, property, radioButtonsType);
17195 break;
17196 case "Subscribe":
17197 this.ParseSubscribeElement(child, dialog, id.Id);
17198 break;
17199 case "Text":
17200 foreach (XAttribute attrib in child.Attributes())
17201 {
17202 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
17203 {
17204 switch (attrib.Name.LocalName)
17205 {
17206 case "SourceFile":
17207 sourceFile = this.core.GetAttributeValue(childSourceLineNumbers, attrib);
17208 break;
17209 default:
17210 this.core.UnexpectedAttribute(child, attrib);
17211 break;
17212 }
17213 }
17214 else
17215 {
17216 this.core.ParseExtensionAttribute(child, attrib);
17217 }
17218 }
17219
17220 text = Common.GetInnerText(child);
17221 if (!String.IsNullOrEmpty(text) && null != sourceFile)
17222 {
17223 this.core.OnMessage(WixErrors.IllegalAttributeWithInnerText(childSourceLineNumbers, child.Name.LocalName, "SourceFile"));
17224 }
17225 break;
17226 default:
17227 this.core.UnexpectedElement(node, child);
17228 break;
17229 }
17230 }
17231 else
17232 {
17233 this.core.ParseExtensionElement(node, child);
17234 }
17235 }
17236
17237 // If the radio buttons have icons, then we need to add the icon attribute.
17238 switch (radioButtonsType)
17239 {
17240 case RadioButtonType.Bitmap:
17241 attributes |= MsiInterop.MsidbControlAttributesBitmap;
17242 break;
17243 case RadioButtonType.Icon:
17244 attributes |= MsiInterop.MsidbControlAttributesIcon;
17245 break;
17246 case RadioButtonType.Text:
17247 // Text is the default so nothing needs to be added bits
17248 break;
17249 }
17250
17251 // If we're tracking disk space, and this is a non-FormatSize Text control, and the text attribute starts with
17252 // '[' and ends with ']', add a space. It is not necessary for the whole string to be a property, just
17253 // those two characters matter.
17254 if (trackDiskSpace && "Text" == controlType &&
17255 MsiInterop.MsidbControlAttributesFormatSize != (attributes & MsiInterop.MsidbControlAttributesFormatSize) &&
17256 null != text && text.StartsWith("[", StringComparison.Ordinal) && text.EndsWith("]", StringComparison.Ordinal))
17257 {
17258 text = String.Concat(text, " ");
17259 }
17260
17261 // the logic for creating control rows is a little tricky because of the way tabable controls are set
17262 Row row = null;
17263 if (!this.core.EncounteredError)
17264 {
17265 if ("CheckBox" == controlType)
17266 {
17267 if (String.IsNullOrEmpty(property) && String.IsNullOrEmpty(checkBoxPropertyRef))
17268 {
17269 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "CheckBoxPropertyRef", true));
17270 }
17271 else if (!String.IsNullOrEmpty(property) && !String.IsNullOrEmpty(checkBoxPropertyRef))
17272 {
17273 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Property", "CheckBoxPropertyRef"));
17274 }
17275 else if (!String.IsNullOrEmpty(property))
17276 {
17277 row = this.core.CreateRow(sourceLineNumbers, "CheckBox");
17278 row[0] = property;
17279 row[1] = checkboxValue;
17280 }
17281 else
17282 {
17283 this.core.CreateSimpleReference(sourceLineNumbers, "CheckBox", checkBoxPropertyRef);
17284 }
17285 }
17286
17287 row = this.core.CreateRow(sourceLineNumbers, table.Name);
17288 row.Access = id.Access;
17289 row[0] = dialog;
17290 row[1] = id.Id;
17291 row[2] = controlType;
17292 row[3] = x;
17293 row[4] = y;
17294 row[5] = width;
17295 row[6] = height;
17296 row[7] = attributes ^ (MsiInterop.MsidbControlAttributesVisible | MsiInterop.MsidbControlAttributesEnabled);
17297 if ("BBControl" == table.Name)
17298 {
17299 row[8] = text; // BBControl.Text
17300
17301 if (null != sourceFile)
17302 {
17303 Row wixBBControlRow = this.core.CreateRow(sourceLineNumbers, "WixBBControl");
17304 wixBBControlRow.Access = id.Access;
17305 wixBBControlRow[0] = dialog;
17306 wixBBControlRow[1] = id.Id;
17307 wixBBControlRow[2] = sourceFile;
17308 }
17309 }
17310 else
17311 {
17312 row[8] = !String.IsNullOrEmpty(property) ? property : checkBoxPropertyRef;
17313 row[9] = text;
17314 if (null != tooltip || null != help)
17315 {
17316 row[11] = String.Concat(tooltip, "|", help); // Separator is required, even if only one is non-null.
17317 }
17318
17319 if (null != sourceFile)
17320 {
17321 Row wixControlRow = this.core.CreateRow(sourceLineNumbers, "WixControl");
17322 wixControlRow.Access = id.Access;
17323 wixControlRow[0] = dialog;
17324 wixControlRow[1] = id.Id;
17325 wixControlRow[2] = sourceFile;
17326 }
17327 }
17328 }
17329
17330 if (!notTabbable)
17331 {
17332 if ("BBControl" == table.Name)
17333 {
17334 this.core.OnMessage(WixErrors.TabbableControlNotAllowedInBillboard(sourceLineNumbers, node.Name.LocalName, controlType));
17335 }
17336
17337 if (null == firstControl)
17338 {
17339 firstControl = id.Id;
17340 }
17341
17342 if (null != lastTabRow)
17343 {
17344 lastTabRow[10] = id.Id;
17345 }
17346 lastTabRow = row;
17347 }
17348
17349 // bitmap and icon controls contain a foreign key into the binary table in the text column;
17350 // add a reference if the identifier of the binary entry is known during compilation
17351 if (("Bitmap" == controlType || "Icon" == controlType) && Common.IsIdentifier(text))
17352 {
17353 this.core.CreateSimpleReference(sourceLineNumbers, "Binary", text);
17354 }
17355 }
17356
17357 /// <summary>
17358 /// Parses a publish control event element.
17359 /// </summary>
17360 /// <param name="node">Element to parse.</param>
17361 /// <param name="dialog">Identifier of parent dialog.</param>
17362 /// <param name="control">Identifier of parent control.</param>
17363 /// <param name="order">Relative order of controls.</param>
17364 private void ParsePublishElement(XElement node, string dialog, string control, ref int order)
17365 {
17366 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
17367 string argument = null;
17368 string condition = null;
17369 string controlEvent = null;
17370 string property = null;
17371
17372 // give this control event a unique ordering
17373 order++;
17374
17375 foreach (XAttribute attrib in node.Attributes())
17376 {
17377 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
17378 {
17379 switch (attrib.Name.LocalName)
17380 {
17381 case "Control":
17382 if (null != control)
17383 {
17384 this.core.OnMessage(WixErrors.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, node.Parent.Name.LocalName));
17385 }
17386 control = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
17387 break;
17388 case "Dialog":
17389 if (null != dialog)
17390 {
17391 this.core.OnMessage(WixErrors.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, node.Parent.Name.LocalName));
17392 }
17393 dialog = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
17394 this.core.CreateSimpleReference(sourceLineNumbers, "Dialog", dialog);
17395 break;
17396 case "Event":
17397 controlEvent = Compiler.UppercaseFirstChar(this.core.GetAttributeValue(sourceLineNumbers, attrib));
17398 break;
17399 case "Order":
17400 order = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, 2147483647);
17401 break;
17402 case "Property":
17403 property = String.Concat("[", this.core.GetAttributeValue(sourceLineNumbers, attrib), "]");
17404 break;
17405 case "Value":
17406 argument = this.core.GetAttributeValue(sourceLineNumbers, attrib);
17407 break;
17408 default:
17409 this.core.UnexpectedAttribute(node, attrib);
17410 break;
17411 }
17412 }
17413 else
17414 {
17415 this.core.ParseExtensionAttribute(node, attrib);
17416 }
17417 }
17418
17419 condition = this.core.GetConditionInnerText(node);
17420
17421 if (null == control)
17422 {
17423 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Control"));
17424 }
17425
17426 if (null == dialog)
17427 {
17428 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Dialog"));
17429 }
17430
17431 if (null == controlEvent && null == property) // need to specify at least one
17432 {
17433 this.core.OnMessage(WixErrors.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "Event", "Property"));
17434 }
17435 else if (null != controlEvent && null != property) // cannot specify both
17436 {
17437 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Event", "Property"));
17438 }
17439
17440 if (null == argument)
17441 {
17442 if (null != controlEvent)
17443 {
17444 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value", "Event"));
17445 }
17446 else if (null != property)
17447 {
17448 // if this is setting a property to null, put a special value in the argument column
17449 argument = "{}";
17450 }
17451 }
17452
17453 this.core.ParseForExtensionElements(node);
17454
17455 if (!this.core.EncounteredError)
17456 {
17457 Row row = this.core.CreateRow(sourceLineNumbers, "ControlEvent");
17458 row[0] = dialog;
17459 row[1] = control;
17460 row[2] = (null != controlEvent ? controlEvent : property);
17461 row[3] = argument;
17462 row[4] = condition;
17463 row[5] = order;
17464 }
17465
17466 if ("DoAction" == controlEvent && null != argument)
17467 {
17468 // if we're not looking at a standard action or a formatted string then create a reference
17469 // to the custom action.
17470 if (!WindowsInstallerStandard.IsStandardAction(argument) && !Common.ContainsProperty(argument))
17471 {
17472 this.core.CreateSimpleReference(sourceLineNumbers, "CustomAction", argument);
17473 }
17474 }
17475
17476 // if we're referring to a dialog but not through a property, add it to the references
17477 if (("NewDialog" == controlEvent || "SpawnDialog" == controlEvent || "SpawnWaitDialog" == controlEvent || "SelectionBrowse" == controlEvent) && Common.IsIdentifier(argument))
17478 {
17479 this.core.CreateSimpleReference(sourceLineNumbers, "Dialog", argument);
17480 }
17481 }
17482
17483 /// <summary>
17484 /// Parses a control subscription element.
17485 /// </summary>
17486 /// <param name="node">Element to parse.</param>
17487 /// <param name="dialog">Identifier of dialog.</param>
17488 /// <param name="control">Identifier of control.</param>
17489 private void ParseSubscribeElement(XElement node, string dialog, string control)
17490 {
17491 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
17492 string controlAttribute = null;
17493 string eventMapping = null;
17494
17495 foreach (XAttribute attrib in node.Attributes())
17496 {
17497 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
17498 {
17499 switch (attrib.Name.LocalName)
17500 {
17501 case "Attribute":
17502 controlAttribute = Compiler.UppercaseFirstChar(this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib));
17503 break;
17504 case "Event":
17505 eventMapping = Compiler.UppercaseFirstChar(this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib));
17506 break;
17507 default:
17508 this.core.UnexpectedAttribute(node, attrib);
17509 break;
17510 }
17511 }
17512 else
17513 {
17514 this.core.ParseExtensionAttribute(node, attrib);
17515 }
17516 }
17517
17518 this.core.ParseForExtensionElements(node);
17519
17520 if (!this.core.EncounteredError)
17521 {
17522 Row row = this.core.CreateRow(sourceLineNumbers, "EventMapping");
17523 row[0] = dialog;
17524 row[1] = control;
17525 row[2] = eventMapping;
17526 row[3] = controlAttribute;
17527 }
17528 }
17529
17530 /// <summary>
17531 /// Parses an upgrade element.
17532 /// </summary>
17533 /// <param name="node">Element to parse.</param>
17534 private void ParseUpgradeElement(XElement node)
17535 {
17536 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
17537 string id = null;
17538
17539 foreach (XAttribute attrib in node.Attributes())
17540 {
17541 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
17542 {
17543 switch (attrib.Name.LocalName)
17544 {
17545 case "Id":
17546 id = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
17547 break;
17548 default:
17549 this.core.UnexpectedAttribute(node, attrib);
17550 break;
17551 }
17552 }
17553 else
17554 {
17555 this.core.ParseExtensionAttribute(node, attrib);
17556 }
17557 }
17558
17559 if (null == id)
17560 {
17561 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
17562 }
17563
17564 // process the UpgradeVersion children here
17565 foreach (XElement child in node.Elements())
17566 {
17567 if (CompilerCore.WixNamespace == child.Name.Namespace)
17568 {
17569 SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
17570
17571 switch (child.Name.LocalName)
17572 {
17573 case "Property":
17574 this.ParsePropertyElement(child);
17575 this.core.OnMessage(WixWarnings.DeprecatedUpgradeProperty(childSourceLineNumbers));
17576 break;
17577 case "UpgradeVersion":
17578 this.ParseUpgradeVersionElement(child, id);
17579 break;
17580 default:
17581 this.core.UnexpectedElement(node, child);
17582 break;
17583 }
17584 }
17585 else
17586 {
17587 this.core.ParseExtensionElement(node, child);
17588 }
17589 }
17590
17591
17592 // No rows created here. All row creation is done in ParseUpgradeVersionElement.
17593 }
17594
17595 /// <summary>
17596 /// Parse upgrade version element.
17597 /// </summary>
17598 /// <param name="node">Element to parse.</param>
17599 /// <param name="upgradeId">Upgrade code.</param>
17600 private void ParseUpgradeVersionElement(XElement node, string upgradeId)
17601 {
17602 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
17603
17604 string actionProperty = null;
17605 string language = null;
17606 string maximum = null;
17607 string minimum = null;
17608 int options = 256;
17609 string removeFeatures = null;
17610
17611 foreach (XAttribute attrib in node.Attributes())
17612 {
17613 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
17614 {
17615 switch (attrib.Name.LocalName)
17616 {
17617 case "ExcludeLanguages":
17618 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
17619 {
17620 options |= MsiInterop.MsidbUpgradeAttributesLanguagesExclusive;
17621 }
17622 break;
17623 case "IgnoreRemoveFailure":
17624 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
17625 {
17626 options |= MsiInterop.MsidbUpgradeAttributesIgnoreRemoveFailure;
17627 }
17628 break;
17629 case "IncludeMaximum":
17630 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
17631 {
17632 options |= MsiInterop.MsidbUpgradeAttributesVersionMaxInclusive;
17633 }
17634 break;
17635 case "IncludeMinimum": // this is "yes" by default
17636 if (YesNoType.No == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
17637 {
17638 options &= ~MsiInterop.MsidbUpgradeAttributesVersionMinInclusive;
17639 }
17640 break;
17641 case "Language":
17642 language = this.core.GetAttributeValue(sourceLineNumbers, attrib);
17643 break;
17644 case "Minimum":
17645 minimum = this.core.GetAttributeVersionValue(sourceLineNumbers, attrib);
17646 break;
17647 case "Maximum":
17648 maximum = this.core.GetAttributeVersionValue(sourceLineNumbers, attrib);
17649 break;
17650 case "MigrateFeatures":
17651 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
17652 {
17653 options |= MsiInterop.MsidbUpgradeAttributesMigrateFeatures;
17654 }
17655 break;
17656 case "OnlyDetect":
17657 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
17658 {
17659 options |= MsiInterop.MsidbUpgradeAttributesOnlyDetect;
17660 }
17661 break;
17662 case "Property":
17663 actionProperty = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
17664 break;
17665 case "RemoveFeatures":
17666 removeFeatures = this.core.GetAttributeValue(sourceLineNumbers, attrib);
17667 break;
17668 default:
17669 this.core.UnexpectedAttribute(node, attrib);
17670 break;
17671 }
17672 }
17673 else
17674 {
17675 this.core.ParseExtensionAttribute(node, attrib);
17676 }
17677 }
17678
17679 if (null == actionProperty)
17680 {
17681 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Property"));
17682 }
17683 else if (actionProperty.ToUpper(CultureInfo.InvariantCulture) != actionProperty)
17684 {
17685 this.core.OnMessage(WixErrors.SecurePropertyNotUppercase(sourceLineNumbers, node.Name.LocalName, "Property", actionProperty));
17686 }
17687
17688 if (null == minimum && null == maximum)
17689 {
17690 this.core.OnMessage(WixErrors.ExpectedAttributes(sourceLineNumbers, node.Name.LocalName, "Minimum", "Maximum"));
17691 }
17692
17693 this.core.ParseForExtensionElements(node);
17694
17695 if (!this.core.EncounteredError)
17696 {
17697 Row row = this.core.CreateRow(sourceLineNumbers, "Upgrade");
17698 row[0] = upgradeId;
17699 row[1] = minimum;
17700 row[2] = maximum;
17701 row[3] = language;
17702 row[4] = options;
17703 row[5] = removeFeatures;
17704 row[6] = actionProperty;
17705
17706 // Ensure the action property is secure.
17707 this.AddWixPropertyRow(sourceLineNumbers, new Identifier(actionProperty, AccessModifier.Private), false, true, false);
17708
17709 // Ensure that RemoveExistingProducts is authored in InstallExecuteSequence
17710 // if at least one row in Upgrade table lacks the OnlyDetect attribute.
17711 if (0 == (options & MsiInterop.MsidbUpgradeAttributesOnlyDetect))
17712 {
17713 this.core.CreateSimpleReference(sourceLineNumbers, "WixAction", "InstallExecuteSequence", "RemoveExistingProducts");
17714 }
17715 }
17716 }
17717
17718 /// <summary>
17719 /// Parses a verb element.
17720 /// </summary>
17721 /// <param name="node">Element to parse.</param>
17722 /// <param name="extension">Extension verb is releated to.</param>
17723 /// <param name="progId">Optional progId for extension.</param>
17724 /// <param name="componentId">Identifier for parent component.</param>
17725 /// <param name="advertise">Flag if verb is advertised.</param>
17726 private void ParseVerbElement(XElement node, string extension, string progId, string componentId, YesNoType advertise)
17727 {
17728 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
17729 string id = null;
17730 string argument = null;
17731 string command = null;
17732 int sequence = CompilerConstants.IntegerNotSet;
17733 string target = null;
17734 string targetFile = null;
17735 string targetProperty = null;
17736
17737 foreach (XAttribute attrib in node.Attributes())
17738 {
17739 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
17740 {
17741 switch (attrib.Name.LocalName)
17742 {
17743 case "Id":
17744 id = this.core.GetAttributeValue(sourceLineNumbers, attrib);
17745 break;
17746 case "Argument":
17747 argument = this.core.GetAttributeValue(sourceLineNumbers, attrib);
17748 break;
17749 case "Command":
17750 command = this.core.GetAttributeValue(sourceLineNumbers, attrib);
17751 break;
17752 case "Sequence":
17753 sequence = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 1, short.MaxValue);
17754 break;
17755 case "Target":
17756 target = this.core.GetAttributeValue(sourceLineNumbers, attrib);
17757 this.core.OnMessage(WixWarnings.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, "TargetFile", "TargetProperty"));
17758 break;
17759 case "TargetFile":
17760 targetFile = this.core.GetAttributeValue(sourceLineNumbers, attrib);
17761 this.core.CreateSimpleReference(sourceLineNumbers, "File", targetFile);
17762 break;
17763 case "TargetProperty":
17764 targetProperty = this.core.GetAttributeValue(sourceLineNumbers, attrib);
17765 break;
17766 default:
17767 this.core.UnexpectedAttribute(node, attrib);
17768 break;
17769 }
17770 }
17771 else
17772 {
17773 this.core.ParseExtensionAttribute(node, attrib);
17774 }
17775 }
17776
17777 if (null == id)
17778 {
17779 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
17780 }
17781
17782 if (null != target && null != targetFile)
17783 {
17784 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Target", "TargetFile"));
17785 }
17786
17787 if (null != target && null != targetProperty)
17788 {
17789 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Target", "TargetProperty"));
17790 }
17791
17792 if (null != targetFile && null != targetProperty)
17793 {
17794 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "TargetFile", "TargetProperty"));
17795 }
17796
17797 this.core.ParseForExtensionElements(node);
17798
17799 if (YesNoType.Yes == advertise)
17800 {
17801 if (null != target)
17802 {
17803 this.core.OnMessage(WixErrors.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "Target"));
17804 }
17805
17806 if (null != targetFile)
17807 {
17808 this.core.OnMessage(WixErrors.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "TargetFile"));
17809 }
17810
17811 if (null != targetProperty)
17812 {
17813 this.core.OnMessage(WixErrors.IllegalAttributeWhenAdvertised(sourceLineNumbers, node.Name.LocalName, "TargetProperty"));
17814 }
17815
17816 if (!this.core.EncounteredError)
17817 {
17818 Row row = this.core.CreateRow(sourceLineNumbers, "Verb");
17819 row[0] = extension;
17820 row[1] = id;
17821 if (CompilerConstants.IntegerNotSet != sequence)
17822 {
17823 row[2] = sequence;
17824 }
17825 row[3] = command;
17826 row[4] = argument;
17827 }
17828 }
17829 else if (YesNoType.No == advertise)
17830 {
17831 if (CompilerConstants.IntegerNotSet != sequence)
17832 {
17833 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Sequence", "Advertise", "no"));
17834 }
17835
17836 if (null == target && null == targetFile && null == targetProperty)
17837 {
17838 this.core.OnMessage(WixErrors.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "TargetFile", "TargetProperty", "Advertise", "no"));
17839 }
17840
17841 if (null == target)
17842 {
17843 if (null != targetFile)
17844 {
17845 target = String.Concat("\"[#", targetFile, "]\"");
17846 }
17847
17848 if (null != targetProperty)
17849 {
17850 target = String.Concat("\"[", targetProperty, "]\"");
17851 }
17852 }
17853
17854 if (null != argument)
17855 {
17856 target = String.Concat(target, " ", argument);
17857 }
17858
17859 string prefix = (null != progId ? progId : String.Concat(".", extension));
17860
17861 if (null != command)
17862 {
17863 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat(prefix, "\\shell\\", id), String.Empty, command, componentId);
17864 }
17865
17866 this.core.CreateRegistryRow(sourceLineNumbers, MsiInterop.MsidbRegistryRootClassesRoot, String.Concat(prefix, "\\shell\\", id, "\\command"), String.Empty, target, componentId);
17867 }
17868 }
17869
17870
17871 /// <summary>
17872 /// Parses an ApprovedExeForElevation element.
17873 /// </summary>
17874 /// <param name="node">Element to parse</param>
17875 private void ParseApprovedExeForElevation(XElement node)
17876 {
17877 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
17878 Identifier id = null;
17879 string key = null;
17880 string valueName = null;
17881 YesNoType win64 = YesNoType.NotSet;
17882
17883 foreach (XAttribute attrib in node.Attributes())
17884 {
17885 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
17886 {
17887 switch (attrib.Name.LocalName)
17888 {
17889 case "Id":
17890 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
17891 break;
17892 case "Key":
17893 key = this.core.GetAttributeValue(sourceLineNumbers, attrib);
17894 break;
17895 case "Value":
17896 valueName = this.core.GetAttributeValue(sourceLineNumbers, attrib);
17897 break;
17898 case "Win64":
17899 win64 = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
17900 break;
17901 default:
17902 this.core.UnexpectedAttribute(node, attrib);
17903 break;
17904 }
17905 }
17906 else
17907 {
17908 this.core.ParseExtensionAttribute(node, attrib);
17909 }
17910 }
17911
17912 if (null == id)
17913 {
17914 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
17915 }
17916
17917 if (null == key)
17918 {
17919 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Key"));
17920 }
17921
17922 BundleApprovedExeForElevationAttributes attributes = BundleApprovedExeForElevationAttributes.None;
17923
17924 if (win64 == YesNoType.Yes)
17925 {
17926 attributes |= BundleApprovedExeForElevationAttributes.Win64;
17927 }
17928
17929 this.core.ParseForExtensionElements(node);
17930
17931 if (!this.core.EncounteredError)
17932 {
17933 WixApprovedExeForElevationRow wixApprovedExeForElevationRow = (WixApprovedExeForElevationRow)this.core.CreateRow(sourceLineNumbers, "WixApprovedExeForElevation", id);
17934 wixApprovedExeForElevationRow.Key = key;
17935 wixApprovedExeForElevationRow.ValueName = valueName;
17936 wixApprovedExeForElevationRow.Attributes = attributes;
17937 }
17938 }
17939
17940 /// <summary>
17941 /// Parses a Bundle element.
17942 /// </summary>
17943 /// <param name="node">Element to parse</param>
17944 private void ParseBundleElement(XElement node)
17945 {
17946 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
17947 string copyright = null;
17948 string aboutUrl = null;
17949 YesNoDefaultType compressed = YesNoDefaultType.Default;
17950 int disableModify = -1;
17951 YesNoType disableRemove = YesNoType.NotSet;
17952 string helpTelephone = null;
17953 string helpUrl = null;
17954 string manufacturer = null;
17955 string name = null;
17956 string tag = null;
17957 string updateUrl = null;
17958 string upgradeCode = null;
17959 string version = null;
17960 string condition = null;
17961 string parentName = null;
17962
17963 string fileSystemSafeBundleName = null;
17964 string logVariablePrefixAndExtension = null;
17965 string iconSourceFile = null;
17966 string splashScreenSourceFile = null;
17967
17968 // Process only standard attributes until the active section is initialized.
17969 foreach (XAttribute attrib in node.Attributes())
17970 {
17971 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
17972 {
17973 switch (attrib.Name.LocalName)
17974 {
17975 case "AboutUrl":
17976 aboutUrl = this.core.GetAttributeValue(sourceLineNumbers, attrib);
17977 break;
17978 case "Compressed":
17979 compressed = this.core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib);
17980 break;
17981 case "Condition":
17982 condition = this.core.GetAttributeValue(sourceLineNumbers, attrib);
17983 break;
17984 case "Copyright":
17985 copyright = this.core.GetAttributeValue(sourceLineNumbers, attrib);
17986 break;
17987 case "DisableModify":
17988 string value = this.core.GetAttributeValue(sourceLineNumbers, attrib);
17989 switch (value)
17990 {
17991 case "button":
17992 disableModify = 2;
17993 break;
17994 case "yes":
17995 disableModify = 1;
17996 break;
17997 case "no":
17998 disableModify = 0;
17999 break;
18000 default:
18001 this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName, value, "button", "yes", "no"));
18002 break;
18003 }
18004 break;
18005 case "DisableRemove":
18006 disableRemove = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
18007 break;
18008 case "DisableRepair":
18009 this.core.OnMessage(WixWarnings.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName));
18010 break;
18011 case "HelpTelephone":
18012 helpTelephone = this.core.GetAttributeValue(sourceLineNumbers, attrib);
18013 break;
18014 case "HelpUrl":
18015 helpUrl = this.core.GetAttributeValue(sourceLineNumbers, attrib);
18016 break;
18017 case "Manufacturer":
18018 manufacturer = this.core.GetAttributeValue(sourceLineNumbers, attrib);
18019 break;
18020 case "IconSourceFile":
18021 iconSourceFile = this.core.GetAttributeValue(sourceLineNumbers, attrib);
18022 break;
18023 case "Name":
18024 name = this.core.GetAttributeValue(sourceLineNumbers, attrib);
18025 break;
18026 case "ParentName":
18027 parentName = this.core.GetAttributeValue(sourceLineNumbers, attrib);
18028 break;
18029 case "SplashScreenSourceFile":
18030 splashScreenSourceFile = this.core.GetAttributeValue(sourceLineNumbers, attrib);
18031 break;
18032 case "Tag":
18033 tag = this.core.GetAttributeValue(sourceLineNumbers, attrib);
18034 break;
18035 case "UpdateUrl":
18036 updateUrl = this.core.GetAttributeValue(sourceLineNumbers, attrib);
18037 break;
18038 case "UpgradeCode":
18039 upgradeCode = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
18040 break;
18041 case "Version":
18042 version = this.core.GetAttributeVersionValue(sourceLineNumbers, attrib);
18043 break;
18044 default:
18045 this.core.UnexpectedAttribute(node, attrib);
18046 break;
18047 }
18048 }
18049 }
18050
18051 if (String.IsNullOrEmpty(version))
18052 {
18053 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version"));
18054 }
18055 else if (!CompilerCore.IsValidModuleOrBundleVersion(version))
18056 {
18057 this.core.OnMessage(WixWarnings.InvalidModuleOrBundleVersion(sourceLineNumbers, "Bundle", version));
18058 }
18059
18060 if (String.IsNullOrEmpty(upgradeCode))
18061 {
18062 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "UpgradeCode"));
18063 }
18064
18065 if (String.IsNullOrEmpty(copyright))
18066 {
18067 if (String.IsNullOrEmpty(manufacturer))
18068 {
18069 copyright = "Copyright (c). All rights reserved.";
18070 }
18071 else
18072 {
18073 copyright = String.Format("Copyright (c) {0}. All rights reserved.", manufacturer);
18074 }
18075 }
18076
18077 if (String.IsNullOrEmpty(name))
18078 {
18079 logVariablePrefixAndExtension = String.Concat("WixBundleLog:Setup.log");
18080 }
18081 else
18082 {
18083 // Ensure only allowable path characters are in "name" (and change spaces to underscores).
18084 fileSystemSafeBundleName = CompilerCore.MakeValidLongFileName(name.Replace(' ', '_'), "_");
18085 logVariablePrefixAndExtension = String.Concat("WixBundleLog:", fileSystemSafeBundleName, ".log");
18086 }
18087
18088 this.activeName = String.IsNullOrEmpty(name) ? Common.GenerateGuid() : name;
18089 this.core.CreateActiveSection(this.activeName, SectionType.Bundle, 0);
18090
18091 // Now that the active section is initialized, process only extension attributes.
18092 foreach (XAttribute attrib in node.Attributes())
18093 {
18094 if (!String.IsNullOrEmpty(attrib.Name.NamespaceName) && CompilerCore.WixNamespace != attrib.Name.Namespace)
18095 {
18096 this.core.ParseExtensionAttribute(node, attrib);
18097 }
18098 }
18099
18100 bool baSeen = false;
18101 bool chainSeen = false;
18102 bool logSeen = false;
18103
18104 foreach (XElement child in node.Elements())
18105 {
18106 if (CompilerCore.WixNamespace == child.Name.Namespace)
18107 {
18108 switch (child.Name.LocalName)
18109 {
18110 case "ApprovedExeForElevation":
18111 this.ParseApprovedExeForElevation(child);
18112 break;
18113 case "BootstrapperApplication":
18114 if (baSeen)
18115 {
18116 SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
18117 this.core.OnMessage(WixErrors.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, "BootstrapperApplication"));
18118 }
18119 this.ParseBootstrapperApplicationElement(child);
18120 baSeen = true;
18121 break;
18122 case "BootstrapperApplicationRef":
18123 this.ParseBootstrapperApplicationRefElement(child);
18124 break;
18125 case "OptionalUpdateRegistration":
18126 this.ParseOptionalUpdateRegistrationElement(child, manufacturer, parentName, name);
18127 break;
18128 case "Catalog":
18129 this.ParseCatalogElement(child);
18130 break;
18131 case "Chain":
18132 if (chainSeen)
18133 {
18134 SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
18135 this.core.OnMessage(WixErrors.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, "Chain"));
18136 }
18137 this.ParseChainElement(child);
18138 chainSeen = true;
18139 break;
18140 case "Container":
18141 this.ParseContainerElement(child);
18142 break;
18143 case "ContainerRef":
18144 this.ParseSimpleRefElement(child, "WixBundleContainer");
18145 break;
18146 case "Log":
18147 if (logSeen)
18148 {
18149 SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
18150 this.core.OnMessage(WixErrors.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, "Log"));
18151 }
18152 logVariablePrefixAndExtension = this.ParseLogElement(child, fileSystemSafeBundleName);
18153 logSeen = true;
18154 break;
18155 case "PayloadGroup":
18156 this.ParsePayloadGroupElement(child, ComplexReferenceParentType.Layout, "BundleLayoutOnlyPayloads");
18157 break;
18158 case "PayloadGroupRef":
18159 this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Layout, "BundleLayoutOnlyPayloads", ComplexReferenceChildType.Unknown, null);
18160 break;
18161 case "RelatedBundle":
18162 this.ParseRelatedBundleElement(child);
18163 break;
18164 case "Update":
18165 this.ParseUpdateElement(child);
18166 break;
18167 case "Variable":
18168 this.ParseVariableElement(child);
18169 break;
18170 case "WixVariable":
18171 this.ParseWixVariableElement(child);
18172 break;
18173 default:
18174 this.core.UnexpectedElement(node, child);
18175 break;
18176 }
18177 }
18178 else
18179 {
18180 this.core.ParseExtensionElement(node, child);
18181 }
18182 }
18183
18184
18185 if (!chainSeen)
18186 {
18187 this.core.OnMessage(WixErrors.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "Chain"));
18188 }
18189
18190 if (!this.core.EncounteredError)
18191 {
18192 if (null != upgradeCode)
18193 {
18194 Row relatedBundleRow = this.core.CreateRow(sourceLineNumbers, "WixRelatedBundle");
18195 relatedBundleRow[0] = upgradeCode;
18196 relatedBundleRow[1] = (int)Wix.RelatedBundle.ActionType.Upgrade;
18197 }
18198
18199 WixBundleContainerRow containerRow = (WixBundleContainerRow)this.core.CreateRow(sourceLineNumbers, "WixBundleContainer");
18200 containerRow.Id = Compiler.BurnDefaultAttachedContainerId;
18201 containerRow.Name = "bundle-attached.cab";
18202 containerRow.Type = ContainerType.Attached;
18203
18204 Row row = this.core.CreateRow(sourceLineNumbers, "WixBundle");
18205 row[0] = version;
18206 row[1] = copyright;
18207 row[2] = name;
18208 row[3] = aboutUrl;
18209 if (-1 != disableModify)
18210 {
18211 row[4] = disableModify;
18212 }
18213 if (YesNoType.NotSet != disableRemove)
18214 {
18215 row[5] = (YesNoType.Yes == disableRemove) ? 1 : 0;
18216 }
18217 // row[6] - (deprecated) "disable repair"
18218 row[7] = helpTelephone;
18219 row[8] = helpUrl;
18220 row[9] = manufacturer;
18221 row[10] = updateUrl;
18222 if (YesNoDefaultType.Default != compressed)
18223 {
18224 row[11] = (YesNoDefaultType.Yes == compressed) ? 1 : 0;
18225 }
18226
18227 row[12] = logVariablePrefixAndExtension;
18228 row[13] = iconSourceFile;
18229 row[14] = splashScreenSourceFile;
18230 row[15] = condition;
18231 row[16] = tag;
18232 row[17] = this.CurrentPlatform.ToString();
18233 row[18] = parentName;
18234 row[19] = upgradeCode;
18235
18236 // Ensure that the bundle stores the well-known persisted values.
18237 WixBundleVariableRow bundleNameWellKnownVariable = (WixBundleVariableRow)this.core.CreateRow(sourceLineNumbers, "WixBundleVariable");
18238 bundleNameWellKnownVariable.Id = Compiler.BURN_BUNDLE_NAME;
18239 bundleNameWellKnownVariable.Hidden = false;
18240 bundleNameWellKnownVariable.Persisted = true;
18241
18242 WixBundleVariableRow bundleOriginalSourceWellKnownVariable = (WixBundleVariableRow)this.core.CreateRow(sourceLineNumbers, "WixBundleVariable");
18243 bundleOriginalSourceWellKnownVariable.Id = Compiler.BURN_BUNDLE_ORIGINAL_SOURCE;
18244 bundleOriginalSourceWellKnownVariable.Hidden = false;
18245 bundleOriginalSourceWellKnownVariable.Persisted = true;
18246
18247 WixBundleVariableRow bundleOriginalSourceFolderWellKnownVariable = (WixBundleVariableRow)this.core.CreateRow(sourceLineNumbers, "WixBundleVariable");
18248 bundleOriginalSourceFolderWellKnownVariable.Id = Compiler.BURN_BUNDLE_ORIGINAL_SOURCE_FOLDER;
18249 bundleOriginalSourceFolderWellKnownVariable.Hidden = false;
18250 bundleOriginalSourceFolderWellKnownVariable.Persisted = true;
18251
18252 WixBundleVariableRow bundleLastUsedSourceWellKnownVariable = (WixBundleVariableRow)this.core.CreateRow(sourceLineNumbers, "WixBundleVariable");
18253 bundleLastUsedSourceWellKnownVariable.Id = Compiler.BURN_BUNDLE_LAST_USED_SOURCE;
18254 bundleLastUsedSourceWellKnownVariable.Hidden = false;
18255 bundleLastUsedSourceWellKnownVariable.Persisted = true;
18256 }
18257 }
18258
18259 /// <summary>
18260 /// Parse a Container element.
18261 /// </summary>
18262 /// <param name="node">Element to parse</param>
18263 private string ParseLogElement(XElement node, string fileSystemSafeBundleName)
18264 {
18265 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
18266 YesNoType disableLog = YesNoType.NotSet;
18267 string variable = "WixBundleLog";
18268 string logPrefix = fileSystemSafeBundleName ?? "Setup";
18269 string logExtension = ".log";
18270
18271 foreach (XAttribute attrib in node.Attributes())
18272 {
18273 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
18274 {
18275 switch (attrib.Name.LocalName)
18276 {
18277 case "Disable":
18278 disableLog = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
18279 break;
18280 case "PathVariable":
18281 variable = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty);
18282 break;
18283 case "Prefix":
18284 logPrefix = this.core.GetAttributeValue(sourceLineNumbers, attrib);
18285 break;
18286 case "Extension":
18287 logExtension = this.core.GetAttributeValue(sourceLineNumbers, attrib);
18288 break;
18289 default:
18290 this.core.UnexpectedAttribute(node, attrib);
18291 break;
18292 }
18293 }
18294 else
18295 {
18296 this.core.ParseExtensionAttribute(node, attrib);
18297 }
18298 }
18299
18300 if (!logExtension.StartsWith(".", StringComparison.Ordinal))
18301 {
18302 logExtension = String.Concat(".", logExtension);
18303 }
18304
18305 this.core.ParseForExtensionElements(node);
18306
18307 return YesNoType.Yes == disableLog ? null : String.Concat(variable, ":", logPrefix, logExtension);
18308 }
18309
18310 /// <summary>
18311 /// Parse a Catalog element.
18312 /// </summary>
18313 /// <param name="node">Element to parse</param>
18314 private void ParseCatalogElement(XElement node)
18315 {
18316 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
18317 Identifier id = null;
18318 string sourceFile = null;
18319
18320 foreach (XAttribute attrib in node.Attributes())
18321 {
18322 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
18323 {
18324 switch (attrib.Name.LocalName)
18325 {
18326 case "Id":
18327 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
18328 break;
18329 case "SourceFile":
18330 sourceFile = this.core.GetAttributeValue(sourceLineNumbers, attrib);
18331 break;
18332 default:
18333 this.core.UnexpectedAttribute(node, attrib);
18334 break;
18335 }
18336 }
18337 }
18338
18339 if (null == id)
18340 {
18341 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
18342 }
18343
18344 if (null == sourceFile)
18345 {
18346 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "SourceFile"));
18347 }
18348
18349 this.core.ParseForExtensionElements(node);
18350
18351 // Create catalog row
18352 if (!this.core.EncounteredError)
18353 {
18354 this.CreatePayloadRow(sourceLineNumbers, id, Path.GetFileName(sourceFile), sourceFile, null, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, ComplexReferenceChildType.Unknown, null, YesNoDefaultType.Yes, YesNoType.Yes, null, null, null);
18355
18356 WixBundleCatalogRow wixCatalogRow = (WixBundleCatalogRow)this.core.CreateRow(sourceLineNumbers, "WixBundleCatalog", id);
18357 wixCatalogRow.Payload = id.Id;
18358 }
18359 }
18360
18361 /// <summary>
18362 /// Parse a Container element.
18363 /// </summary>
18364 /// <param name="node">Element to parse</param>
18365 private void ParseContainerElement(XElement node)
18366 {
18367 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
18368 Identifier id = null;
18369 string downloadUrl = null;
18370 string name = null;
18371 ContainerType type = ContainerType.Detached;
18372
18373 foreach (XAttribute attrib in node.Attributes())
18374 {
18375 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
18376 {
18377 switch (attrib.Name.LocalName)
18378 {
18379 case "Id":
18380 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
18381 break;
18382 case "DownloadUrl":
18383 downloadUrl = this.core.GetAttributeValue(sourceLineNumbers, attrib);
18384 break;
18385 case "Name":
18386 name = this.core.GetAttributeValue(sourceLineNumbers, attrib);
18387 break;
18388 case "Type":
18389 string typeString = this.core.GetAttributeValue(sourceLineNumbers, attrib);
18390 if (!Enum.TryParse<ContainerType>(typeString, out type))
18391 {
18392 this.core.OnMessage(WixErrors.IllegalAttributeValueWithLegalList(sourceLineNumbers, node.Name.LocalName, "Type", typeString, "attached, detached"));
18393 }
18394 break;
18395 default:
18396 this.core.UnexpectedAttribute(node, attrib);
18397 break;
18398 }
18399 }
18400 else
18401 {
18402 this.core.ParseExtensionAttribute(node, attrib);
18403 }
18404 }
18405
18406 if (null == id)
18407 {
18408 if (!String.IsNullOrEmpty(name))
18409 {
18410 id = this.core.CreateIdentifierFromFilename(name);
18411 }
18412
18413 if (null == id)
18414 {
18415 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
18416 id = Identifier.Invalid;
18417 }
18418 else if (!Common.IsIdentifier(id.Id))
18419 {
18420 this.core.OnMessage(WixErrors.IllegalIdentifier(sourceLineNumbers, node.Name.LocalName, "Id", id.Id));
18421 }
18422 }
18423 else if (null == name)
18424 {
18425 name = id.Id;
18426 }
18427
18428 if (!String.IsNullOrEmpty(downloadUrl) && ContainerType.Detached != type)
18429 {
18430 this.core.OnMessage(WixErrors.IllegalAttributeWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "DownloadUrl", "Type", "attached"));
18431 }
18432
18433 foreach (XElement child in node.Elements())
18434 {
18435 if (CompilerCore.WixNamespace == child.Name.Namespace)
18436 {
18437 switch (child.Name.LocalName)
18438 {
18439 case "PackageGroupRef":
18440 this.ParsePackageGroupRefElement(child, ComplexReferenceParentType.Container, id.Id);
18441 break;
18442 default:
18443 this.core.UnexpectedElement(node, child);
18444 break;
18445 }
18446 }
18447 else
18448 {
18449 this.core.ParseExtensionElement(node, child);
18450 }
18451 }
18452
18453
18454 if (!this.core.EncounteredError)
18455 {
18456 WixBundleContainerRow row = (WixBundleContainerRow)this.core.CreateRow(sourceLineNumbers, "WixBundleContainer", id);
18457 row.Name = name;
18458 row.Type = type;
18459 row.DownloadUrl = downloadUrl;
18460 }
18461 }
18462
18463 /// <summary>
18464 /// Parse the BoostrapperApplication element.
18465 /// </summary>
18466 /// <param name="node">Element to parse</param>
18467 private void ParseBootstrapperApplicationElement(XElement node)
18468 {
18469 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
18470 string id = null;
18471 string previousId = null;
18472 ComplexReferenceChildType previousType = ComplexReferenceChildType.Unknown;
18473
18474 // The BootstrapperApplication element acts like a Payload element so delegate to the "Payload" attribute parsing code to parse and create a Payload entry.
18475 id = this.ParsePayloadElementContent(node, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId, false);
18476 if (null != id)
18477 {
18478 previousId = id;
18479 previousType = ComplexReferenceChildType.Payload;
18480 }
18481
18482 foreach (XElement child in node.Elements())
18483 {
18484 if (CompilerCore.WixNamespace == child.Name.Namespace)
18485 {
18486 switch (child.Name.LocalName)
18487 {
18488 case "Payload":
18489 previousId = this.ParsePayloadElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId);
18490 previousType = ComplexReferenceChildType.Payload;
18491 break;
18492 case "PayloadGroupRef":
18493 previousId = this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId);
18494 previousType = ComplexReferenceChildType.PayloadGroup;
18495 break;
18496 default:
18497 this.core.UnexpectedElement(node, child);
18498 break;
18499 }
18500 }
18501 else
18502 {
18503 this.core.ParseExtensionElement(node, child);
18504 }
18505 }
18506
18507 if (null == previousId)
18508 {
18509 // We need *either* <Payload> or <PayloadGroupRef> or even just @SourceFile on the BA...
18510 // but we just say there's a missing <Payload>.
18511 // TODO: Is there a better message for this?
18512 this.core.OnMessage(WixErrors.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "Payload"));
18513 }
18514
18515 // Add the application as an attached container and if an Id was provided add that too.
18516 if (!this.core.EncounteredError)
18517 {
18518 WixBundleContainerRow containerRow = (WixBundleContainerRow)this.core.CreateRow(sourceLineNumbers, "WixBundleContainer");
18519 containerRow.Id = Compiler.BurnUXContainerId;
18520 containerRow.Name = "bundle-ux.cab";
18521 containerRow.Type = ContainerType.Attached;
18522
18523 if (!String.IsNullOrEmpty(id))
18524 {
18525 Row row = this.core.CreateRow(sourceLineNumbers, "WixBootstrapperApplication");
18526 row[0] = id;
18527 }
18528 }
18529 }
18530
18531 /// <summary>
18532 /// Parse the BoostrapperApplicationRef element.
18533 /// </summary>
18534 /// <param name="node">Element to parse</param>
18535 private void ParseBootstrapperApplicationRefElement(XElement node)
18536 {
18537 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
18538 string id = null;
18539 string previousId = null;
18540 ComplexReferenceChildType previousType = ComplexReferenceChildType.Unknown;
18541
18542 foreach (XAttribute attrib in node.Attributes())
18543 {
18544 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
18545 {
18546 switch (attrib.Name.LocalName)
18547 {
18548 case "Id":
18549 id = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
18550 break;
18551 default:
18552 this.core.UnexpectedAttribute(node, attrib);
18553 break;
18554 }
18555 }
18556 else
18557 {
18558 this.core.ParseExtensionAttribute(node, attrib);
18559 }
18560 }
18561
18562 foreach (XElement child in node.Elements())
18563 {
18564 if (CompilerCore.WixNamespace == child.Name.Namespace)
18565 {
18566 switch (child.Name.LocalName)
18567 {
18568 case "Payload":
18569 previousId = this.ParsePayloadElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId);
18570 previousType = ComplexReferenceChildType.Payload;
18571 break;
18572 case "PayloadGroupRef":
18573 previousId = this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, previousType, previousId);
18574 previousType = ComplexReferenceChildType.PayloadGroup;
18575 break;
18576 default:
18577 this.core.UnexpectedElement(node, child);
18578 break;
18579 }
18580 }
18581 else
18582 {
18583 this.core.ParseExtensionElement(node, child);
18584 }
18585 }
18586
18587
18588 if (String.IsNullOrEmpty(id))
18589 {
18590 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
18591 }
18592 else
18593 {
18594 this.core.CreateSimpleReference(sourceLineNumbers, "WixBootstrapperApplication", id);
18595 }
18596 }
18597
18598 /// <summary>
18599 /// Parse the OptionalUpdateRegistration element.
18600 /// </summary>
18601 /// <param name="node">The element to parse.</param>
18602 /// <param name="defaultManufacturer">The manufacturer.</param>
18603 /// <param name="defaultProductFamily">The product family.</param>
18604 /// <param name="defaultName">The bundle name.</param>
18605 private void ParseOptionalUpdateRegistrationElement(XElement node, string defaultManufacturer, string defaultProductFamily, string defaultName)
18606 {
18607 const string defaultClassification = "Update";
18608
18609 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
18610 string manufacturer = null;
18611 string department = null;
18612 string productFamily = null;
18613 string name = null;
18614 string classification = defaultClassification;
18615
18616 foreach (XAttribute attrib in node.Attributes())
18617 {
18618 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
18619 {
18620 switch (attrib.Name.LocalName)
18621 {
18622 case "Manufacturer":
18623 manufacturer = this.core.GetAttributeValue(sourceLineNumbers, attrib);
18624 break;
18625 case "Department":
18626 department = this.core.GetAttributeValue(sourceLineNumbers, attrib);
18627 break;
18628 case "ProductFamily":
18629 productFamily = this.core.GetAttributeValue(sourceLineNumbers, attrib);
18630 break;
18631 case "Name":
18632 name = this.core.GetAttributeValue(sourceLineNumbers, attrib);
18633 break;
18634 case "Classification":
18635 classification = this.core.GetAttributeValue(sourceLineNumbers, attrib);
18636 break;
18637 default:
18638 this.core.UnexpectedAttribute(node, attrib);
18639 break;
18640 }
18641 }
18642 else
18643 {
18644 this.core.ParseExtensionAttribute(node, attrib);
18645 }
18646 }
18647
18648 if (String.IsNullOrEmpty(manufacturer))
18649 {
18650 if (!String.IsNullOrEmpty(defaultManufacturer))
18651 {
18652 manufacturer = defaultManufacturer;
18653 }
18654 else
18655 {
18656 this.core.OnMessage(WixErrors.ExpectedAttributeInElementOrParent(sourceLineNumbers, node.Name.LocalName, "Manufacturer", node.Parent.Name.LocalName));
18657 }
18658 }
18659
18660 if (String.IsNullOrEmpty(productFamily))
18661 {
18662 if (!String.IsNullOrEmpty(defaultProductFamily))
18663 {
18664 productFamily = defaultProductFamily;
18665 }
18666 }
18667
18668 if (String.IsNullOrEmpty(name))
18669 {
18670 if (!String.IsNullOrEmpty(defaultName))
18671 {
18672 name = defaultName;
18673 }
18674 else
18675 {
18676 this.core.OnMessage(WixErrors.ExpectedAttributeInElementOrParent(sourceLineNumbers, node.Name.LocalName, "Name", node.Parent.Name.LocalName));
18677 }
18678 }
18679
18680 if (String.IsNullOrEmpty(classification))
18681 {
18682 this.core.OnMessage(WixErrors.IllegalEmptyAttributeValue(sourceLineNumbers, node.Name.LocalName, "Classification", defaultClassification));
18683 }
18684
18685 this.core.ParseForExtensionElements(node);
18686
18687 if (!this.core.EncounteredError)
18688 {
18689 Row row = this.core.CreateRow(sourceLineNumbers, "WixUpdateRegistration");
18690 row[0] = manufacturer;
18691 row[1] = department;
18692 row[2] = productFamily;
18693 row[3] = name;
18694 row[4] = classification;
18695 }
18696 }
18697
18698 /// <summary>
18699 /// Parse Payload element.
18700 /// </summary>
18701 /// <param name="node">Element to parse</param>
18702 /// <param name="parentType">ComplexReferenceParentType of parent element. (BA or PayloadGroup)</param>
18703 /// <param name="parentId">Identifier of parent element.</param>
18704 private string ParsePayloadElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId)
18705 {
18706 Debug.Assert(ComplexReferenceParentType.PayloadGroup == parentType || ComplexReferenceParentType.Package == parentType || ComplexReferenceParentType.Container == parentType);
18707 Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PayloadGroup == previousType || ComplexReferenceChildType.Payload == previousType);
18708
18709 string id = ParsePayloadElementContent(node, parentType, parentId, previousType, previousId, true);
18710 Dictionary<string, string> context = new Dictionary<string, string>();
18711 context["Id"] = id;
18712
18713 foreach (XElement child in node.Elements())
18714 {
18715 if (CompilerCore.WixNamespace == child.Name.Namespace)
18716 {
18717 switch (child.Name.LocalName)
18718 {
18719 default:
18720 this.core.UnexpectedElement(node, child);
18721 break;
18722 }
18723 }
18724 else
18725 {
18726 this.core.ParseExtensionElement(node, child, context);
18727 }
18728 }
18729
18730 return id;
18731 }
18732
18733 /// <summary>
18734 /// Parse the attributes of the Payload element.
18735 /// </summary>
18736 /// <param name="node">Element to parse</param>
18737 /// <param name="parentType">ComplexReferenceParentType of parent element.</param>
18738 /// <param name="parentId">Identifier of parent element.</param>
18739 private string ParsePayloadElementContent(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId, bool required)
18740 {
18741 Debug.Assert(ComplexReferenceParentType.PayloadGroup == parentType || ComplexReferenceParentType.Package == parentType || ComplexReferenceParentType.Container == parentType);
18742
18743 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
18744 YesNoDefaultType compressed = YesNoDefaultType.Default;
18745 YesNoType enableSignatureVerification = YesNoType.No;
18746 Identifier id = null;
18747 string name = null;
18748 string sourceFile = null;
18749 string downloadUrl = null;
18750 Wix.RemotePayload remotePayload = null;
18751
18752 // This list lets us evaluate extension attributes *after* all core attributes
18753 // have been parsed and dealt with, regardless of authoring order.
18754 List<XAttribute> extensionAttributes = new List<XAttribute>();
18755
18756 foreach (XAttribute attrib in node.Attributes())
18757 {
18758 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
18759 {
18760 switch (attrib.Name.LocalName)
18761 {
18762 case "Id":
18763 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
18764 break;
18765 case "Compressed":
18766 compressed = this.core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib);
18767 break;
18768 case "Name":
18769 name = this.core.GetAttributeLongFilename(sourceLineNumbers, attrib, false, true);
18770 break;
18771 case "SourceFile":
18772 sourceFile = this.core.GetAttributeValue(sourceLineNumbers, attrib);
18773 break;
18774 case "DownloadUrl":
18775 downloadUrl = this.core.GetAttributeValue(sourceLineNumbers, attrib);
18776 break;
18777 case "EnableSignatureVerification":
18778 enableSignatureVerification = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
18779 break;
18780 default:
18781 this.core.UnexpectedAttribute(node, attrib);
18782 break;
18783 }
18784 }
18785 else
18786 {
18787 extensionAttributes.Add(attrib);
18788 }
18789 }
18790
18791 if (!required && null == sourceFile)
18792 {
18793 // Nothing left to do!
18794 return null;
18795 }
18796
18797 if (null == id)
18798 {
18799 id = this.core.CreateIdentifier("pay", (null != sourceFile) ? sourceFile.ToUpperInvariant() : String.Empty);
18800 }
18801
18802 // Now that the PayloadId is known, we can parse the extension attributes.
18803 Dictionary<string, string> context = new Dictionary<string, string>();
18804 context["Id"] = id.Id;
18805
18806 foreach (XAttribute extensionAttribute in extensionAttributes)
18807 {
18808 this.core.ParseExtensionAttribute(node, extensionAttribute, context);
18809 }
18810
18811 // We only handle the elements we care about. Let caller handle other children.
18812 foreach (XElement child in node.Elements(CompilerCore.WixNamespace + "RemotePayload"))
18813 {
18814 SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
18815
18816 if (CompilerCore.WixNamespace == node.Name.Namespace && node.Name.LocalName != "ExePackage")
18817 {
18818 this.core.OnMessage(WixErrors.RemotePayloadUnsupported(childSourceLineNumbers));
18819 continue;
18820 }
18821
18822 if (null != remotePayload)
18823 {
18824 this.core.OnMessage(WixErrors.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, child.Name.LocalName));
18825 }
18826
18827 remotePayload = this.ParseRemotePayloadElement(child);
18828 }
18829
18830 if (null != sourceFile && null != remotePayload)
18831 {
18832 this.core.OnMessage(WixErrors.UnexpectedElementWithAttribute(sourceLineNumbers, node.Name.LocalName, "RemotePayload", "SourceFile"));
18833 }
18834 else if (null == sourceFile && null == remotePayload)
18835 {
18836 this.core.OnMessage(WixErrors.ExpectedAttributeOrElement(sourceLineNumbers, node.Name.LocalName, "SourceFile", "RemotePayload"));
18837 }
18838 else if (null == sourceFile)
18839 {
18840 sourceFile = String.Empty;
18841 }
18842
18843 if (null == downloadUrl && null != remotePayload)
18844 {
18845 this.core.OnMessage(WixErrors.ExpectedAttributeWithElement(sourceLineNumbers, node.Name.LocalName, "DownloadUrl", "RemotePayload"));
18846 }
18847
18848 if (Compiler.BurnUXContainerId == parentId)
18849 {
18850 if (compressed == YesNoDefaultType.No)
18851 {
18852 core.OnMessage(WixWarnings.UxPayloadsOnlySupportEmbedding(sourceLineNumbers, sourceFile));
18853 }
18854
18855 compressed = YesNoDefaultType.Yes;
18856 }
18857
18858 this.CreatePayloadRow(sourceLineNumbers, id, name, sourceFile, downloadUrl, parentType, parentId, previousType, previousId, compressed, enableSignatureVerification, null, null, remotePayload);
18859
18860 return id.Id;
18861 }
18862
18863 private Wix.RemotePayload ParseRemotePayloadElement(XElement node)
18864 {
18865 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
18866 Wix.RemotePayload remotePayload = new Wix.RemotePayload();
18867
18868 foreach (XAttribute attrib in node.Attributes())
18869 {
18870 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
18871 {
18872 switch (attrib.Name.LocalName)
18873 {
18874 case "CertificatePublicKey":
18875 remotePayload.CertificatePublicKey = this.core.GetAttributeValue(sourceLineNumbers, attrib);
18876 break;
18877 case "CertificateThumbprint":
18878 remotePayload.CertificateThumbprint = this.core.GetAttributeValue(sourceLineNumbers, attrib);
18879 break;
18880 case "Description":
18881 remotePayload.Description = this.core.GetAttributeValue(sourceLineNumbers, attrib);
18882 break;
18883 case "Hash":
18884 remotePayload.Hash = this.core.GetAttributeValue(sourceLineNumbers, attrib);
18885 break;
18886 case "ProductName":
18887 remotePayload.ProductName = this.core.GetAttributeValue(sourceLineNumbers, attrib);
18888 break;
18889 case "Size":
18890 remotePayload.Size = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, int.MaxValue);
18891 break;
18892 case "Version":
18893 remotePayload.Version = this.core.GetAttributeValue(sourceLineNumbers, attrib);
18894 break;
18895 default:
18896 this.core.UnexpectedAttribute(node, attrib);
18897 break;
18898 }
18899 }
18900 else
18901 {
18902 this.core.ParseExtensionAttribute(node, attrib);
18903 }
18904 }
18905
18906 if (String.IsNullOrEmpty(remotePayload.ProductName))
18907 {
18908 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ProductName"));
18909 }
18910
18911 if (String.IsNullOrEmpty(remotePayload.Description))
18912 {
18913 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Description"));
18914 }
18915
18916 if (String.IsNullOrEmpty(remotePayload.Hash))
18917 {
18918 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Hash"));
18919 }
18920
18921 if (0 == remotePayload.Size)
18922 {
18923 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Size"));
18924 }
18925
18926 if (String.IsNullOrEmpty(remotePayload.Version))
18927 {
18928 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version"));
18929 }
18930
18931 return remotePayload;
18932 }
18933
18934 /// <summary>
18935 /// Creates the row for a Payload.
18936 /// </summary>
18937 /// <param name="node">Element to parse</param>
18938 /// <param name="parentType">ComplexReferenceParentType of parent element</param>
18939 /// <param name="parentId">Identifier of parent element.</param>
18940 private WixBundlePayloadRow CreatePayloadRow(SourceLineNumber sourceLineNumbers, Identifier id, string name, string sourceFile, string downloadUrl, ComplexReferenceParentType parentType,
18941 string parentId, ComplexReferenceChildType previousType, string previousId, YesNoDefaultType compressed, YesNoType enableSignatureVerification, string displayName, string description,
18942 Wix.RemotePayload remotePayload)
18943 {
18944 WixBundlePayloadRow row = null;
18945
18946 if (!this.core.EncounteredError)
18947 {
18948 row = (WixBundlePayloadRow)this.core.CreateRow(sourceLineNumbers, "WixBundlePayload", id);
18949 row.Name = String.IsNullOrEmpty(name) ? Path.GetFileName(sourceFile) : name;
18950 row.SourceFile = sourceFile;
18951 row.DownloadUrl = downloadUrl;
18952 row.Compressed = compressed;
18953 row.UnresolvedSourceFile = sourceFile; // duplicate of sourceFile but in a string column so it won't get resolved to a full path during binding.
18954 row.DisplayName = displayName;
18955 row.Description = description;
18956 row.EnableSignatureValidation = (YesNoType.Yes == enableSignatureVerification);
18957
18958 if (null != remotePayload)
18959 {
18960 row.Description = remotePayload.Description;
18961 row.DisplayName = remotePayload.ProductName;
18962 row.Hash = remotePayload.Hash;
18963 row.PublicKey = remotePayload.CertificatePublicKey;
18964 row.Thumbprint = remotePayload.CertificateThumbprint;
18965 row.FileSize = remotePayload.Size;
18966 row.Version = remotePayload.Version;
18967 }
18968
18969 this.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.Payload, id.Id, previousType, previousId);
18970 }
18971
18972 return row;
18973 }
18974
18975 /// <summary>
18976 /// Parse PayloadGroup element.
18977 /// </summary>
18978 /// <param name="node">Element to parse</param>
18979 /// <param name="parentType">Optional ComplexReferenceParentType of parent element. (typically another PayloadGroup)</param>
18980 /// <param name="parentId">Identifier of parent element.</param>
18981 private void ParsePayloadGroupElement(XElement node, ComplexReferenceParentType parentType, string parentId)
18982 {
18983 Debug.Assert(ComplexReferenceParentType.Unknown == parentType || ComplexReferenceParentType.Layout == parentType || ComplexReferenceParentType.PayloadGroup == parentType);
18984
18985 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
18986 Identifier id = null;
18987
18988 foreach (XAttribute attrib in node.Attributes())
18989 {
18990 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
18991 {
18992 switch (attrib.Name.LocalName)
18993 {
18994 case "Id":
18995 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
18996 break;
18997 default:
18998 this.core.UnexpectedAttribute(node, attrib);
18999 break;
19000 }
19001 }
19002 else
19003 {
19004 this.core.ParseExtensionAttribute(node, attrib);
19005 }
19006 }
19007
19008 if (null == id)
19009 {
19010 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
19011 id = Identifier.Invalid;
19012 }
19013
19014 ComplexReferenceChildType previousType = ComplexReferenceChildType.Unknown;
19015 string previousId = null;
19016 foreach (XElement child in node.Elements())
19017 {
19018 if (CompilerCore.WixNamespace == child.Name.Namespace)
19019 {
19020 switch (child.Name.LocalName)
19021 {
19022 case "Payload":
19023 previousId = this.ParsePayloadElement(child, ComplexReferenceParentType.PayloadGroup, id.Id, previousType, previousId);
19024 previousType = ComplexReferenceChildType.Payload;
19025 break;
19026 case "PayloadGroupRef":
19027 previousId = this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.PayloadGroup, id.Id, previousType, previousId);
19028 previousType = ComplexReferenceChildType.PayloadGroup;
19029 break;
19030 default:
19031 this.core.UnexpectedElement(node, child);
19032 break;
19033 }
19034 }
19035 else
19036 {
19037 this.core.ParseExtensionElement(node, child);
19038 }
19039 }
19040
19041
19042 if (!this.core.EncounteredError)
19043 {
19044 this.core.CreateRow(sourceLineNumbers, "WixBundlePayloadGroup", id);
19045
19046 this.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.PayloadGroup, id.Id, ComplexReferenceChildType.Unknown, null);
19047 }
19048 }
19049
19050 /// <summary>
19051 /// Parses a payload group reference element.
19052 /// </summary>
19053 /// <param name="node">Element to parse.</param>
19054 /// <param name="parentType">ComplexReferenceParentType of parent element (BA or PayloadGroup).</param>
19055 /// <param name="parentId">Identifier of parent element.</param>
19056 private string ParsePayloadGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId)
19057 {
19058 Debug.Assert(ComplexReferenceParentType.Layout == parentType || ComplexReferenceParentType.PayloadGroup == parentType || ComplexReferenceParentType.Package == parentType || ComplexReferenceParentType.Container == parentType);
19059 Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PayloadGroup == previousType || ComplexReferenceChildType.Payload == previousType);
19060
19061 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
19062 string id = null;
19063
19064 foreach (XAttribute attrib in node.Attributes())
19065 {
19066 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
19067 {
19068 switch (attrib.Name.LocalName)
19069 {
19070 case "Id":
19071 id = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
19072 this.core.CreateSimpleReference(sourceLineNumbers, "WixBundlePayloadGroup", id);
19073 break;
19074 default:
19075 this.core.UnexpectedAttribute(node, attrib);
19076 break;
19077 }
19078 }
19079 else
19080 {
19081 this.core.ParseExtensionAttribute(node, attrib);
19082 }
19083 }
19084
19085 if (null == id)
19086 {
19087 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
19088 }
19089
19090 this.core.ParseForExtensionElements(node);
19091
19092 this.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.PayloadGroup, id, previousType, previousId);
19093
19094 return id;
19095 }
19096
19097 /// <summary>
19098 /// Creates group and ordering information.
19099 /// </summary>
19100 /// <param name="sourceLineNumbers">Source line numbers.</param>
19101 /// <param name="parentType">Type of parent group, if known.</param>
19102 /// <param name="parentId">Identifier of parent group, if known.</param>
19103 /// <param name="type">Type of this item.</param>
19104 /// <param name="id">Identifier for this item.</param>
19105 /// <param name="previousType">Type of previous item, if known.</param>
19106 /// <param name="previousId">Identifier of previous item, if known</param>
19107 private void CreateGroupAndOrderingRows(SourceLineNumber sourceLineNumbers,
19108 ComplexReferenceParentType parentType, string parentId,
19109 ComplexReferenceChildType type, string id,
19110 ComplexReferenceChildType previousType, string previousId)
19111 {
19112 if (ComplexReferenceParentType.Unknown != parentType && null != parentId)
19113 {
19114 this.core.CreateWixGroupRow(sourceLineNumbers, parentType, parentId, type, id);
19115 }
19116
19117 if (ComplexReferenceChildType.Unknown != previousType && null != previousId)
19118 {
19119 this.CreateWixOrderingRow(sourceLineNumbers, type, id, previousType, previousId);
19120 }
19121 }
19122
19123 /// <summary>
19124 /// Parse ExitCode element.
19125 /// </summary>
19126 /// <param name="node">Element to parse</param>
19127 /// <param name="packageId">Id of parent element</param>
19128 private void ParseExitCodeElement(XElement node, string packageId)
19129 {
19130 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
19131 int value = CompilerConstants.IntegerNotSet;
19132 ExitCodeBehaviorType behavior = ExitCodeBehaviorType.NotSet;
19133
19134 foreach (XAttribute attrib in node.Attributes())
19135 {
19136 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
19137 {
19138 switch (attrib.Name.LocalName)
19139 {
19140 case "Value":
19141 value = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, int.MinValue + 2, int.MaxValue);
19142 break;
19143 case "Behavior":
19144 string behaviorString = this.core.GetAttributeValue(sourceLineNumbers, attrib);
19145 if (!Enum.TryParse<ExitCodeBehaviorType>(behaviorString, true, out behavior))
19146 {
19147 this.core.OnMessage(WixErrors.IllegalAttributeValueWithLegalList(sourceLineNumbers, node.Name.LocalName, "Behavior", behaviorString, "success, error, scheduleReboot, forceReboot"));
19148 }
19149 break;
19150 default:
19151 this.core.UnexpectedAttribute(node, attrib);
19152 break;
19153 }
19154 }
19155 else
19156 {
19157 this.core.ParseExtensionAttribute(node, attrib);
19158 }
19159 }
19160
19161 if (ExitCodeBehaviorType.NotSet == behavior)
19162 {
19163 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Behavior"));
19164 }
19165
19166 this.core.ParseForExtensionElements(node);
19167
19168 if (!this.core.EncounteredError)
19169 {
19170 WixBundlePackageExitCodeRow row = (WixBundlePackageExitCodeRow)this.core.CreateRow(sourceLineNumbers, "WixBundlePackageExitCode");
19171 row.ChainPackageId = packageId;
19172 row.Code = value;
19173 row.Behavior = behavior;
19174 }
19175 }
19176
19177 /// <summary>
19178 /// Parse Chain element.
19179 /// </summary>
19180 /// <param name="node">Element to parse</param>
19181 private void ParseChainElement(XElement node)
19182 {
19183 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
19184 WixChainAttributes attributes = WixChainAttributes.None;
19185
19186 foreach (XAttribute attrib in node.Attributes())
19187 {
19188 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
19189 {
19190 switch (attrib.Name.LocalName)
19191 {
19192 case "DisableRollback":
19193 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
19194 {
19195 attributes |= WixChainAttributes.DisableRollback;
19196 }
19197 break;
19198 case "DisableSystemRestore":
19199 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
19200 {
19201 attributes |= WixChainAttributes.DisableSystemRestore;
19202 }
19203 break;
19204 case "ParallelCache":
19205 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
19206 {
19207 attributes |= WixChainAttributes.ParallelCache;
19208 }
19209 break;
19210 default:
19211 this.core.UnexpectedAttribute(node, attrib);
19212 break;
19213 }
19214 }
19215 else
19216 {
19217 this.core.ParseExtensionAttribute(node, attrib);
19218 }
19219 }
19220
19221 // Ensure there is always a rollback boundary at the beginning of the chain.
19222 this.CreateRollbackBoundary(sourceLineNumbers, new Identifier("WixDefaultBoundary", AccessModifier.Public), YesNoType.Yes, YesNoType.No, ComplexReferenceParentType.PackageGroup, "WixChain", ComplexReferenceChildType.Unknown, null);
19223
19224 string previousId = "WixDefaultBoundary";
19225 ComplexReferenceChildType previousType = ComplexReferenceChildType.Package;
19226
19227 foreach (XElement child in node.Elements())
19228 {
19229 if (CompilerCore.WixNamespace == child.Name.Namespace)
19230 {
19231 switch (child.Name.LocalName)
19232 {
19233 case "MsiPackage":
19234 previousId = this.ParseMsiPackageElement(child, ComplexReferenceParentType.PackageGroup, "WixChain", previousType, previousId);
19235 previousType = ComplexReferenceChildType.Package;
19236 break;
19237 case "MspPackage":
19238 previousId = this.ParseMspPackageElement(child, ComplexReferenceParentType.PackageGroup, "WixChain", previousType, previousId);
19239 previousType = ComplexReferenceChildType.Package;
19240 break;
19241 case "MsuPackage":
19242 previousId = this.ParseMsuPackageElement(child, ComplexReferenceParentType.PackageGroup, "WixChain", previousType, previousId);
19243 previousType = ComplexReferenceChildType.Package;
19244 break;
19245 case "ExePackage":
19246 previousId = this.ParseExePackageElement(child, ComplexReferenceParentType.PackageGroup, "WixChain", previousType, previousId);
19247 previousType = ComplexReferenceChildType.Package;
19248 break;
19249 case "RollbackBoundary":
19250 previousId = this.ParseRollbackBoundaryElement(child, ComplexReferenceParentType.PackageGroup, "WixChain", previousType, previousId);
19251 previousType = ComplexReferenceChildType.Package;
19252 break;
19253 case "PackageGroupRef":
19254 previousId = this.ParsePackageGroupRefElement(child, ComplexReferenceParentType.PackageGroup, "WixChain", previousType, previousId);
19255 previousType = ComplexReferenceChildType.PackageGroup;
19256 break;
19257 default:
19258 this.core.UnexpectedElement(node, child);
19259 break;
19260 }
19261 }
19262 else
19263 {
19264 this.core.ParseExtensionElement(node, child);
19265 }
19266 }
19267
19268
19269 if (null == previousId)
19270 {
19271 this.core.OnMessage(WixErrors.ExpectedElement(sourceLineNumbers, node.Name.LocalName, "MsiPackage", "ExePackage", "PackageGroupRef"));
19272 }
19273
19274 if (!this.core.EncounteredError)
19275 {
19276 WixChainRow row = (WixChainRow)this.core.CreateRow(sourceLineNumbers, "WixChain");
19277 row.Attributes = attributes;
19278 }
19279 }
19280
19281 /// <summary>
19282 /// Parse MsiPackage element
19283 /// </summary>
19284 /// <param name="node">Element to parse</param>
19285 /// <param name="parentType">Type of parent group, if known.</param>
19286 /// <param name="parentId">Identifier of parent group, if known.</param>
19287 /// <param name="previousType">Type of previous item, if known.</param>
19288 /// <param name="previousId">Identifier of previous item, if known</param>
19289 /// <returns>Identifier for package element.</returns>
19290 private string ParseMsiPackageElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId)
19291 {
19292 return ParseChainPackage(node, WixBundlePackageType.Msi, parentType, parentId, previousType, previousId);
19293 }
19294
19295 /// <summary>
19296 /// Parse MspPackage element
19297 /// </summary>
19298 /// <param name="node">Element to parse</param>
19299 /// <param name="parentType">Type of parent group, if known.</param>
19300 /// <param name="parentId">Identifier of parent group, if known.</param>
19301 /// <param name="previousType">Type of previous item, if known.</param>
19302 /// <param name="previousId">Identifier of previous item, if known</param>
19303 /// <returns>Identifier for package element.</returns>
19304 private string ParseMspPackageElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId)
19305 {
19306 return ParseChainPackage(node, WixBundlePackageType.Msp, parentType, parentId, previousType, previousId);
19307 }
19308
19309 /// <summary>
19310 /// Parse MsuPackage element
19311 /// </summary>
19312 /// <param name="node">Element to parse</param>
19313 /// <param name="parentType">Type of parent group, if known.</param>
19314 /// <param name="parentId">Identifier of parent group, if known.</param>
19315 /// <param name="previousType">Type of previous item, if known.</param>
19316 /// <param name="previousId">Identifier of previous item, if known</param>
19317 /// <returns>Identifier for package element.</returns>
19318 private string ParseMsuPackageElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId)
19319 {
19320 return ParseChainPackage(node, WixBundlePackageType.Msu, parentType, parentId, previousType, previousId);
19321 }
19322
19323 /// <summary>
19324 /// Parse ExePackage element
19325 /// </summary>
19326 /// <param name="node">Element to parse</param>
19327 /// <param name="parentType">Type of parent group, if known.</param>
19328 /// <param name="parentId">Identifier of parent group, if known.</param>
19329 /// <param name="previousType">Type of previous item, if known.</param>
19330 /// <param name="previousId">Identifier of previous item, if known</param>
19331 /// <returns>Identifier for package element.</returns>
19332 private string ParseExePackageElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId)
19333 {
19334 return ParseChainPackage(node, WixBundlePackageType.Exe, parentType, parentId, previousType, previousId);
19335 }
19336
19337 /// <summary>
19338 /// Parse RollbackBoundary element
19339 /// </summary>
19340 /// <param name="node">Element to parse</param>
19341 /// <param name="parentType">Type of parent group, if known.</param>
19342 /// <param name="parentId">Identifier of parent group, if known.</param>
19343 /// <param name="previousType">Type of previous item, if known.</param>
19344 /// <param name="previousId">Identifier of previous item, if known</param>
19345 /// <returns>Identifier for package element.</returns>
19346 private string ParseRollbackBoundaryElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId)
19347 {
19348 Debug.Assert(ComplexReferenceParentType.PackageGroup == parentType);
19349 Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PackageGroup == previousType || ComplexReferenceChildType.Package == previousType);
19350
19351 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
19352 Identifier id = null;
19353 YesNoType vital = YesNoType.Yes;
19354 YesNoType transaction = YesNoType.No;
19355
19356 // This list lets us evaluate extension attributes *after* all core attributes
19357 // have been parsed and dealt with, regardless of authoring order.
19358 List<XAttribute> extensionAttributes = new List<XAttribute>();
19359
19360 foreach (XAttribute attrib in node.Attributes())
19361 {
19362 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
19363 {
19364 bool allowed = true;
19365 switch (attrib.Name.LocalName)
19366 {
19367 case "Id":
19368 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
19369 break;
19370 case "Vital":
19371 vital = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
19372 break;
19373 case "Transaction":
19374 transaction = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
19375 break;
19376 default:
19377 allowed = false;
19378 break;
19379 }
19380
19381 if (!allowed)
19382 {
19383 this.core.UnexpectedAttribute(node, attrib);
19384 }
19385 }
19386 else
19387 {
19388 // Save the extension attributes for later...
19389 extensionAttributes.Add(attrib);
19390 }
19391 }
19392
19393 if (null == id)
19394 {
19395 if (!String.IsNullOrEmpty(previousId))
19396 {
19397 id = this.core.CreateIdentifier("rba", previousId);
19398 }
19399
19400 if (null == id)
19401 {
19402 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
19403 id = Identifier.Invalid;
19404 }
19405 else if (!Common.IsIdentifier(id.Id))
19406 {
19407 this.core.OnMessage(WixErrors.IllegalIdentifier(sourceLineNumbers, node.Name.LocalName, "Id", id.Id));
19408 }
19409 }
19410
19411 // Now that the rollback identifier is known, we can parse the extension attributes...
19412 Dictionary<string, string> contextValues = new Dictionary<string, string>();
19413 contextValues["RollbackBoundaryId"] = id.Id;
19414 foreach (XAttribute attribute in extensionAttributes)
19415 {
19416 this.core.ParseExtensionAttribute(node, attribute, contextValues);
19417 }
19418
19419 this.core.ParseForExtensionElements(node);
19420
19421 if (!this.core.EncounteredError)
19422 {
19423 this.CreateRollbackBoundary(sourceLineNumbers, id, vital, transaction, parentType, parentId, previousType, previousId);
19424 }
19425
19426 return id.Id;
19427 }
19428
19429 /// <summary>
19430 /// Parses one of the ChainPackage elements
19431 /// </summary>
19432 /// <param name="node">Element to parse</param>
19433 /// <param name="packageType">Type of package to parse</param>
19434 /// <param name="parentType">Type of parent group, if known.</param>
19435 /// <param name="parentId">Identifier of parent group, if known.</param>
19436 /// <param name="previousType">Type of previous item, if known.</param>
19437 /// <param name="previousId">Identifier of previous item, if known</param>
19438 /// <returns>Identifier for package element.</returns>
19439 /// <remarks>This method contains the shared logic for parsing all of the ChainPackage
19440 /// types, as there is more in common between them than different.</remarks>
19441 private string ParseChainPackage(XElement node, WixBundlePackageType packageType, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId)
19442 {
19443 Debug.Assert(ComplexReferenceParentType.PackageGroup == parentType);
19444 Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PackageGroup == previousType || ComplexReferenceChildType.Package == previousType);
19445
19446 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
19447 Identifier id = null;
19448 string name = null;
19449 string sourceFile = null;
19450 string downloadUrl = null;
19451 string after = null;
19452 string installCondition = null;
19453 YesNoAlwaysType cache = YesNoAlwaysType.Yes; // the default is to cache everything in tradeoff for stability over disk space.
19454 string cacheId = null;
19455 string description = null;
19456 string displayName = null;
19457 string logPathVariable = (packageType == WixBundlePackageType.Msu) ? String.Empty : null;
19458 string rollbackPathVariable = (packageType == WixBundlePackageType.Msu) ? String.Empty : null;
19459 YesNoType permanent = YesNoType.NotSet;
19460 YesNoType visible = YesNoType.NotSet;
19461 YesNoType vital = YesNoType.Yes;
19462 string installCommand = null;
19463 string repairCommand = null;
19464 YesNoType repairable = YesNoType.NotSet;
19465 string uninstallCommand = null;
19466 YesNoDefaultType perMachine = YesNoDefaultType.NotSet;
19467 string detectCondition = null;
19468 string protocol = null;
19469 int installSize = CompilerConstants.IntegerNotSet;
19470 string msuKB = null;
19471 YesNoType suppressLooseFilePayloadGeneration = YesNoType.NotSet;
19472 YesNoType enableSignatureVerification = YesNoType.No;
19473 YesNoDefaultType compressed = YesNoDefaultType.Default;
19474 YesNoType displayInternalUI = YesNoType.NotSet;
19475 YesNoType enableFeatureSelection = YesNoType.NotSet;
19476 YesNoType forcePerMachine = YesNoType.NotSet;
19477 Wix.RemotePayload remotePayload = null;
19478 YesNoType slipstream = YesNoType.NotSet;
19479
19480 string[] expectedNetFx4Args = new string[] { "/q", "/norestart", "/chainingpackage" };
19481
19482 // This list lets us evaluate extension attributes *after* all core attributes
19483 // have been parsed and dealt with, regardless of authoring order.
19484 List<XAttribute> extensionAttributes = new List<XAttribute>();
19485
19486 foreach (XAttribute attrib in node.Attributes())
19487 {
19488 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
19489 {
19490 bool allowed = true;
19491 switch (attrib.Name.LocalName)
19492 {
19493 case "Id":
19494 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
19495 break;
19496 case "Name":
19497 name = this.core.GetAttributeLongFilename(sourceLineNumbers, attrib, false, true);
19498 if (!this.core.IsValidLongFilename(name, false, true))
19499 {
19500 this.core.OnMessage(WixErrors.IllegalLongFilename(sourceLineNumbers, node.Name.LocalName, "Name", name));
19501 }
19502 break;
19503 case "SourceFile":
19504 sourceFile = this.core.GetAttributeValue(sourceLineNumbers, attrib);
19505 break;
19506 case "DownloadUrl":
19507 downloadUrl = this.core.GetAttributeValue(sourceLineNumbers, attrib);
19508 break;
19509 case "After":
19510 after = this.core.GetAttributeValue(sourceLineNumbers, attrib);
19511 break;
19512 case "InstallCondition":
19513 installCondition = this.core.GetAttributeValue(sourceLineNumbers, attrib);
19514 break;
19515 case "Cache":
19516 cache = this.core.GetAttributeYesNoAlwaysValue(sourceLineNumbers, attrib);
19517 break;
19518 case "CacheId":
19519 cacheId = this.core.GetAttributeValue(sourceLineNumbers, attrib);
19520 break;
19521 case "Description":
19522 description = this.core.GetAttributeValue(sourceLineNumbers, attrib);
19523 break;
19524 case "DisplayName":
19525 displayName = this.core.GetAttributeValue(sourceLineNumbers, attrib);
19526 break;
19527 case "DisplayInternalUI":
19528 displayInternalUI = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
19529 allowed = (packageType == WixBundlePackageType.Msi || packageType == WixBundlePackageType.Msp);
19530 break;
19531 case "EnableFeatureSelection":
19532 enableFeatureSelection = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
19533 allowed = (packageType == WixBundlePackageType.Msi);
19534 break;
19535 case "ForcePerMachine":
19536 forcePerMachine = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
19537 allowed = (packageType == WixBundlePackageType.Msi);
19538 break;
19539 case "LogPathVariable":
19540 logPathVariable = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty);
19541 break;
19542 case "RollbackLogPathVariable":
19543 rollbackPathVariable = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty);
19544 break;
19545 case "Permanent":
19546 permanent = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
19547 break;
19548 case "Visible":
19549 visible = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
19550 allowed = (packageType == WixBundlePackageType.Msi);
19551 break;
19552 case "Vital":
19553 vital = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
19554 break;
19555 case "InstallCommand":
19556 installCommand = this.core.GetAttributeValue(sourceLineNumbers, attrib);
19557 allowed = (packageType == WixBundlePackageType.Exe);
19558 break;
19559 case "RepairCommand":
19560 repairCommand = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty);
19561 repairable = YesNoType.Yes;
19562 allowed = (packageType == WixBundlePackageType.Exe);
19563 break;
19564 case "UninstallCommand":
19565 uninstallCommand = this.core.GetAttributeValue(sourceLineNumbers, attrib);
19566 allowed = (packageType == WixBundlePackageType.Exe);
19567 break;
19568 case "PerMachine":
19569 perMachine = this.core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib);
19570 allowed = (packageType == WixBundlePackageType.Exe || packageType == WixBundlePackageType.Msp);
19571 break;
19572 case "DetectCondition":
19573 detectCondition = this.core.GetAttributeValue(sourceLineNumbers, attrib);
19574 allowed = (packageType == WixBundlePackageType.Exe || packageType == WixBundlePackageType.Msu);
19575 break;
19576 case "Protocol":
19577 protocol = this.core.GetAttributeValue(sourceLineNumbers, attrib);
19578 allowed = (packageType == WixBundlePackageType.Exe);
19579 break;
19580 case "InstallSize":
19581 installSize = this.core.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, int.MaxValue);
19582 break;
19583 case "KB":
19584 msuKB = this.core.GetAttributeValue(sourceLineNumbers, attrib);
19585 allowed = (packageType == WixBundlePackageType.Msu);
19586 break;
19587 case "Compressed":
19588 compressed = this.core.GetAttributeYesNoDefaultValue(sourceLineNumbers, attrib);
19589 break;
19590 case "SuppressLooseFilePayloadGeneration":
19591 this.core.OnMessage(WixWarnings.DeprecatedAttribute(sourceLineNumbers, node.Name.LocalName, attrib.Name.LocalName));
19592 suppressLooseFilePayloadGeneration = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
19593 allowed = (packageType == WixBundlePackageType.Msi);
19594 break;
19595 case "EnableSignatureVerification":
19596 enableSignatureVerification = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
19597 break;
19598 case "Slipstream":
19599 slipstream = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
19600 allowed = (packageType == WixBundlePackageType.Msp);
19601 break;
19602 default:
19603 allowed = false;
19604 break;
19605 }
19606
19607 if (!allowed)
19608 {
19609 this.core.UnexpectedAttribute(node, attrib);
19610 }
19611 }
19612 else
19613 {
19614 // Save the extension attributes for later...
19615 extensionAttributes.Add(attrib);
19616 }
19617 }
19618
19619 // We need to handle RemotePayload up front because it effects value of sourceFile which is used in Id generation. Id is needed by other child elements.
19620 foreach (XElement child in node.Elements(CompilerCore.WixNamespace + "RemotePayload"))
19621 {
19622 SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
19623
19624 if (CompilerCore.WixNamespace == node.Name.Namespace && node.Name.LocalName != "ExePackage" && node.Name.LocalName != "MsuPackage")
19625 {
19626 this.core.OnMessage(WixErrors.RemotePayloadUnsupported(childSourceLineNumbers));
19627 continue;
19628 }
19629
19630 if (null != remotePayload)
19631 {
19632 this.core.OnMessage(WixErrors.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, child.Name.LocalName));
19633 }
19634
19635 remotePayload = this.ParseRemotePayloadElement(child);
19636 }
19637
19638 if (String.IsNullOrEmpty(sourceFile))
19639 {
19640 if (String.IsNullOrEmpty(name))
19641 {
19642 this.core.OnMessage(WixErrors.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.LocalName, "Name", "SourceFile"));
19643 }
19644 else if (null == remotePayload)
19645 {
19646 sourceFile = Path.Combine("SourceDir", name);
19647 }
19648 else
19649 {
19650 sourceFile = String.Empty; // SourceFile is required it cannot be null.
19651 }
19652 }
19653 else if (null != remotePayload)
19654 {
19655 this.core.OnMessage(WixErrors.UnexpectedElementWithAttribute(sourceLineNumbers, node.Name.LocalName, "RemotePayload", "SourceFile"));
19656 }
19657 else if (sourceFile.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal))
19658 {
19659 if (String.IsNullOrEmpty(name))
19660 {
19661 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name", "SourceFile", sourceFile));
19662 }
19663 else
19664 {
19665 sourceFile = Path.Combine(sourceFile, Path.GetFileName(name));
19666 }
19667 }
19668
19669 if (null == downloadUrl && null != remotePayload)
19670 {
19671 this.core.OnMessage(WixErrors.ExpectedAttributeWithElement(sourceLineNumbers, node.Name.LocalName, "DownloadUrl", "RemotePayload"));
19672 }
19673
19674 if (YesNoDefaultType.No != compressed && null != remotePayload)
19675 {
19676 compressed = YesNoDefaultType.No;
19677 this.core.OnMessage(WixWarnings.RemotePayloadsMustNotAlsoBeCompressed(sourceLineNumbers, node.Name.LocalName));
19678 }
19679
19680 if (null == id)
19681 {
19682 if (!String.IsNullOrEmpty(name))
19683 {
19684 id = this.core.CreateIdentifierFromFilename(Path.GetFileName(name));
19685 }
19686 else if (!String.IsNullOrEmpty(sourceFile))
19687 {
19688 id = this.core.CreateIdentifierFromFilename(Path.GetFileName(sourceFile));
19689 }
19690
19691 if (null == id)
19692 {
19693 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
19694 id = Identifier.Invalid;
19695 }
19696 else if (!Common.IsIdentifier(id.Id))
19697 {
19698 this.core.OnMessage(WixErrors.IllegalIdentifier(sourceLineNumbers, node.Name.LocalName, "Id", id.Id));
19699 }
19700 }
19701
19702 if (null == logPathVariable)
19703 {
19704 logPathVariable = String.Concat("WixBundleLog_", id.Id);
19705 }
19706
19707 if (null == rollbackPathVariable)
19708 {
19709 rollbackPathVariable = String.Concat("WixBundleRollbackLog_", id.Id);
19710 }
19711
19712 if (!String.IsNullOrEmpty(protocol) && !protocol.Equals("burn", StringComparison.Ordinal) && !protocol.Equals("netfx4", StringComparison.Ordinal) && !protocol.Equals("none", StringComparison.Ordinal))
19713 {
19714 this.core.OnMessage(WixErrors.IllegalAttributeValueWithLegalList(sourceLineNumbers, node.Name.LocalName, "Protocol", protocol, "none, burn, netfx4"));
19715 }
19716
19717 if (!String.IsNullOrEmpty(protocol) && protocol.Equals("netfx4", StringComparison.Ordinal))
19718 {
19719 foreach (string expectedArgument in expectedNetFx4Args)
19720 {
19721 if (null == installCommand || -1 == installCommand.IndexOf(expectedArgument, StringComparison.OrdinalIgnoreCase))
19722 {
19723 this.core.OnMessage(WixWarnings.AttributeShouldContain(sourceLineNumbers, node.Name.LocalName, "InstallCommand", installCommand, expectedArgument, "Protocol", "netfx4"));
19724 }
19725
19726 if (null == repairCommand || -1 == repairCommand.IndexOf(expectedArgument, StringComparison.OrdinalIgnoreCase))
19727 {
19728 this.core.OnMessage(WixWarnings.AttributeShouldContain(sourceLineNumbers, node.Name.LocalName, "RepairCommand", repairCommand, expectedArgument, "Protocol", "netfx4"));
19729 }
19730
19731 if (null == uninstallCommand || -1 == uninstallCommand.IndexOf(expectedArgument, StringComparison.OrdinalIgnoreCase))
19732 {
19733 this.core.OnMessage(WixWarnings.AttributeShouldContain(sourceLineNumbers, node.Name.LocalName, "UninstallCommand", uninstallCommand, expectedArgument, "Protocol", "netfx4"));
19734 }
19735 }
19736 }
19737
19738 // Only set default scope for EXEs and MSPs if not already set.
19739 if ((WixBundlePackageType.Exe == packageType || WixBundlePackageType.Msp == packageType) && YesNoDefaultType.NotSet == perMachine)
19740 {
19741 perMachine = YesNoDefaultType.Default;
19742 }
19743
19744 // Now that the package ID is known, we can parse the extension attributes...
19745 Dictionary<string, string> contextValues = new Dictionary<string, string>() { { "PackageId", id.Id } };
19746 foreach (XAttribute attribute in extensionAttributes)
19747 {
19748 this.core.ParseExtensionAttribute(node, attribute, contextValues);
19749 }
19750
19751 foreach (XElement child in node.Elements())
19752 {
19753 if (CompilerCore.WixNamespace == child.Name.Namespace)
19754 {
19755 bool allowed = true;
19756 switch (child.Name.LocalName)
19757 {
19758 case "SlipstreamMsp":
19759 allowed = (packageType == WixBundlePackageType.Msi);
19760 if (allowed)
19761 {
19762 this.ParseSlipstreamMspElement(child, id.Id);
19763 }
19764 break;
19765 case "MsiProperty":
19766 allowed = (packageType == WixBundlePackageType.Msi || packageType == WixBundlePackageType.Msp);
19767 if (allowed)
19768 {
19769 this.ParseMsiPropertyElement(child, id.Id);
19770 }
19771 break;
19772 case "Payload":
19773 this.ParsePayloadElement(child, ComplexReferenceParentType.Package, id.Id, ComplexReferenceChildType.Unknown, null);
19774 break;
19775 case "PayloadGroupRef":
19776 this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Package, id.Id, ComplexReferenceChildType.Unknown, null);
19777 break;
19778 case "ExitCode":
19779 allowed = (packageType == WixBundlePackageType.Exe);
19780 if (allowed)
19781 {
19782 this.ParseExitCodeElement(child, id.Id);
19783 }
19784 break;
19785 case "CommandLine":
19786 allowed = (packageType == WixBundlePackageType.Exe);
19787 if (allowed)
19788 {
19789 this.ParseCommandLineElement(child, id.Id);
19790 }
19791 break;
19792 case "RemotePayload":
19793 // Handled previously
19794 break;
19795 default:
19796 allowed = false;
19797 break;
19798 }
19799
19800 if (!allowed)
19801 {
19802 this.core.UnexpectedElement(node, child);
19803 }
19804 }
19805 else
19806 {
19807 Dictionary<string, string> context = new Dictionary<string, string>() { { "Id", id.Id } };
19808 this.core.ParseExtensionElement(node, child, context);
19809 }
19810 }
19811
19812 if (!this.core.EncounteredError)
19813 {
19814 // We create the package contents as a payload with this package as the parent
19815 this.CreatePayloadRow(sourceLineNumbers, id, name, sourceFile, downloadUrl, ComplexReferenceParentType.Package, id.Id,
19816 ComplexReferenceChildType.Unknown, null, compressed, enableSignatureVerification, displayName, description, remotePayload);
19817
19818 WixChainItemRow chainItemRow = (WixChainItemRow)this.core.CreateRow(sourceLineNumbers, "WixChainItem", id);
19819
19820 WixBundlePackageAttributes attributes = 0;
19821 attributes |= (YesNoType.Yes == permanent) ? WixBundlePackageAttributes.Permanent : 0;
19822 attributes |= (YesNoType.Yes == visible) ? WixBundlePackageAttributes.Visible : 0;
19823
19824 WixBundlePackageRow chainPackageRow = (WixBundlePackageRow)this.core.CreateRow(sourceLineNumbers, "WixBundlePackage", id);
19825 chainPackageRow.Type = packageType;
19826 chainPackageRow.PackagePayload = id.Id;
19827 chainPackageRow.Attributes = attributes;
19828
19829 chainPackageRow.InstallCondition = installCondition;
19830
19831 if (YesNoAlwaysType.NotSet != cache)
19832 {
19833 chainPackageRow.Cache = cache;
19834 }
19835
19836 chainPackageRow.CacheId = cacheId;
19837
19838 if (YesNoType.NotSet != vital)
19839 {
19840 chainPackageRow.Vital = vital;
19841 }
19842
19843 if (YesNoDefaultType.NotSet != perMachine)
19844 {
19845 chainPackageRow.PerMachine = perMachine;
19846 }
19847
19848 chainPackageRow.LogPathVariable = logPathVariable;
19849 chainPackageRow.RollbackLogPathVariable = rollbackPathVariable;
19850
19851 if (CompilerConstants.IntegerNotSet != installSize)
19852 {
19853 chainPackageRow.InstallSize = installSize;
19854 }
19855
19856 switch (packageType)
19857 {
19858 case WixBundlePackageType.Exe:
19859 WixBundleExePackageAttributes exeAttributes = 0;
19860 exeAttributes |= (YesNoType.Yes == repairable) ? WixBundleExePackageAttributes.Repairable : 0;
19861
19862 WixBundleExePackageRow exeRow = (WixBundleExePackageRow)this.core.CreateRow(sourceLineNumbers, "WixBundleExePackage", id);
19863 exeRow.Attributes = exeAttributes;
19864 exeRow.DetectCondition = detectCondition;
19865 exeRow.InstallCommand = installCommand;
19866 exeRow.RepairCommand = repairCommand;
19867 exeRow.UninstallCommand = uninstallCommand;
19868 exeRow.ExeProtocol = protocol;
19869 break;
19870
19871 case WixBundlePackageType.Msi:
19872 WixBundleMsiPackageAttributes msiAttributes = 0;
19873 msiAttributes |= (YesNoType.Yes == displayInternalUI) ? WixBundleMsiPackageAttributes.DisplayInternalUI : 0;
19874 msiAttributes |= (YesNoType.Yes == enableFeatureSelection) ? WixBundleMsiPackageAttributes.EnableFeatureSelection : 0;
19875 msiAttributes |= (YesNoType.Yes == forcePerMachine) ? WixBundleMsiPackageAttributes.ForcePerMachine : 0;
19876 msiAttributes |= (YesNoType.Yes == suppressLooseFilePayloadGeneration) ? WixBundleMsiPackageAttributes.SuppressLooseFilePayloadGeneration : 0;
19877
19878 WixBundleMsiPackageRow msiRow = (WixBundleMsiPackageRow)this.core.CreateRow(sourceLineNumbers, "WixBundleMsiPackage", id);
19879 msiRow.Attributes = msiAttributes;
19880 break;
19881
19882 case WixBundlePackageType.Msp:
19883 WixBundleMspPackageAttributes mspAttributes = 0;
19884 mspAttributes |= (YesNoType.Yes == displayInternalUI) ? WixBundleMspPackageAttributes.DisplayInternalUI : 0;
19885 mspAttributes |= (YesNoType.Yes == slipstream) ? WixBundleMspPackageAttributes.Slipstream : 0;
19886
19887 WixBundleMspPackageRow mspRow = (WixBundleMspPackageRow)this.core.CreateRow(sourceLineNumbers, "WixBundleMspPackage", id);
19888 mspRow.Attributes = mspAttributes;
19889 break;
19890
19891 case WixBundlePackageType.Msu:
19892 WixBundleMsuPackageRow msuRow = (WixBundleMsuPackageRow)this.core.CreateRow(sourceLineNumbers, "WixBundleMsuPackage", id);
19893 msuRow.DetectCondition = detectCondition;
19894 msuRow.MsuKB = msuKB;
19895 break;
19896 }
19897
19898 this.CreateChainPackageMetaRows(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.Package, id.Id, previousType, previousId, after);
19899 }
19900
19901 return id.Id;
19902 }
19903
19904 /// <summary>
19905 /// Parse CommandLine element.
19906 /// </summary>
19907 /// <param name="node">Element to parse</param>
19908 private void ParseCommandLineElement(XElement node, string packageId)
19909 {
19910 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
19911 string installArgument = null;
19912 string uninstallArgument = null;
19913 string repairArgument = null;
19914 string condition = null;
19915
19916 foreach (XAttribute attrib in node.Attributes())
19917 {
19918 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
19919 {
19920 switch (attrib.Name.LocalName)
19921 {
19922 case "InstallArgument":
19923 installArgument = this.core.GetAttributeValue(sourceLineNumbers, attrib);
19924 break;
19925 case "UninstallArgument":
19926 uninstallArgument = this.core.GetAttributeValue(sourceLineNumbers, attrib);
19927 break;
19928 case "RepairArgument":
19929 repairArgument = this.core.GetAttributeValue(sourceLineNumbers, attrib);
19930 break;
19931 case "Condition":
19932 condition = this.core.GetAttributeValue(sourceLineNumbers, attrib);
19933 break;
19934 default:
19935 this.core.UnexpectedAttribute(node, attrib);
19936 break;
19937 }
19938 }
19939 else
19940 {
19941 this.core.ParseExtensionAttribute(node, attrib);
19942 }
19943 }
19944
19945 if (String.IsNullOrEmpty(condition))
19946 {
19947 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Condition"));
19948 }
19949
19950 this.core.ParseForExtensionElements(node);
19951
19952 if (!this.core.EncounteredError)
19953 {
19954 WixBundlePackageCommandLineRow row = (WixBundlePackageCommandLineRow)this.core.CreateRow(sourceLineNumbers, "WixBundlePackageCommandLine");
19955 row.ChainPackageId = packageId;
19956 row.InstallArgument = installArgument;
19957 row.UninstallArgument = uninstallArgument;
19958 row.RepairArgument = repairArgument;
19959 row.Condition = condition;
19960 }
19961 }
19962
19963 /// <summary>
19964 /// Parse PackageGroup element.
19965 /// </summary>
19966 /// <param name="node">Element to parse</param>
19967 private void ParsePackageGroupElement(XElement node)
19968 {
19969 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
19970 Identifier id = null;
19971
19972 foreach (XAttribute attrib in node.Attributes())
19973 {
19974 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
19975 {
19976 switch (attrib.Name.LocalName)
19977 {
19978 case "Id":
19979 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
19980 break;
19981 default:
19982 this.core.UnexpectedAttribute(node, attrib);
19983 break;
19984 }
19985 }
19986 else
19987 {
19988 this.core.ParseExtensionAttribute(node, attrib);
19989 }
19990 }
19991
19992 if (null == id)
19993 {
19994 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
19995 id = Identifier.Invalid;
19996 }
19997
19998 ComplexReferenceChildType previousType = ComplexReferenceChildType.Unknown;
19999 string previousId = null;
20000 foreach (XElement child in node.Elements())
20001 {
20002 if (CompilerCore.WixNamespace == child.Name.Namespace)
20003 {
20004 switch (child.Name.LocalName)
20005 {
20006 case "MsiPackage":
20007 previousId = this.ParseMsiPackageElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId);
20008 previousType = ComplexReferenceChildType.Package;
20009 break;
20010 case "MspPackage":
20011 previousId = this.ParseMspPackageElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId);
20012 previousType = ComplexReferenceChildType.Package;
20013 break;
20014 case "MsuPackage":
20015 previousId = this.ParseMsuPackageElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId);
20016 previousType = ComplexReferenceChildType.Package;
20017 break;
20018 case "ExePackage":
20019 previousId = this.ParseExePackageElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId);
20020 previousType = ComplexReferenceChildType.Package;
20021 break;
20022 case "RollbackBoundary":
20023 previousId = this.ParseRollbackBoundaryElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId);
20024 previousType = ComplexReferenceChildType.Package;
20025 break;
20026 case "PackageGroupRef":
20027 previousId = this.ParsePackageGroupRefElement(child, ComplexReferenceParentType.PackageGroup, id.Id, previousType, previousId);
20028 previousType = ComplexReferenceChildType.PackageGroup;
20029 break;
20030 default:
20031 this.core.UnexpectedElement(node, child);
20032 break;
20033 }
20034 }
20035 else
20036 {
20037 this.core.ParseExtensionElement(node, child);
20038 }
20039 }
20040
20041
20042 if (!this.core.EncounteredError)
20043 {
20044 this.core.CreateRow(sourceLineNumbers, "WixBundlePackageGroup", id);
20045 }
20046 }
20047
20048 /// <summary>
20049 /// Parses a package group reference element.
20050 /// </summary>
20051 /// <param name="node">Element to parse.</param>
20052 /// <param name="parentType">ComplexReferenceParentType of parent element (Unknown or PackageGroup).</param>
20053 /// <param name="parentId">Identifier of parent element.</param>
20054 /// <returns>Identifier for package group element.</rereturns>
20055 private string ParsePackageGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId)
20056 {
20057 return this.ParsePackageGroupRefElement(node, parentType, parentId, ComplexReferenceChildType.Unknown, null);
20058 }
20059
20060 /// <summary>
20061 /// Parses a package group reference element.
20062 /// </summary>
20063 /// <param name="node">Element to parse.</param>
20064 /// <param name="parentType">ComplexReferenceParentType of parent element (Unknown or PackageGroup).</param>
20065 /// <param name="parentId">Identifier of parent element.</param>
20066 /// <param name="parentType">ComplexReferenceParentType of previous element (Unknown, Package, or PackageGroup).</param>
20067 /// <param name="parentId">Identifier of parent element.</param>
20068 /// <returns>Identifier for package group element.</rereturns>
20069 private string ParsePackageGroupRefElement(XElement node, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId)
20070 {
20071 Debug.Assert(ComplexReferenceParentType.Unknown == parentType || ComplexReferenceParentType.PackageGroup == parentType || ComplexReferenceParentType.Container == parentType);
20072 Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PackageGroup == previousType || ComplexReferenceChildType.Package == previousType);
20073
20074 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
20075 string id = null;
20076 string after = null;
20077
20078 foreach (XAttribute attrib in node.Attributes())
20079 {
20080 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
20081 {
20082 switch (attrib.Name.LocalName)
20083 {
20084 case "Id":
20085 id = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
20086 this.core.CreateSimpleReference(sourceLineNumbers, "WixBundlePackageGroup", id);
20087 break;
20088 case "After":
20089 after = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
20090 break;
20091 default:
20092 this.core.UnexpectedAttribute(node, attrib);
20093 break;
20094 }
20095 }
20096 else
20097 {
20098 this.core.ParseExtensionAttribute(node, attrib);
20099
20100 }
20101 }
20102
20103 if (null == id)
20104 {
20105 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
20106 }
20107
20108 if (null != after && ComplexReferenceParentType.Container == parentType)
20109 {
20110 this.core.OnMessage(WixErrors.IllegalAttributeWhenNested(sourceLineNumbers, node.Name.LocalName, "After", parentId));
20111 }
20112
20113 this.core.ParseForExtensionElements(node);
20114
20115 if (ComplexReferenceParentType.Container == parentType)
20116 {
20117 this.core.CreateWixGroupRow(sourceLineNumbers, ComplexReferenceParentType.Container, parentId, ComplexReferenceChildType.PackageGroup, id);
20118 }
20119 else
20120 {
20121 this.CreateChainPackageMetaRows(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.PackageGroup, id, previousType, previousId, after);
20122 }
20123
20124 return id;
20125 }
20126
20127 /// <summary>
20128 /// Creates rollback boundary.
20129 /// </summary>
20130 /// <param name="sourceLineNumbers">Source line numbers.</param>
20131 /// <param name="id">Identifier for the rollback boundary.</param>
20132 /// <param name="vital">Indicates whether the rollback boundary is vital or not.</param>
20133 /// <param name="parentType">Type of parent group.</param>
20134 /// <param name="parentId">Identifier of parent group.</param>
20135 /// <param name="previousType">Type of previous item, if any.</param>
20136 /// <param name="previousId">Identifier of previous item, if any.</param>
20137 private void CreateRollbackBoundary(SourceLineNumber sourceLineNumbers, Identifier id, YesNoType vital, YesNoType transaction, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType previousType, string previousId)
20138 {
20139 WixChainItemRow row = (WixChainItemRow)this.core.CreateRow(sourceLineNumbers, "WixChainItem", id);
20140
20141 WixBundleRollbackBoundaryRow rollbackBoundary = (WixBundleRollbackBoundaryRow)this.core.CreateRow(sourceLineNumbers, "WixBundleRollbackBoundary", id);
20142
20143 if (YesNoType.NotSet != vital)
20144 {
20145 rollbackBoundary.Vital = vital;
20146 }
20147 if (YesNoType.NotSet != transaction)
20148 {
20149 rollbackBoundary.Transaction = transaction;
20150 }
20151
20152 this.CreateChainPackageMetaRows(sourceLineNumbers, parentType, parentId, ComplexReferenceChildType.Package, id.Id, previousType, previousId, null);
20153 }
20154
20155 /// <summary>
20156 /// Creates group and ordering information for packages
20157 /// </summary>
20158 /// <param name="sourceLineNumbers">Source line numbers.</param>
20159 /// <param name="parentType">Type of parent group, if known.</param>
20160 /// <param name="parentId">Identifier of parent group, if known.</param>
20161 /// <param name="type">Type of this item.</param>
20162 /// <param name="id">Identifier for this item.</param>
20163 /// <param name="previousType">Type of previous item, if known.</param>
20164 /// <param name="previousId">Identifier of previous item, if known</param>
20165 /// <param name="afterId">Identifier of explicit 'After' attribute, if given.</param>
20166 private void CreateChainPackageMetaRows(SourceLineNumber sourceLineNumbers,
20167 ComplexReferenceParentType parentType, string parentId,
20168 ComplexReferenceChildType type, string id,
20169 ComplexReferenceChildType previousType, string previousId, string afterId)
20170 {
20171 // If there's an explicit 'After' attribute, it overrides the inferred previous item.
20172 if (null != afterId)
20173 {
20174 previousType = ComplexReferenceChildType.Package;
20175 previousId = afterId;
20176 }
20177
20178 this.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId, type, id, previousType, previousId);
20179 }
20180
20181 // TODO: Should we define our own enum for this, just to ensure there's no "cross-contamination"?
20182 // TODO: Also, we could potentially include an 'Attributes' field to track things like
20183 // 'before' vs. 'after', and explicit vs. inferred dependencies.
20184 private void CreateWixOrderingRow(SourceLineNumber sourceLineNumbers,
20185 ComplexReferenceChildType itemType, string itemId,
20186 ComplexReferenceChildType dependsOnType, string dependsOnId)
20187 {
20188 if (!this.core.EncounteredError)
20189 {
20190 Row row = this.core.CreateRow(sourceLineNumbers, "WixOrdering");
20191 row[0] = itemType.ToString();
20192 row[1] = itemId;
20193 row[2] = dependsOnType.ToString();
20194 row[3] = dependsOnId;
20195 }
20196 }
20197
20198 /// <summary>
20199 /// Parse MsiProperty element
20200 /// </summary>
20201 /// <param name="node">Element to parse</param>
20202 /// <param name="packageId">Id of parent element</param>
20203 private void ParseMsiPropertyElement(XElement node, string packageId)
20204 {
20205 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
20206 string name = null;
20207 string value = null;
20208 string condition = null;
20209
20210 foreach (XAttribute attrib in node.Attributes())
20211 {
20212 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
20213 {
20214 switch (attrib.Name.LocalName)
20215 {
20216 case "Name":
20217 name = this.core.GetAttributeMsiPropertyNameValue(sourceLineNumbers, attrib);
20218 break;
20219 case "Value":
20220 value = this.core.GetAttributeValue(sourceLineNumbers, attrib);
20221 break;
20222 case "Condition":
20223 condition = this.core.GetAttributeValue(sourceLineNumbers, attrib);
20224 break;
20225 default:
20226 this.core.UnexpectedAttribute(node, attrib);
20227 break;
20228 }
20229 }
20230 else
20231 {
20232 this.core.ParseExtensionAttribute(node, attrib);
20233 }
20234 }
20235
20236 if (null == name)
20237 {
20238 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
20239 }
20240
20241 if (null == value)
20242 {
20243 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value"));
20244 }
20245
20246 this.core.ParseForExtensionElements(node);
20247
20248 if (!this.core.EncounteredError)
20249 {
20250 WixBundleMsiPropertyRow row = (WixBundleMsiPropertyRow)this.core.CreateRow(sourceLineNumbers, "WixBundleMsiProperty");
20251 row.ChainPackageId = packageId;
20252 row.Name = name;
20253 row.Value = value;
20254
20255 if (!String.IsNullOrEmpty(condition))
20256 {
20257 row.Condition = condition;
20258 }
20259 }
20260 }
20261
20262 /// <summary>
20263 /// Parse SlipstreamMsp element
20264 /// </summary>
20265 /// <param name="node">Element to parse</param>
20266 /// <param name="packageId">Id of parent element</param>
20267 private void ParseSlipstreamMspElement(XElement node, string packageId)
20268 {
20269 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
20270 string id = null;
20271
20272 foreach (XAttribute attrib in node.Attributes())
20273 {
20274 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
20275 {
20276 switch (attrib.Name.LocalName)
20277 {
20278 case "Id":
20279 id = this.core.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
20280 this.core.CreateSimpleReference(sourceLineNumbers, "WixBundlePackage", id);
20281 break;
20282 default:
20283 this.core.UnexpectedAttribute(node, attrib);
20284 break;
20285 }
20286 }
20287 else
20288 {
20289 this.core.ParseExtensionAttribute(node, attrib);
20290 }
20291 }
20292
20293 if (null == id)
20294 {
20295 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
20296 }
20297
20298 this.core.ParseForExtensionElements(node);
20299
20300 if (!this.core.EncounteredError)
20301 {
20302 WixBundleSlipstreamMspRow row = (WixBundleSlipstreamMspRow)this.core.CreateRow(sourceLineNumbers, "WixBundleSlipstreamMsp");
20303 row.ChainPackageId = packageId;
20304 row.MspPackageId = id;
20305 }
20306 }
20307
20308 /// <summary>
20309 /// Parse RelatedBundle element
20310 /// </summary>
20311 /// <param name="node">Element to parse</param>
20312 private void ParseRelatedBundleElement(XElement node)
20313 {
20314 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
20315 string id = null;
20316 string action = null;
20317 Wix.RelatedBundle.ActionType actionType = Wix.RelatedBundle.ActionType.Detect;
20318
20319 foreach (XAttribute attrib in node.Attributes())
20320 {
20321 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
20322 {
20323 switch (attrib.Name.LocalName)
20324 {
20325 case "Id":
20326 id = this.core.GetAttributeGuidValue(sourceLineNumbers, attrib, false);
20327 break;
20328 case "Action":
20329 action = this.core.GetAttributeValue(sourceLineNumbers, attrib);
20330 break;
20331 default:
20332 this.core.UnexpectedAttribute(node, attrib);
20333 break;
20334 }
20335 }
20336 else
20337 {
20338 this.core.ParseExtensionAttribute(node, attrib);
20339 }
20340 }
20341
20342 if (null == id)
20343 {
20344 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
20345 }
20346
20347 if (!String.IsNullOrEmpty(action))
20348 {
20349 actionType = Wix.RelatedBundle.ParseActionType(action);
20350 switch (actionType)
20351 {
20352 case Wix.RelatedBundle.ActionType.Detect:
20353 break;
20354 case Wix.RelatedBundle.ActionType.Upgrade:
20355 break;
20356 case Wix.RelatedBundle.ActionType.Addon:
20357 break;
20358 case Wix.RelatedBundle.ActionType.Patch:
20359 break;
20360 default:
20361 this.core.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, node.Name.LocalName, "Action", action, "Detect", "Upgrade", "Addon", "Patch"));
20362 break;
20363 }
20364 }
20365
20366 this.core.ParseForExtensionElements(node);
20367
20368 if (!this.core.EncounteredError)
20369 {
20370 Row row = this.core.CreateRow(sourceLineNumbers, "WixRelatedBundle");
20371 row[0] = id;
20372 row[1] = (int)actionType;
20373 }
20374 }
20375
20376 /// <summary>
20377 /// Parse Update element
20378 /// </summary>
20379 /// <param name="node">Element to parse</param>
20380 private void ParseUpdateElement(XElement node)
20381 {
20382 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
20383 string location = null;
20384
20385 foreach (XAttribute attrib in node.Attributes())
20386 {
20387 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
20388 {
20389 switch (attrib.Name.LocalName)
20390 {
20391 case "Location":
20392 location = this.core.GetAttributeValue(sourceLineNumbers, attrib);
20393 break;
20394 default:
20395 this.core.UnexpectedAttribute(node, attrib);
20396 break;
20397 }
20398 }
20399 else
20400 {
20401 this.core.ParseExtensionAttribute(node, attrib);
20402 }
20403 }
20404
20405 if (null == location)
20406 {
20407 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Location"));
20408 }
20409
20410 this.core.ParseForExtensionElements(node);
20411
20412 if (!this.core.EncounteredError)
20413 {
20414 Row row = this.core.CreateRow(sourceLineNumbers, "WixBundleUpdate");
20415 row[0] = location;
20416 }
20417 }
20418
20419 /// <summary>
20420 /// Parse Variable element
20421 /// </summary>
20422 /// <param name="node">Element to parse</param>
20423 private void ParseVariableElement(XElement node)
20424 {
20425 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
20426 bool hidden = false;
20427 string name = null;
20428 bool persisted = false;
20429 string value = null;
20430 string type = null;
20431
20432 foreach (XAttribute attrib in node.Attributes())
20433 {
20434 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
20435 {
20436 switch (attrib.Name.LocalName)
20437 {
20438 case "Hidden":
20439 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
20440 {
20441 hidden = true;
20442 }
20443 break;
20444 case "Name":
20445 name = this.core.GetAttributeBundleVariableValue(sourceLineNumbers, attrib);
20446 break;
20447 case "Persisted":
20448 if (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib))
20449 {
20450 persisted = true;
20451 }
20452 break;
20453 case "Value":
20454 value = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty);
20455 break;
20456 case "Type":
20457 type = this.core.GetAttributeValue(sourceLineNumbers, attrib);
20458 break;
20459 default:
20460 this.core.UnexpectedAttribute(node, attrib);
20461 break;
20462 }
20463 }
20464 else
20465 {
20466 this.core.ParseExtensionAttribute(node, attrib);
20467 }
20468 }
20469
20470 if (null == name)
20471 {
20472 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Name"));
20473 }
20474 else if (name.StartsWith("Wix", StringComparison.OrdinalIgnoreCase))
20475 {
20476 this.core.OnMessage(WixErrors.ReservedNamespaceViolation(sourceLineNumbers, node.Name.LocalName, "Name", "Wix"));
20477 }
20478
20479 if (null == type && null != value)
20480 {
20481 // Infer the type from the current value...
20482 if (value.StartsWith("v", StringComparison.OrdinalIgnoreCase))
20483 {
20484 // Version constructor does not support simple "v#" syntax so check to see if the value is
20485 // non-negative real quick.
20486 Int32 number;
20487 if (Int32.TryParse(value.Substring(1), NumberStyles.None, CultureInfo.InvariantCulture.NumberFormat, out number))
20488 {
20489 type = "version";
20490 }
20491 else
20492 {
20493 // Sadly, Version doesn't have a TryParse() method until .NET 4, so we have to try/catch to see if it parses.
20494 try
20495 {
20496 Version version = new Version(value.Substring(1));
20497 type = "version";
20498 }
20499 catch (Exception)
20500 {
20501 }
20502 }
20503 }
20504
20505 // Not a version, check for numeric.
20506 if (null == type)
20507 {
20508 Int64 number;
20509 if (Int64.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture.NumberFormat, out number))
20510 {
20511 type = "numeric";
20512 }
20513 else
20514 {
20515 type = "string";
20516 }
20517 }
20518 }
20519
20520 if (null == value && null != type)
20521 {
20522 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, "Variable", "Value", "Type"));
20523 }
20524
20525 this.core.ParseForExtensionElements(node);
20526
20527 if (!this.core.EncounteredError)
20528 {
20529 WixBundleVariableRow row = (WixBundleVariableRow)this.core.CreateRow(sourceLineNumbers, "WixBundleVariable");
20530 row.Id = name;
20531 row.Value = value;
20532 row.Type = type;
20533 row.Hidden = hidden;
20534 row.Persisted = persisted;
20535 }
20536 }
20537
20538
20539
20540 /// <summary>
20541 /// Parses a Wix element.
20542 /// </summary>
20543 /// <param name="node">Element to parse.</param>
20544 private void ParseWixElement(XElement node)
20545 {
20546 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
20547 string requiredVersion = null;
20548
20549 foreach (XAttribute attrib in node.Attributes())
20550 {
20551 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
20552 {
20553 switch (attrib.Name.LocalName)
20554 {
20555 case "RequiredVersion":
20556 requiredVersion = this.core.GetAttributeVersionValue(sourceLineNumbers, attrib);
20557 break;
20558 default:
20559 this.core.UnexpectedAttribute(node, attrib);
20560 break;
20561 }
20562 }
20563 else
20564 {
20565 this.core.ParseExtensionAttribute(node, attrib);
20566 }
20567 }
20568
20569 if (null != requiredVersion)
20570 {
20571 this.core.VerifyRequiredVersion(sourceLineNumbers, requiredVersion);
20572 }
20573
20574 foreach (XElement child in node.Elements())
20575 {
20576 if (CompilerCore.WixNamespace == child.Name.Namespace)
20577 {
20578 switch (child.Name.LocalName)
20579 {
20580 case "Bundle":
20581 this.ParseBundleElement(child);
20582 break;
20583 case "Fragment":
20584 this.ParseFragmentElement(child);
20585 break;
20586 case "Module":
20587 this.ParseModuleElement(child);
20588 break;
20589 case "PatchCreation":
20590 this.ParsePatchCreationElement(child);
20591 break;
20592 case "Product":
20593 this.ParseProductElement(child);
20594 break;
20595 case "Patch":
20596 this.ParsePatchElement(child);
20597 break;
20598 default:
20599 this.core.UnexpectedElement(node, child);
20600 break;
20601 }
20602 }
20603 else
20604 {
20605 this.core.ParseExtensionElement(node, child);
20606 }
20607 }
20608 }
20609
20610 /// <summary>
20611 /// Parses a WixVariable element.
20612 /// </summary>
20613 /// <param name="node">Element to parse.</param>
20614 private void ParseWixVariableElement(XElement node)
20615 {
20616 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
20617 Identifier id = null;
20618 bool overridable = false;
20619 string value = null;
20620
20621 foreach (XAttribute attrib in node.Attributes())
20622 {
20623 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
20624 {
20625 switch (attrib.Name.LocalName)
20626 {
20627 case "Id":
20628 id = this.core.GetAttributeIdentifier(sourceLineNumbers, attrib);
20629 break;
20630 case "Overridable":
20631 overridable = (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib));
20632 break;
20633 case "Value":
20634 value = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty);
20635 break;
20636 default:
20637 this.core.UnexpectedAttribute(node, attrib);
20638 break;
20639 }
20640 }
20641 else
20642 {
20643 this.core.ParseExtensionAttribute(node, attrib);
20644 }
20645 }
20646
20647 if (null == id)
20648 {
20649 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Id"));
20650 }
20651
20652 if (null == value)
20653 {
20654 this.core.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Value"));
20655 }
20656
20657 this.core.ParseForExtensionElements(node);
20658
20659 if (!this.core.EncounteredError)
20660 {
20661 WixVariableRow wixVariableRow = (WixVariableRow)this.core.CreateRow(sourceLineNumbers, "WixVariable", id);
20662 wixVariableRow.Value = value;
20663 wixVariableRow.Overridable = overridable;
20664 }
20665 }
20666 }
20667}
diff --git a/src/WixToolset.Core/CompilerCore.cs b/src/WixToolset.Core/CompilerCore.cs
new file mode 100644
index 00000000..8640a2da
--- /dev/null
+++ b/src/WixToolset.Core/CompilerCore.cs
@@ -0,0 +1,1932 @@
1// Copyright (c) .NET Foundation and 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.Collections;
7 using System.Collections.Generic;
8 using System.Diagnostics;
9 using System.Diagnostics.CodeAnalysis;
10 using System.Globalization;
11 using System.IO;
12 using System.Reflection;
13 using System.Security.Cryptography;
14 using System.Text;
15 using System.Text.RegularExpressions;
16 using System.Xml.Linq;
17 using WixToolset.Data;
18 using WixToolset.Data.Rows;
19 using WixToolset.Extensibility;
20 using Wix = WixToolset.Data.Serialize;
21
22 internal enum ValueListKind
23 {
24 /// <summary>
25 /// A list of values with nothing before the final value.
26 /// </summary>
27 None,
28
29 /// <summary>
30 /// A list of values with 'and' before the final value.
31 /// </summary>
32 And,
33
34 /// <summary>
35 /// A list of values with 'or' before the final value.
36 /// </summary>
37 Or
38 }
39
40 /// <summary>
41 /// Core class for the compiler.
42 /// </summary>
43 internal sealed class CompilerCore : ICompilerCore
44 {
45 internal static readonly XNamespace W3SchemaPrefix = "http://www.w3.org/";
46 internal static readonly XNamespace WixNamespace = "http://wixtoolset.org/schemas/v4/wxs";
47
48 public const int DefaultMaximumUncompressedMediaSize = 200; // Default value is 200 MB
49 public const int MinValueOfMaxCabSizeForLargeFileSplitting = 20; // 20 MB
50 public const int MaxValueOfMaxCabSizeForLargeFileSplitting = 2 * 1024; // 2048 MB (i.e. 2 GB)
51
52 private static readonly Regex AmbiguousFilename = new Regex(@"^.{6}\~\d", RegexOptions.Compiled);
53
54 private const string IllegalLongFilenameCharacters = @"[\\\?|><:/\*""]"; // illegal: \ ? | > < : / * "
55 private static readonly Regex IllegalLongFilename = new Regex(IllegalLongFilenameCharacters, RegexOptions.Compiled);
56
57 private const string LegalLongFilenameCharacters = @"[^\\\?|><:/\*""]"; // opposite of illegal above.
58 private static readonly Regex LegalLongFilename = new Regex(String.Concat("^", LegalLongFilenameCharacters, @"{1,259}$"), RegexOptions.Compiled);
59
60 private const string LegalRelativeLongFilenameCharacters = @"[^\?|><:/\*""]"; // (like legal long, but we allow '\') illegal: ? | > < : / * "
61 private static readonly Regex LegalRelativeLongFilename = new Regex(String.Concat("^", LegalRelativeLongFilenameCharacters, @"{1,259}$"), RegexOptions.Compiled);
62
63 private const string LegalWildcardLongFilenameCharacters = @"[^\\|><:/""]"; // illegal: \ | > < : / "
64 private static readonly Regex LegalWildcardLongFilename = new Regex(String.Concat("^", LegalWildcardLongFilenameCharacters, @"{1,259}$"));
65
66 private static readonly Regex PutGuidHere = new Regex(@"PUT\-GUID\-(?:\d+\-)?HERE", RegexOptions.Singleline);
67
68 private static readonly Regex LegalIdentifierWithAccess = new Regex(@"^((?<access>public|internal|protected|private)\s+)?(?<id>[_A-Za-z][0-9A-Za-z_\.]*)$", RegexOptions.Compiled | RegexOptions.ExplicitCapture);
69
70 // Built-in variables (from burn\engine\variable.cpp, "vrgBuiltInVariables", around line 113)
71 private static readonly List<String> BuiltinBundleVariables = new List<string>(
72 new string[] {
73 "AdminToolsFolder",
74 "AppDataFolder",
75 "CommonAppDataFolder",
76 "CommonFiles64Folder",
77 "CommonFilesFolder",
78 "CompatibilityMode",
79 "Date",
80 "DesktopFolder",
81 "FavoritesFolder",
82 "FontsFolder",
83 "InstallerName",
84 "InstallerVersion",
85 "LocalAppDataFolder",
86 "LogonUser",
87 "MyPicturesFolder",
88 "NTProductType",
89 "NTSuiteBackOffice",
90 "NTSuiteDataCenter",
91 "NTSuiteEnterprise",
92 "NTSuitePersonal",
93 "NTSuiteSmallBusiness",
94 "NTSuiteSmallBusinessRestricted",
95 "NTSuiteWebServer",
96 "PersonalFolder",
97 "Privileged",
98 "ProgramFiles64Folder",
99 "ProgramFiles6432Folder",
100 "ProgramFilesFolder",
101 "ProgramMenuFolder",
102 "RebootPending",
103 "SendToFolder",
104 "ServicePackLevel",
105 "StartMenuFolder",
106 "StartupFolder",
107 "System64Folder",
108 "SystemFolder",
109 "TempFolder",
110 "TemplateFolder",
111 "TerminalServer",
112 "UserLanguageID",
113 "UserUILanguageID",
114 "VersionMsi",
115 "VersionNT",
116 "VersionNT64",
117 "WindowsFolder",
118 "WindowsVolume",
119 "WixBundleAction",
120 "WixBundleForcedRestartPackage",
121 "WixBundleElevated",
122 "WixBundleInstalled",
123 "WixBundleProviderKey",
124 "WixBundleTag",
125 "WixBundleVersion",
126 });
127
128 private static readonly List<string> DisallowedMsiProperties = new List<string>(
129 new string[] {
130 "ACTION",
131 "ADDLOCAL",
132 "ADDSOURCE",
133 "ADDDEFAULT",
134 "ADVERTISE",
135 "ALLUSERS",
136 "REBOOT",
137 "REINSTALL",
138 "REINSTALLMODE",
139 "REMOVE"
140 });
141
142 private TableDefinitionCollection tableDefinitions;
143 private Dictionary<XNamespace, ICompilerExtension> extensions;
144 private Intermediate intermediate;
145 private bool showPedanticMessages;
146
147 private HashSet<string> activeSectionInlinedDirectoryIds;
148 private HashSet<string> activeSectionSimpleReferences;
149
150 /// <summary>
151 /// Constructor for all compiler core.
152 /// </summary>
153 /// <param name="intermediate">The Intermediate object representing compiled source document.</param>
154 /// <param name="tableDefinitions">The loaded table definition collection.</param>
155 /// <param name="extensions">The WiX extensions collection.</param>
156 internal CompilerCore(Intermediate intermediate, TableDefinitionCollection tableDefinitions, Dictionary<XNamespace, ICompilerExtension> extensions)
157 {
158 this.tableDefinitions = tableDefinitions;
159 this.extensions = extensions;
160 this.intermediate = intermediate;
161 }
162
163 /// <summary>
164 /// Gets the section the compiler is currently emitting symbols into.
165 /// </summary>
166 /// <value>The section the compiler is currently emitting symbols into.</value>
167 public Section ActiveSection { get; private set; }
168
169 /// <summary>
170 /// Gets or sets the platform which the compiler will use when defaulting 64-bit attributes and elements.
171 /// </summary>
172 /// <value>The platform which the compiler will use when defaulting 64-bit attributes and elements.</value>
173 public Platform CurrentPlatform { get; set; }
174
175 /// <summary>
176 /// Gets whether the compiler core encountered an error while processing.
177 /// </summary>
178 /// <value>Flag if core encountered an error during processing.</value>
179 public bool EncounteredError
180 {
181 get { return Messaging.Instance.EncounteredError; }
182 }
183
184 /// <summary>
185 /// Gets or sets the option to show pedantic messages.
186 /// </summary>
187 /// <value>The option to show pedantic messages.</value>
188 public bool ShowPedanticMessages
189 {
190 get { return this.showPedanticMessages; }
191 set { this.showPedanticMessages = value; }
192 }
193
194 /// <summary>
195 /// Gets the table definitions used by the compiler core.
196 /// </summary>
197 /// <value>Table definition collection.</value>
198 public TableDefinitionCollection TableDefinitions
199 {
200 get { return this.tableDefinitions; }
201 }
202
203 /// <summary>
204 /// Convert a bit array into an int value.
205 /// </summary>
206 /// <param name="bits">The bit array to convert.</param>
207 /// <returns>The converted int value.</returns>
208 public int CreateIntegerFromBitArray(BitArray bits)
209 {
210 if (32 != bits.Length)
211 {
212 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");
213 }
214
215 int[] intArray = new int[1];
216 bits.CopyTo(intArray, 0);
217
218 return intArray[0];
219 }
220
221 /// <summary>
222 /// Sets a bit in a bit array based on the index at which an attribute name was found in a string array.
223 /// </summary>
224 /// <param name="attributeNames">Array of attributes that map to bits.</param>
225 /// <param name="attributeName">Name of attribute to check.</param>
226 /// <param name="attributeValue">Value of attribute to check.</param>
227 /// <param name="bits">The bit array in which the bit will be set if found.</param>
228 /// <param name="offset">The offset into the bit array.</param>
229 /// <returns>true if the bit was set; false otherwise.</returns>
230 public bool TrySetBitFromName(string[] attributeNames, string attributeName, YesNoType attributeValue, BitArray bits, int offset)
231 {
232 for (int i = 0; i < attributeNames.Length; i++)
233 {
234 if (attributeName.Equals(attributeNames[i], StringComparison.Ordinal))
235 {
236 bits.Set(i + offset, YesNoType.Yes == attributeValue);
237 return true;
238 }
239 }
240
241 return false;
242 }
243
244 /// <summary>
245 /// Verifies that a filename is ambiguous.
246 /// </summary>
247 /// <param name="filename">Filename to verify.</param>
248 /// <returns>true if the filename is ambiguous; false otherwise.</returns>
249 public static bool IsAmbiguousFilename(string filename)
250 {
251 if (null == filename || 0 == filename.Length)
252 {
253 return false;
254 }
255
256 return CompilerCore.AmbiguousFilename.IsMatch(filename);
257 }
258
259 /// <summary>
260 /// Verifies that a value is a legal identifier.
261 /// </summary>
262 /// <param name="value">The value to verify.</param>
263 /// <returns>true if the value is an identifier; false otherwise.</returns>
264 public bool IsValidIdentifier(string value)
265 {
266 return Common.IsIdentifier(value);
267 }
268
269 /// <summary>
270 /// Verifies if an identifier is a valid loc identifier.
271 /// </summary>
272 /// <param name="identifier">Identifier to verify.</param>
273 /// <returns>True if the identifier is a valid loc identifier.</returns>
274 public bool IsValidLocIdentifier(string identifier)
275 {
276 if (String.IsNullOrEmpty(identifier))
277 {
278 return false;
279 }
280
281 Match match = Common.WixVariableRegex.Match(identifier);
282
283 return (match.Success && "loc" == match.Groups["namespace"].Value && 0 == match.Index && identifier.Length == match.Length);
284 }
285
286 /// <summary>
287 /// Verifies if a filename is a valid long filename.
288 /// </summary>
289 /// <param name="filename">Filename to verify.</param>
290 /// <param name="allowWildcards">true if wildcards are allowed in the filename.</param>
291 /// <param name="allowRelative">true if relative paths are allowed in the filename.</param>
292 /// <returns>True if the filename is a valid long filename</returns>
293 public bool IsValidLongFilename(string filename, bool allowWildcards = false, bool allowRelative = false)
294 {
295 if (String.IsNullOrEmpty(filename))
296 {
297 return false;
298 }
299
300 // check for a non-period character (all periods is not legal)
301 bool nonPeriodFound = false;
302 foreach (char character in filename)
303 {
304 if ('.' != character)
305 {
306 nonPeriodFound = true;
307 break;
308 }
309 }
310
311 if (allowWildcards)
312 {
313 return (nonPeriodFound && CompilerCore.LegalWildcardLongFilename.IsMatch(filename));
314 }
315 else if (allowRelative)
316 {
317 return (nonPeriodFound && CompilerCore.LegalRelativeLongFilename.IsMatch(filename));
318 }
319 else
320 {
321 return (nonPeriodFound && CompilerCore.LegalLongFilename.IsMatch(filename));
322 }
323 }
324
325 /// <summary>
326 /// Verifies if a filename is a valid short filename.
327 /// </summary>
328 /// <param name="filename">Filename to verify.</param>
329 /// <param name="allowWildcards">true if wildcards are allowed in the filename.</param>
330 /// <returns>True if the filename is a valid short filename</returns>
331 public bool IsValidShortFilename(string filename, bool allowWildcards)
332 {
333 return Common.IsValidShortFilename(filename, allowWildcards);
334 }
335
336 /// <summary>
337 /// Replaces the illegal filename characters to create a legal name.
338 /// </summary>
339 /// <param name="filename">Filename to make valid.</param>
340 /// <param name="replace">Replacement string for invalid characters in filename.</param>
341 /// <returns>Valid filename.</returns>
342 public static string MakeValidLongFileName(string filename, string replace)
343 {
344 return CompilerCore.IllegalLongFilename.Replace(filename, replace);
345 }
346
347 /// <summary>
348 /// Creates a short file/directory name using an identifier and long file/directory name as input.
349 /// </summary>
350 /// <param name="longName">The long file/directory name.</param>
351 /// <param name="keepExtension">The option to keep the extension on generated short names.</param>
352 /// <param name="allowWildcards">true if wildcards are allowed in the filename.</param>
353 /// <param name="args">Any additional information to include in the hash for the generated short name.</param>
354 /// <returns>The generated 8.3-compliant short file/directory name.</returns>
355 public string CreateShortName(string longName, bool keepExtension, bool allowWildcards, params string[] args)
356 {
357 // canonicalize the long name if its not a localization identifier (they are case-sensitive)
358 if (!this.IsValidLocIdentifier(longName))
359 {
360 longName = longName.ToLowerInvariant();
361 }
362
363 // collect all the data
364 List<string> strings = new List<string>(1 + args.Length);
365 strings.Add(longName);
366 strings.AddRange(args);
367
368 // prepare for hashing
369 string stringData = String.Join("|", strings);
370 byte[] data = Encoding.UTF8.GetBytes(stringData);
371
372 // hash the data
373 byte[] hash;
374 using (SHA1 sha1 = new SHA1CryptoServiceProvider())
375 {
376 hash = sha1.ComputeHash(data);
377 }
378
379 // generate the short file/directory name without an extension
380 StringBuilder shortName = new StringBuilder(Convert.ToBase64String(hash));
381 shortName.Remove(8, shortName.Length - 8).Replace('+', '-').Replace('/', '_');
382
383 if (keepExtension)
384 {
385 string extension = Path.GetExtension(longName);
386
387 if (4 < extension.Length)
388 {
389 extension = extension.Substring(0, 4);
390 }
391
392 shortName.Append(extension);
393
394 // check the generated short name to ensure its still legal (the extension may not be legal)
395 if (!this.IsValidShortFilename(shortName.ToString(), allowWildcards))
396 {
397 // remove the extension (by truncating the generated file name back to the generated characters)
398 shortName.Length -= extension.Length;
399 }
400 }
401
402 return shortName.ToString().ToLowerInvariant();
403 }
404
405 /// <summary>
406 /// Verifies the given string is a valid product version.
407 /// </summary>
408 /// <param name="version">The product version to verify.</param>
409 /// <returns>True if version is a valid product version</returns>
410 public static bool IsValidProductVersion(string version)
411 {
412 if (!Common.IsValidBinderVariable(version))
413 {
414 Version ver = new Version(version);
415
416 if (255 < ver.Major || 255 < ver.Minor || 65535 < ver.Build)
417 {
418 return false;
419 }
420 }
421
422 return true;
423 }
424
425 /// <summary>
426 /// Verifies the given string is a valid module or bundle version.
427 /// </summary>
428 /// <param name="version">The version to verify.</param>
429 /// <returns>True if version is a valid module or bundle version.</returns>
430 public static bool IsValidModuleOrBundleVersion(string version)
431 {
432 return Common.IsValidModuleOrBundleVersion(version);
433 }
434
435 /// <summary>
436 /// Get an element's inner text and trims any extra whitespace.
437 /// </summary>
438 /// <param name="element">The element with inner text to be trimmed.</param>
439 /// <returns>The node's inner text trimmed.</returns>
440 public string GetTrimmedInnerText(XElement element)
441 {
442 string value = Common.GetInnerText(element);
443 return (null == value) ? null : value.Trim();
444 }
445
446 /// <summary>
447 /// Gets element's inner text and ensure's it is safe for use in a condition by trimming any extra whitespace.
448 /// </summary>
449 /// <param name="element">The element to ensure inner text is a condition.</param>
450 /// <returns>The value converted into a safe condition.</returns>
451 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
452 public string GetConditionInnerText(XElement element)
453 {
454 string value = element.Value;
455 if (0 < value.Length)
456 {
457 value = value.Trim();
458 value = value.Replace('\t', ' ');
459 value = value.Replace('\r', ' ');
460 value = value.Replace('\n', ' ');
461 }
462 else // return null for a non-existant condition
463 {
464 value = null;
465 }
466
467 return value;
468 }
469
470 /// <summary>
471 /// Creates a version 3 name-based UUID.
472 /// </summary>
473 /// <param name="namespaceGuid">The namespace UUID.</param>
474 /// <param name="value">The value.</param>
475 /// <returns>The generated GUID for the given namespace and value.</returns>
476 public string CreateGuid(Guid namespaceGuid, string value)
477 {
478 return Uuid.NewUuid(namespaceGuid, value).ToString("B").ToUpperInvariant();
479 }
480
481 /// <summary>
482 /// Creates a row in the active section.
483 /// </summary>
484 /// <param name="sourceLineNumbers">Source and line number of current row.</param>
485 /// <param name="tableName">Name of table to create row in.</param>
486 /// <returns>New row.</returns>
487 public Row CreateRow(SourceLineNumber sourceLineNumbers, string tableName, Identifier identifier = null)
488 {
489 return this.CreateRow(sourceLineNumbers, tableName, this.ActiveSection, identifier);
490 }
491
492 /// <summary>
493 /// Creates a row in the active given <paramref name="section"/>.
494 /// </summary>
495 /// <param name="sourceLineNumbers">Source and line number of current row.</param>
496 /// <param name="tableName">Name of table to create row in.</param>
497 /// <param name="section">The section to which the row is added. If null, the row is added to the active section.</param>
498 /// <returns>New row.</returns>
499 internal Row CreateRow(SourceLineNumber sourceLineNumbers, string tableName, Section section, Identifier identifier = null)
500 {
501 TableDefinition tableDefinition = this.tableDefinitions[tableName];
502 Table table = section.EnsureTable(tableDefinition);
503 Row row = table.CreateRow(sourceLineNumbers);
504
505 if (null != identifier)
506 {
507 row.Access = identifier.Access;
508 row[0] = identifier.Id;
509 }
510
511 return row;
512 }
513
514 /// <summary>
515 /// Creates directories using the inline directory syntax.
516 /// </summary>
517 /// <param name="sourceLineNumbers">Source line information.</param>
518 /// <param name="attribute">The attribute to parse.</param>
519 /// <param name="parentId">Optional identifier of parent directory.</param>
520 /// <returns>Identifier of the leaf directory created.</returns>
521 public string CreateDirectoryReferenceFromInlineSyntax(SourceLineNumber sourceLineNumbers, XAttribute attribute, string parentId)
522 {
523 string id = null;
524 string[] inlineSyntax = this.GetAttributeInlineDirectorySyntax(sourceLineNumbers, attribute, true);
525
526 if (null != inlineSyntax)
527 {
528 // Special case the single entry in the inline syntax since it is the most common case
529 // and needs no extra processing. It's just a reference to an existing directory.
530 if (1 == inlineSyntax.Length)
531 {
532 id = inlineSyntax[0];
533 this.CreateSimpleReference(sourceLineNumbers, "Directory", id);
534 }
535 else // start creating rows for the entries in the inline syntax
536 {
537 id = parentId;
538
539 int pathStartsAt = 0;
540 if (inlineSyntax[0].EndsWith(":"))
541 {
542 // TODO: should overriding the parent identifier with a specific id be an error or a warning or just let it slide?
543 //if (null != parentId)
544 //{
545 // this.core.OnMessage(WixErrors.Xxx(sourceLineNumbers));
546 //}
547
548 id = inlineSyntax[0].TrimEnd(':');
549 this.CreateSimpleReference(sourceLineNumbers, "Directory", id);
550
551 pathStartsAt = 1;
552 }
553
554 for (int i = pathStartsAt; i < inlineSyntax.Length; ++i)
555 {
556 Identifier inlineId = this.CreateDirectoryRow(sourceLineNumbers, null, id, inlineSyntax[i]);
557 id = inlineId.Id;
558 }
559 }
560 }
561
562 return id;
563 }
564
565 /// <summary>
566 /// Creates a patch resource reference to the list of resoures to be filtered when producing a patch. This method should only be used when processing children of a patch family.
567 /// </summary>
568 /// <param name="sourceLineNumbers">Source and line number of current row.</param>
569 /// <param name="tableName">Name of table to create row in.</param>
570 /// <param name="primaryKeys">Array of keys that make up the primary key of the table.</param>
571 /// <returns>New row.</returns>
572 public void CreatePatchFamilyChildReference(SourceLineNumber sourceLineNumbers, string tableName, params string[] primaryKeys)
573 {
574 Row patchReferenceRow = this.CreateRow(sourceLineNumbers, "WixPatchRef");
575 patchReferenceRow[0] = tableName;
576 patchReferenceRow[1] = String.Join("/", primaryKeys);
577 }
578
579 /// <summary>
580 /// Creates a Registry row in the active section.
581 /// </summary>
582 /// <param name="sourceLineNumbers">Source and line number of the current row.</param>
583 /// <param name="root">The registry entry root.</param>
584 /// <param name="key">The registry entry key.</param>
585 /// <param name="name">The registry entry name.</param>
586 /// <param name="value">The registry entry value.</param>
587 /// <param name="componentId">The component which will control installation/uninstallation of the registry entry.</param>
588 /// <param name="escapeLeadingHash">If true, "escape" leading '#' characters so the value is written as a REG_SZ.</param>
589 public Identifier CreateRegistryRow(SourceLineNumber sourceLineNumbers, int root, string key, string name, string value, string componentId, bool escapeLeadingHash)
590 {
591 Identifier id = null;
592
593 if (!this.EncounteredError)
594 {
595 if (-1 > root || 3 < root)
596 {
597 throw new ArgumentOutOfRangeException("root");
598 }
599
600 if (null == key)
601 {
602 throw new ArgumentNullException("key");
603 }
604
605 if (null == componentId)
606 {
607 throw new ArgumentNullException("componentId");
608 }
609
610 // escape the leading '#' character for string registry values
611 if (escapeLeadingHash && null != value && value.StartsWith("#", StringComparison.Ordinal))
612 {
613 value = String.Concat("#", value);
614 }
615
616 id = this.CreateIdentifier("reg", componentId, root.ToString(CultureInfo.InvariantCulture.NumberFormat), key.ToLowerInvariant(), (null != name ? name.ToLowerInvariant() : name));
617 Row row = this.CreateRow(sourceLineNumbers, "Registry", id);
618 row[1] = root;
619 row[2] = key;
620 row[3] = name;
621 row[4] = value;
622 row[5] = componentId;
623 }
624
625 return id;
626 }
627
628 /// <summary>
629 /// Creates a Registry row in the active section.
630 /// </summary>
631 /// <param name="sourceLineNumbers">Source and line number of the current row.</param>
632 /// <param name="root">The registry entry root.</param>
633 /// <param name="key">The registry entry key.</param>
634 /// <param name="name">The registry entry name.</param>
635 /// <param name="value">The registry entry value.</param>
636 /// <param name="componentId">The component which will control installation/uninstallation of the registry entry.</param>
637 public Identifier CreateRegistryRow(SourceLineNumber sourceLineNumbers, int root, string key, string name, string value, string componentId)
638 {
639 return this.CreateRegistryRow(sourceLineNumbers, root, key, name, value, componentId, true);
640 }
641
642 /// <summary>
643 /// Create a WixSimpleReference row in the active section.
644 /// </summary>
645 /// <param name="sourceLineNumbers">Source line information for the row.</param>
646 /// <param name="tableName">The table name of the simple reference.</param>
647 /// <param name="primaryKeys">The primary keys of the simple reference.</param>
648 public void CreateSimpleReference(SourceLineNumber sourceLineNumbers, string tableName, params string[] primaryKeys)
649 {
650 if (!this.EncounteredError)
651 {
652 string joinedKeys = String.Join("/", primaryKeys);
653 string id = String.Concat(tableName, ":", joinedKeys);
654
655 // If this simple reference hasn't been added to the active section already, add it.
656 if (this.activeSectionSimpleReferences.Add(id))
657 {
658 WixSimpleReferenceRow wixSimpleReferenceRow = (WixSimpleReferenceRow)this.CreateRow(sourceLineNumbers, "WixSimpleReference");
659 wixSimpleReferenceRow.TableName = tableName;
660 wixSimpleReferenceRow.PrimaryKeys = joinedKeys;
661 }
662 }
663 }
664
665 /// <summary>
666 /// A row in the WixGroup table is added for this child node and its parent node.
667 /// </summary>
668 /// <param name="sourceLineNumbers">Source line information for the row.</param>
669 /// <param name="parentType">Type of child's complex reference parent.</param>
670 /// <param name="parentId">Id of the parenet node.</param>
671 /// <param name="childType">Complex reference type of child</param>
672 /// <param name="childId">Id of the Child Node.</param>
673 public void CreateWixGroupRow(SourceLineNumber sourceLineNumbers, ComplexReferenceParentType parentType, string parentId, ComplexReferenceChildType childType, string childId)
674 {
675 if (!this.EncounteredError)
676 {
677 if (null == parentId || ComplexReferenceParentType.Unknown == parentType)
678 {
679 return;
680 }
681
682 if (null == childId)
683 {
684 throw new ArgumentNullException("childId");
685 }
686
687 WixGroupRow WixGroupRow = (WixGroupRow)this.CreateRow(sourceLineNumbers, "WixGroup");
688 WixGroupRow.ParentId = parentId;
689 WixGroupRow.ParentType = parentType;
690 WixGroupRow.ChildId = childId;
691 WixGroupRow.ChildType = childType;
692 }
693 }
694
695 /// <summary>
696 /// Add the appropriate rows to make sure that the given table shows up
697 /// in the resulting output.
698 /// </summary>
699 /// <param name="sourceLineNumbers">Source line numbers.</param>
700 /// <param name="tableName">Name of the table to ensure existance of.</param>
701 public void EnsureTable(SourceLineNumber sourceLineNumbers, string tableName)
702 {
703 if (!this.EncounteredError)
704 {
705 Row row = this.CreateRow(sourceLineNumbers, "WixEnsureTable");
706 row[0] = tableName;
707
708 // We don't add custom table definitions to the tableDefinitions collection,
709 // so if it's not in there, it better be a custom table. If the Id is just wrong,
710 // instead of a custom table, we get an unresolved reference at link time.
711 if (!this.tableDefinitions.Contains(tableName))
712 {
713 this.CreateSimpleReference(sourceLineNumbers, "WixCustomTable", tableName);
714 }
715 }
716 }
717
718 /// <summary>
719 /// Get an attribute value.
720 /// </summary>
721 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
722 /// <param name="attribute">The attribute containing the value to get.</param>
723 /// <param name="emptyRule">A rule for the contents of the value. If the contents do not follow the rule, an error is thrown.</param>
724 /// <returns>The attribute's value.</returns>
725 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
726 public string GetAttributeValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, EmptyRule emptyRule = EmptyRule.CanBeWhitespaceOnly)
727 {
728 return Common.GetAttributeValue(sourceLineNumbers, attribute, emptyRule);
729 }
730
731 /// <summary>
732 /// Get a valid code page by web name or number from a string attribute.
733 /// </summary>
734 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
735 /// <param name="attribute">The attribute containing the value to get.</param>
736 /// <returns>A valid code page integer value.</returns>
737 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
738 public int GetAttributeCodePageValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
739 {
740 if (null == attribute)
741 {
742 throw new ArgumentNullException("attribute");
743 }
744
745 string value = this.GetAttributeValue(sourceLineNumbers, attribute);
746
747 try
748 {
749 int codePage = Common.GetValidCodePage(value);
750 return codePage;
751 }
752 catch (NotSupportedException)
753 {
754 this.OnMessage(WixErrors.IllegalCodepageAttribute(sourceLineNumbers, value, attribute.Parent.Name.LocalName, attribute.Name.LocalName));
755 }
756
757 return CompilerConstants.IllegalInteger;
758 }
759
760 /// <summary>
761 /// Get a valid code page by web name or number from a string attribute.
762 /// </summary>
763 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
764 /// <param name="attribute">The attribute containing the value to get.</param>
765 /// <param name="onlyAscii">Whether to allow Unicode (UCS) or UTF code pages.</param>
766 /// <returns>A valid code page integer value or variable expression.</returns>
767 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
768 public string GetAttributeLocalizableCodePageValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool onlyAnsi = false)
769 {
770 if (null == attribute)
771 {
772 throw new ArgumentNullException("attribute");
773 }
774
775 string value = this.GetAttributeValue(sourceLineNumbers, attribute);
776
777 // allow for localization of code page names and values
778 if (IsValidLocIdentifier(value))
779 {
780 return value;
781 }
782
783 try
784 {
785 int codePage = Common.GetValidCodePage(value, false, onlyAnsi, sourceLineNumbers);
786 return codePage.ToString(CultureInfo.InvariantCulture);
787 }
788 catch (NotSupportedException)
789 {
790 // not a valid windows code page
791 this.OnMessage(WixErrors.IllegalCodepageAttribute(sourceLineNumbers, value, attribute.Parent.Name.LocalName, attribute.Name.LocalName));
792 }
793 catch (WixException e)
794 {
795 this.OnMessage(e.Error);
796 }
797
798 return null;
799 }
800
801 /// <summary>
802 /// Get an integer attribute value and displays an error for an illegal integer value.
803 /// </summary>
804 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
805 /// <param name="attribute">The attribute containing the value to get.</param>
806 /// <param name="minimum">The minimum legal value.</param>
807 /// <param name="maximum">The maximum legal value.</param>
808 /// <returns>The attribute's integer value or a special value if an error occurred during conversion.</returns>
809 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
810 public int GetAttributeIntegerValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, int minimum, int maximum)
811 {
812 return Common.GetAttributeIntegerValue(sourceLineNumbers, attribute, minimum, maximum);
813 }
814
815 /// <summary>
816 /// Get a long integral attribute value and displays an error for an illegal long value.
817 /// </summary>
818 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
819 /// <param name="attribute">The attribute containing the value to get.</param>
820 /// <param name="minimum">The minimum legal value.</param>
821 /// <param name="maximum">The maximum legal value.</param>
822 /// <returns>The attribute's long value or a special value if an error occurred during conversion.</returns>
823 public long GetAttributeLongValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, long minimum, long maximum)
824 {
825 Debug.Assert(minimum > CompilerConstants.LongNotSet && minimum > CompilerConstants.IllegalLong, "The legal values for this attribute collide with at least one sentinel used during parsing.");
826
827 string value = this.GetAttributeValue(sourceLineNumbers, attribute);
828
829 if (0 < value.Length)
830 {
831 try
832 {
833 long longValue = Convert.ToInt64(value, CultureInfo.InvariantCulture.NumberFormat);
834
835 if (CompilerConstants.LongNotSet == longValue || CompilerConstants.IllegalLong == longValue)
836 {
837 this.OnMessage(WixErrors.IntegralValueSentinelCollision(sourceLineNumbers, longValue));
838 }
839 else if (minimum > longValue || maximum < longValue)
840 {
841 this.OnMessage(WixErrors.IntegralValueOutOfRange(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, longValue, minimum, maximum));
842 longValue = CompilerConstants.IllegalLong;
843 }
844
845 return longValue;
846 }
847 catch (FormatException)
848 {
849 this.OnMessage(WixErrors.IllegalLongValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
850 }
851 catch (OverflowException)
852 {
853 this.OnMessage(WixErrors.IllegalLongValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
854 }
855 }
856
857 return CompilerConstants.IllegalLong;
858 }
859
860 /// <summary>
861 /// Get a date time attribute value and display errors for illegal values.
862 /// </summary>
863 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
864 /// <param name="attribute">The attribute containing the value to get.</param>
865 /// <returns>Int representation of the date time.</returns>
866 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
867 public int GetAttributeDateTimeValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
868 {
869 if (null == attribute)
870 {
871 throw new ArgumentNullException("attribute");
872 }
873
874 string value = this.GetAttributeValue(sourceLineNumbers, attribute);
875
876 if (0 < value.Length)
877 {
878 try
879 {
880 DateTime date = DateTime.Parse(value, CultureInfo.InvariantCulture.DateTimeFormat);
881
882 return ((((date.Year - 1980) * 512) + (date.Month * 32 + date.Day)) * 65536) +
883 (date.Hour * 2048) + (date.Minute * 32) + (date.Second / 2);
884 }
885 catch (ArgumentOutOfRangeException)
886 {
887 this.OnMessage(WixErrors.InvalidDateTimeFormat(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
888 }
889 catch (FormatException)
890 {
891 this.OnMessage(WixErrors.InvalidDateTimeFormat(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
892 }
893 catch (OverflowException)
894 {
895 this.OnMessage(WixErrors.InvalidDateTimeFormat(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
896 }
897 }
898
899 return CompilerConstants.IllegalInteger;
900 }
901
902 /// <summary>
903 /// Get an integer attribute value or localize variable and displays an error for
904 /// an illegal value.
905 /// </summary>
906 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
907 /// <param name="attribute">The attribute containing the value to get.</param>
908 /// <param name="minimum">The minimum legal value.</param>
909 /// <param name="maximum">The maximum legal value.</param>
910 /// <returns>The attribute's integer value or localize variable as a string or a special value if an error occurred during conversion.</returns>
911 public string GetAttributeLocalizableIntegerValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, int minimum, int maximum)
912 {
913 if (null == attribute)
914 {
915 throw new ArgumentNullException("attribute");
916 }
917
918 Debug.Assert(minimum > CompilerConstants.IntegerNotSet && minimum > CompilerConstants.IllegalInteger, "The legal values for this attribute collide with at least one sentinel used during parsing.");
919
920 string value = this.GetAttributeValue(sourceLineNumbers, attribute);
921
922 if (0 < value.Length)
923 {
924 if (IsValidLocIdentifier(value) || Common.IsValidBinderVariable(value))
925 {
926 return value;
927 }
928 else
929 {
930 try
931 {
932 int integer = Convert.ToInt32(value, CultureInfo.InvariantCulture.NumberFormat);
933
934 if (CompilerConstants.IntegerNotSet == integer || CompilerConstants.IllegalInteger == integer)
935 {
936 this.OnMessage(WixErrors.IntegralValueSentinelCollision(sourceLineNumbers, integer));
937 }
938 else if (minimum > integer || maximum < integer)
939 {
940 this.OnMessage(WixErrors.IntegralValueOutOfRange(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, integer, minimum, maximum));
941 integer = CompilerConstants.IllegalInteger;
942 }
943
944 return value;
945 }
946 catch (FormatException)
947 {
948 this.OnMessage(WixErrors.IllegalIntegerValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
949 }
950 catch (OverflowException)
951 {
952 this.OnMessage(WixErrors.IllegalIntegerValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
953 }
954 }
955 }
956
957 return null;
958 }
959
960 /// <summary>
961 /// Get a guid attribute value and displays an error for an illegal guid value.
962 /// </summary>
963 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
964 /// <param name="attribute">The attribute containing the value to get.</param>
965 /// <param name="generatable">Determines whether the guid can be automatically generated.</param>
966 /// <param name="canBeEmpty">If true, no error is raised on empty value. If false, an error is raised.</param>
967 /// <returns>The attribute's guid value or a special value if an error occurred.</returns>
968 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
969 [SuppressMessage("Microsoft.Performance", "CA1807:AvoidUnnecessaryStringCreation")]
970 public string GetAttributeGuidValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool generatable = false, bool canBeEmpty = false)
971 {
972 if (null == attribute)
973 {
974 throw new ArgumentNullException("attribute");
975 }
976
977 EmptyRule emptyRule = canBeEmpty ? EmptyRule.CanBeEmpty : EmptyRule.CanBeWhitespaceOnly;
978 string value = this.GetAttributeValue(sourceLineNumbers, attribute, emptyRule);
979
980 if (String.IsNullOrEmpty(value) && canBeEmpty)
981 {
982 return String.Empty;
983 }
984 else if (!String.IsNullOrEmpty(value))
985 {
986 // If the value starts and ends with braces or parenthesis, accept that and strip them off.
987 if ((value.StartsWith("{", StringComparison.Ordinal) && value.EndsWith("}", StringComparison.Ordinal))
988 || (value.StartsWith("(", StringComparison.Ordinal) && value.EndsWith(")", StringComparison.Ordinal)))
989 {
990 value = value.Substring(1, value.Length - 2);
991 }
992
993 try
994 {
995 Guid guid;
996
997 if (generatable && "*".Equals(value, StringComparison.Ordinal))
998 {
999 return value;
1000 }
1001
1002 if (CompilerCore.PutGuidHere.IsMatch(value))
1003 {
1004 this.OnMessage(WixErrors.ExampleGuid(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
1005 return CompilerConstants.IllegalGuid;
1006 }
1007 else if (value.StartsWith("!(loc", StringComparison.Ordinal) || value.StartsWith("$(loc", StringComparison.Ordinal) || value.StartsWith("!(wix", StringComparison.Ordinal))
1008 {
1009 return value;
1010 }
1011 else
1012 {
1013 guid = new Guid(value);
1014 }
1015
1016 string uppercaseGuid = guid.ToString().ToUpper(CultureInfo.InvariantCulture);
1017
1018 if (this.showPedanticMessages)
1019 {
1020 if (uppercaseGuid != value)
1021 {
1022 this.OnMessage(WixErrors.GuidContainsLowercaseLetters(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
1023 }
1024 }
1025
1026 return String.Concat("{", uppercaseGuid, "}");
1027 }
1028 catch (FormatException)
1029 {
1030 this.OnMessage(WixErrors.IllegalGuidValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
1031 }
1032 }
1033
1034 return CompilerConstants.IllegalGuid;
1035 }
1036
1037 /// <summary>
1038 /// Get an identifier attribute value and displays an error for an illegal identifier value.
1039 /// </summary>
1040 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
1041 /// <param name="attribute">The attribute containing the value to get.</param>
1042 /// <returns>The attribute's identifier value or a special value if an error occurred.</returns>
1043 public Identifier GetAttributeIdentifier(SourceLineNumber sourceLineNumbers, XAttribute attribute)
1044 {
1045 string value = Common.GetAttributeValue(sourceLineNumbers, attribute, EmptyRule.CanBeEmpty);
1046 AccessModifier access = AccessModifier.Public;
1047
1048 Match match = CompilerCore.LegalIdentifierWithAccess.Match(value);
1049 if (!match.Success)
1050 {
1051 return null;
1052 }
1053 else if (match.Groups["access"].Success)
1054 {
1055 access = (AccessModifier)Enum.Parse(typeof(AccessModifier), match.Groups["access"].Value, true);
1056 }
1057
1058 value = match.Groups["id"].Value;
1059
1060 if (Common.IsIdentifier(value) && 72 < value.Length)
1061 {
1062 this.OnMessage(WixWarnings.IdentifierTooLong(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
1063 }
1064
1065 return new Identifier(value, access);
1066 }
1067
1068 /// <summary>
1069 /// Get an identifier attribute value and displays an error for an illegal identifier value.
1070 /// </summary>
1071 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
1072 /// <param name="attribute">The attribute containing the value to get.</param>
1073 /// <returns>The attribute's identifier value or a special value if an error occurred.</returns>
1074 public string GetAttributeIdentifierValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
1075 {
1076 return Common.GetAttributeIdentifierValue(sourceLineNumbers, attribute);
1077 }
1078
1079 /// <summary>
1080 /// Gets a yes/no value and displays an error for an illegal yes/no value.
1081 /// </summary>
1082 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
1083 /// <param name="attribute">The attribute containing the value to get.</param>
1084 /// <returns>The attribute's YesNoType value.</returns>
1085 public YesNoType GetAttributeYesNoValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
1086 {
1087 string value = this.GetAttributeValue(sourceLineNumbers, attribute);
1088
1089 YesNoType result = YesNoType.IllegalValue;
1090 if (value.Equals("yes") || value.Equals("true"))
1091 {
1092 result = YesNoType.Yes;
1093 }
1094 else if (value.Equals("no") || value.Equals("false"))
1095 {
1096 result = YesNoType.No;
1097 }
1098 else
1099 {
1100 this.OnMessage(WixErrors.IllegalYesNoValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
1101 }
1102
1103 return result;
1104 }
1105
1106 /// <summary>
1107 /// Gets a yes/no/default value and displays an error for an illegal yes/no value.
1108 /// </summary>
1109 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
1110 /// <param name="attribute">The attribute containing the value to get.</param>
1111 /// <returns>The attribute's YesNoDefaultType value.</returns>
1112 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
1113 public YesNoDefaultType GetAttributeYesNoDefaultValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
1114 {
1115 string value = this.GetAttributeValue(sourceLineNumbers, attribute);
1116
1117 if (0 < value.Length)
1118 {
1119 switch (Wix.Enums.ParseYesNoDefaultType(value))
1120 {
1121 case Wix.YesNoDefaultType.@default:
1122 return YesNoDefaultType.Default;
1123 case Wix.YesNoDefaultType.no:
1124 return YesNoDefaultType.No;
1125 case Wix.YesNoDefaultType.yes:
1126 return YesNoDefaultType.Yes;
1127 case Wix.YesNoDefaultType.NotSet:
1128 // Previous code never returned 'NotSet'!
1129 break;
1130 default:
1131 this.OnMessage(WixErrors.IllegalYesNoDefaultValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
1132 break;
1133 }
1134 }
1135
1136 return YesNoDefaultType.IllegalValue;
1137 }
1138
1139 /// <summary>
1140 /// Gets a yes/no/always value and displays an error for an illegal value.
1141 /// </summary>
1142 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
1143 /// <param name="attribute">The attribute containing the value to get.</param>
1144 /// <returns>The attribute's YesNoAlwaysType value.</returns>
1145 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
1146 public YesNoAlwaysType GetAttributeYesNoAlwaysValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
1147 {
1148 string value = this.GetAttributeValue(sourceLineNumbers, attribute);
1149
1150 if (0 < value.Length)
1151 {
1152 switch (Wix.Enums.ParseYesNoAlwaysType(value))
1153 {
1154 case Wix.YesNoAlwaysType.@always:
1155 return YesNoAlwaysType.Always;
1156 case Wix.YesNoAlwaysType.no:
1157 return YesNoAlwaysType.No;
1158 case Wix.YesNoAlwaysType.yes:
1159 return YesNoAlwaysType.Yes;
1160 case Wix.YesNoAlwaysType.NotSet:
1161 // Previous code never returned 'NotSet'!
1162 break;
1163 default:
1164 this.OnMessage(WixErrors.IllegalYesNoAlwaysValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
1165 break;
1166 }
1167 }
1168
1169 return YesNoAlwaysType.IllegalValue;
1170 }
1171
1172 /// <summary>
1173 /// Gets a short filename value and displays an error for an illegal short filename value.
1174 /// </summary>
1175 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
1176 /// <param name="attribute">The attribute containing the value to get.</param>
1177 /// <param name="allowWildcards">true if wildcards are allowed in the filename.</param>
1178 /// <returns>The attribute's short filename value.</returns>
1179 public string GetAttributeShortFilename(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowWildcards)
1180 {
1181 if (null == attribute)
1182 {
1183 throw new ArgumentNullException("attribute");
1184 }
1185
1186 string value = this.GetAttributeValue(sourceLineNumbers, attribute);
1187
1188 if (0 < value.Length)
1189 {
1190 if (!this.IsValidShortFilename(value, allowWildcards) && !this.IsValidLocIdentifier(value))
1191 {
1192 this.OnMessage(WixErrors.IllegalShortFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
1193 }
1194 else if (CompilerCore.IsAmbiguousFilename(value))
1195 {
1196 this.OnMessage(WixWarnings.AmbiguousFileOrDirectoryName(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
1197 }
1198 }
1199
1200 return value;
1201 }
1202
1203 /// <summary>
1204 /// Gets a long filename value and displays an error for an illegal long filename value.
1205 /// </summary>
1206 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
1207 /// <param name="attribute">The attribute containing the value to get.</param>
1208 /// <param name="allowWildcards">true if wildcards are allowed in the filename.</param>
1209 /// <param name="allowRelative">true if relative paths are allowed in the filename.</param>
1210 /// <returns>The attribute's long filename value.</returns>
1211 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
1212 public string GetAttributeLongFilename(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowWildcards = false, bool allowRelative = false)
1213 {
1214 if (null == attribute)
1215 {
1216 throw new ArgumentNullException("attribute");
1217 }
1218
1219 string value = this.GetAttributeValue(sourceLineNumbers, attribute);
1220
1221 if (0 < value.Length)
1222 {
1223 if (!this.IsValidLongFilename(value, allowWildcards, allowRelative) && !this.IsValidLocIdentifier(value))
1224 {
1225 if (allowRelative)
1226 {
1227 this.OnMessage(WixErrors.IllegalRelativeLongFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
1228 }
1229 else
1230 {
1231 this.OnMessage(WixErrors.IllegalLongFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
1232 }
1233 }
1234 else if (allowRelative)
1235 {
1236 string normalizedPath = value.Replace('\\', '/');
1237 if (normalizedPath.StartsWith("../", StringComparison.Ordinal) || normalizedPath.Contains("/../"))
1238 {
1239 this.OnMessage(WixErrors.PayloadMustBeRelativeToCache(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
1240 }
1241 }
1242 else if (CompilerCore.IsAmbiguousFilename(value))
1243 {
1244 this.OnMessage(WixWarnings.AmbiguousFileOrDirectoryName(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
1245 }
1246 }
1247
1248 return value;
1249 }
1250
1251 /// <summary>
1252 /// Gets a version value or possibly a binder variable and displays an error for an illegal version value.
1253 /// </summary>
1254 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
1255 /// <param name="attribute">The attribute containing the value to get.</param>
1256 /// <returns>The attribute's version value.</returns>
1257 public string GetAttributeVersionValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
1258 {
1259 string value = this.GetAttributeValue(sourceLineNumbers, attribute);
1260
1261 if (!String.IsNullOrEmpty(value))
1262 {
1263 try
1264 {
1265 return new Version(value).ToString();
1266 }
1267 catch (FormatException) // illegal integer in version
1268 {
1269 // Allow versions to contain binder variables.
1270 if (Common.ContainsValidBinderVariable(value))
1271 {
1272 return value;
1273 }
1274
1275 this.OnMessage(WixErrors.IllegalVersionValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
1276 }
1277 catch (ArgumentException)
1278 {
1279 this.OnMessage(WixErrors.IllegalVersionValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
1280 }
1281 }
1282
1283 return null;
1284 }
1285
1286 /// <summary>
1287 /// Gets a RegistryRoot value and displays an error for an illegal value.
1288 /// </summary>
1289 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
1290 /// <param name="attribute">The attribute containing the value to get.</param>
1291 /// <param name="allowHkmu">Whether HKMU is considered a valid value.</param>
1292 /// <returns>The attribute's RegisitryRootType value.</returns>
1293 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
1294 public Wix.RegistryRootType GetAttributeRegistryRootValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowHkmu)
1295 {
1296 Wix.RegistryRootType registryRoot = Wix.RegistryRootType.NotSet;
1297 string value = this.GetAttributeValue(sourceLineNumbers, attribute);
1298
1299 if (0 < value.Length)
1300 {
1301 registryRoot = Wix.Enums.ParseRegistryRootType(value);
1302
1303 if (Wix.RegistryRootType.IllegalValue == registryRoot || (!allowHkmu && Wix.RegistryRootType.HKMU == registryRoot))
1304 {
1305 // TODO: Find a way to expose the valid values programatically!
1306 if (allowHkmu)
1307 {
1308 this.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value,
1309 "HKMU", "HKCR", "HKCU", "HKLM", "HKU"));
1310 }
1311 else
1312 {
1313 this.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value,
1314 "HKCR", "HKCU", "HKLM", "HKU"));
1315 }
1316 }
1317 }
1318
1319 return registryRoot;
1320 }
1321
1322 /// <summary>
1323 /// Gets a RegistryRoot as a MsiInterop.MsidbRegistryRoot value and displays an error for an illegal value.
1324 /// </summary>
1325 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
1326 /// <param name="attribute">The attribute containing the value to get.</param>
1327 /// <param name="allowHkmu">Whether HKMU is returned as -1 (true), or treated as an error (false).</param>
1328 /// <returns>The attribute's RegisitryRootType value.</returns>
1329 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
1330 public int GetAttributeMsidbRegistryRootValue(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool allowHkmu)
1331 {
1332 Wix.RegistryRootType registryRoot = this.GetAttributeRegistryRootValue(sourceLineNumbers, attribute, allowHkmu);
1333
1334 switch (registryRoot)
1335 {
1336 case Wix.RegistryRootType.NotSet:
1337 return CompilerConstants.IntegerNotSet;
1338 case Wix.RegistryRootType.HKCR:
1339 return Core.Native.MsiInterop.MsidbRegistryRootClassesRoot;
1340 case Wix.RegistryRootType.HKCU:
1341 return Core.Native.MsiInterop.MsidbRegistryRootCurrentUser;
1342 case Wix.RegistryRootType.HKLM:
1343 return Core.Native.MsiInterop.MsidbRegistryRootLocalMachine;
1344 case Wix.RegistryRootType.HKU:
1345 return Core.Native.MsiInterop.MsidbRegistryRootUsers;
1346 case Wix.RegistryRootType.HKMU:
1347 // This is gross, but there was *one* registry root parsing instance
1348 // (in Compiler.ParseRegistrySearchElement()) that did not explicitly
1349 // handle HKMU and it fell through to the default error case. The
1350 // others treated it as -1, which is what we do here.
1351 if (allowHkmu)
1352 {
1353 return -1;
1354 }
1355 break;
1356 }
1357
1358 return CompilerConstants.IntegerNotSet;
1359 }
1360
1361 /// <summary>
1362 /// Gets an InstallUninstallType value and displays an error for an illegal value.
1363 /// </summary>
1364 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
1365 /// <param name="attribute">The attribute containing the value to get.</param>
1366 /// <returns>The attribute's InstallUninstallType value.</returns>
1367 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
1368 public Wix.InstallUninstallType GetAttributeInstallUninstallValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
1369 {
1370 Wix.InstallUninstallType installUninstall = Wix.InstallUninstallType.NotSet;
1371 string value = this.GetAttributeValue(sourceLineNumbers, attribute);
1372
1373 if (0 < value.Length)
1374 {
1375 installUninstall = Wix.Enums.ParseInstallUninstallType(value);
1376
1377 if (Wix.InstallUninstallType.IllegalValue == installUninstall)
1378 {
1379 // TODO: Find a way to expose the valid values programatically!
1380 this.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value,
1381 "install", "uninstall", "both"));
1382 }
1383 }
1384
1385 return installUninstall;
1386 }
1387
1388 /// <summary>
1389 /// Gets an ExitType value and displays an error for an illegal value.
1390 /// </summary>
1391 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
1392 /// <param name="attribute">The attribute containing the value to get.</param>
1393 /// <returns>The attribute's ExitType value.</returns>
1394 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
1395 public Wix.ExitType GetAttributeExitValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
1396 {
1397 string value = this.GetAttributeValue(sourceLineNumbers, attribute);
1398
1399 Wix.ExitType result = Wix.ExitType.NotSet;
1400 if (!Enum.TryParse<Wix.ExitType>(value, out result))
1401 {
1402 result = Wix.ExitType.IllegalValue;
1403
1404 // TODO: Find a way to expose the valid values programatically!
1405 this.OnMessage(WixErrors.IllegalAttributeValue(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value,
1406 "success", "cancel", "error", "suspend"));
1407 }
1408
1409 return result;
1410 }
1411
1412 /// <summary>
1413 /// Gets a Bundle variable value and displays an error for an illegal value.
1414 /// </summary>
1415 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
1416 /// <param name="attribute">The attribute containing the value to get.</param>
1417 /// <returns>The attribute's value.</returns>
1418 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
1419 public string GetAttributeBundleVariableValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
1420 {
1421 string value = this.GetAttributeValue(sourceLineNumbers, attribute);
1422
1423 if (!String.IsNullOrEmpty(value))
1424 {
1425 if (CompilerCore.BuiltinBundleVariables.Contains(value))
1426 {
1427 string illegalValues = CompilerCore.CreateValueList(ValueListKind.Or, CompilerCore.BuiltinBundleVariables);
1428 this.OnMessage(WixErrors.IllegalAttributeValueWithIllegalList(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, illegalValues));
1429 }
1430 }
1431
1432 return value;
1433 }
1434
1435 /// <summary>
1436 /// Gets an MsiProperty name value and displays an error for an illegal value.
1437 /// </summary>
1438 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
1439 /// <param name="attribute">The attribute containing the value to get.</param>
1440 /// <returns>The attribute's value.</returns>
1441 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
1442 public string GetAttributeMsiPropertyNameValue(SourceLineNumber sourceLineNumbers, XAttribute attribute)
1443 {
1444 string value = this.GetAttributeValue(sourceLineNumbers, attribute);
1445
1446 if (0 < value.Length)
1447 {
1448 if (CompilerCore.DisallowedMsiProperties.Contains(value))
1449 {
1450 string illegalValues = CompilerCore.CreateValueList(ValueListKind.Or, CompilerCore.DisallowedMsiProperties);
1451 this.OnMessage(WixErrors.DisallowedMsiProperty(sourceLineNumbers, value, illegalValues));
1452 }
1453 }
1454
1455 return value;
1456 }
1457
1458 /// <summary>
1459 /// Checks if the string contains a property (i.e. "foo[Property]bar")
1460 /// </summary>
1461 /// <param name="possibleProperty">String to evaluate for properties.</param>
1462 /// <returns>True if a property is found in the string.</returns>
1463 public bool ContainsProperty(string possibleProperty)
1464 {
1465 return Common.ContainsProperty(possibleProperty);
1466 }
1467
1468 /// <summary>
1469 /// Generate an identifier by hashing data from the row.
1470 /// </summary>
1471 /// <param name="prefix">Three letter or less prefix for generated row identifier.</param>
1472 /// <param name="args">Information to hash.</param>
1473 /// <returns>The generated identifier.</returns>
1474 [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "System.InvalidOperationException.#ctor(System.String)")]
1475 public Identifier CreateIdentifier(string prefix, params string[] args)
1476 {
1477 string id = Common.GenerateIdentifier(prefix, args);
1478 return new Identifier(id, AccessModifier.Private);
1479 }
1480
1481 /// <summary>
1482 /// Create an identifier based on passed file name
1483 /// </summary>
1484 /// <param name="name">File name to generate identifer from</param>
1485 /// <returns></returns>
1486 public Identifier CreateIdentifierFromFilename(string filename)
1487 {
1488 string id = Common.GetIdentifierFromName(filename);
1489 return new Identifier(id, AccessModifier.Private);
1490 }
1491
1492 /// <summary>
1493 /// Attempts to use an extension to parse the attribute.
1494 /// </summary>
1495 /// <param name="element">Element containing attribute to be parsed.</param>
1496 /// <param name="attribute">Attribute to be parsed.</param>
1497 /// <param name="context">Extra information about the context in which this element is being parsed.</param>
1498 public void ParseExtensionAttribute(XElement element, XAttribute attribute, IDictionary<string, string> context = null)
1499 {
1500 // Ignore attributes defined by the W3C because we'll assume they are always right.
1501 if ((String.IsNullOrEmpty(attribute.Name.NamespaceName) && attribute.Name.LocalName.Equals("xmlns", StringComparison.Ordinal)) ||
1502 attribute.Name.NamespaceName.StartsWith(CompilerCore.W3SchemaPrefix.NamespaceName, StringComparison.Ordinal))
1503 {
1504 return;
1505 }
1506
1507 ICompilerExtension extension;
1508 if (this.TryFindExtension(attribute.Name.NamespaceName, out extension))
1509 {
1510 extension.ParseAttribute(element, attribute, context);
1511 }
1512 else
1513 {
1514 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(element);
1515 this.OnMessage(WixErrors.UnhandledExtensionAttribute(sourceLineNumbers, element.Name.LocalName, attribute.Name.LocalName, attribute.Name.NamespaceName));
1516 }
1517 }
1518
1519 /// <summary>
1520 /// Attempts to use an extension to parse the element.
1521 /// </summary>
1522 /// <param name="parentElement">Element containing element to be parsed.</param>
1523 /// <param name="element">Element to be parsed.</param>
1524 /// <param name="context">Extra information about the context in which this element is being parsed.</param>
1525 public void ParseExtensionElement(XElement parentElement, XElement element, IDictionary<string, string> context = null)
1526 {
1527 ICompilerExtension extension;
1528 if (this.TryFindExtension(element.Name.Namespace, out extension))
1529 {
1530 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(parentElement);
1531 extension.ParseElement(parentElement, element, context);
1532 }
1533 else
1534 {
1535 SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(element);
1536 this.OnMessage(WixErrors.UnhandledExtensionElement(childSourceLineNumbers, parentElement.Name.LocalName, element.Name.LocalName, element.Name.NamespaceName));
1537 }
1538 }
1539
1540 /// <summary>
1541 /// Process all children of the element looking for extensions and erroring on the unexpected.
1542 /// </summary>
1543 /// <param name="element">Element to parse children.</param>
1544 public void ParseForExtensionElements(XElement element)
1545 {
1546 foreach (XElement child in element.Elements())
1547 {
1548 if (element.Name.Namespace == child.Name.Namespace)
1549 {
1550 this.UnexpectedElement(element, child);
1551 }
1552 else
1553 {
1554 this.ParseExtensionElement(element, child);
1555 }
1556 }
1557 }
1558
1559 /// <summary>
1560 /// Attempts to use an extension to parse the element, with support for setting component keypath.
1561 /// </summary>
1562 /// <param name="parentElement">Element containing element to be parsed.</param>
1563 /// <param name="element">Element to be parsed.</param>
1564 /// <param name="contextValues">Extra information about the context in which this element is being parsed.</param>
1565 public ComponentKeyPath ParsePossibleKeyPathExtensionElement(XElement parentElement, XElement element, IDictionary<string, string> context)
1566 {
1567 ComponentKeyPath keyPath = null;
1568
1569 ICompilerExtension extension;
1570 if (this.TryFindExtension(element.Name.Namespace, out extension))
1571 {
1572 keyPath = extension.ParsePossibleKeyPathElement(parentElement, element, context);
1573 }
1574 else
1575 {
1576 SourceLineNumber childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(element);
1577 this.OnMessage(WixErrors.UnhandledExtensionElement(childSourceLineNumbers, parentElement.Name.LocalName, element.Name.LocalName, element.Name.NamespaceName));
1578 }
1579
1580 return keyPath;
1581 }
1582
1583 /// <summary>
1584 /// Displays an unexpected attribute error if the attribute is not the namespace attribute.
1585 /// </summary>
1586 /// <param name="element">Element containing unexpected attribute.</param>
1587 /// <param name="attribute">The unexpected attribute.</param>
1588 public void UnexpectedAttribute(XElement element, XAttribute attribute)
1589 {
1590 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(element);
1591 Common.UnexpectedAttribute(sourceLineNumbers, attribute);
1592 }
1593
1594 /// <summary>
1595 /// Display an unexepected element error.
1596 /// </summary>
1597 /// <param name="parentElement">The parent element.</param>
1598 /// <param name="childElement">The unexpected child element.</param>
1599 public void UnexpectedElement(XElement parentElement, XElement childElement)
1600 {
1601 SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(childElement);
1602
1603 this.OnMessage(WixErrors.UnexpectedElement(sourceLineNumbers, parentElement.Name.LocalName, childElement.Name.LocalName));
1604 }
1605
1606 /// <summary>
1607 /// Sends a message to the message delegate if there is one.
1608 /// </summary>
1609 /// <param name="mea">Message event arguments.</param>
1610 public void OnMessage(MessageEventArgs e)
1611 {
1612 Messaging.Instance.OnMessage(e);
1613 }
1614
1615 /// <summary>
1616 /// Verifies that the calling assembly version is equal to or newer than the given <paramref name="requiredVersion"/>.
1617 /// </summary>
1618 /// <param name="sourceLineNumbers">Source line information about the owner element.</param>
1619 /// <param name="requiredVersion">The version required of the calling assembly.</param>
1620 internal void VerifyRequiredVersion(SourceLineNumber sourceLineNumbers, string requiredVersion)
1621 {
1622 // an null or empty string means any version will work
1623 if (!string.IsNullOrEmpty(requiredVersion))
1624 {
1625 Assembly caller = Assembly.GetCallingAssembly();
1626 AssemblyName name = caller.GetName();
1627 FileVersionInfo fv = FileVersionInfo.GetVersionInfo(caller.Location);
1628
1629 Version versionRequired = new Version(requiredVersion);
1630 Version versionCurrent = new Version(fv.FileVersion);
1631
1632 if (versionRequired > versionCurrent)
1633 {
1634 if (this.GetType().Assembly.Equals(caller))
1635 {
1636 this.OnMessage(WixErrors.InsufficientVersion(sourceLineNumbers, versionCurrent, versionRequired));
1637 }
1638 else
1639 {
1640 this.OnMessage(WixErrors.InsufficientVersion(sourceLineNumbers, versionCurrent, versionRequired, name.Name));
1641 }
1642 }
1643 }
1644 }
1645
1646 /// <summary>
1647 /// Creates a new section and makes it the active section in the core.
1648 /// </summary>
1649 /// <param name="id">Unique identifier for the section.</param>
1650 /// <param name="type">Type of section to create.</param>
1651 /// <param name="codepage">Codepage for the resulting database for this ection.</param>
1652 /// <returns>New section.</returns>
1653 internal Section CreateActiveSection(string id, SectionType type, int codepage)
1654 {
1655 this.ActiveSection = this.CreateSection(id, type, codepage);
1656
1657 this.activeSectionInlinedDirectoryIds = new HashSet<string>();
1658 this.activeSectionSimpleReferences = new HashSet<string>();
1659
1660 return this.ActiveSection;
1661 }
1662
1663 /// <summary>
1664 /// Creates a new section.
1665 /// </summary>
1666 /// <param name="id">Unique identifier for the section.</param>
1667 /// <param name="type">Type of section to create.</param>
1668 /// <param name="codepage">Codepage for the resulting database for this ection.</param>
1669 /// <returns>New section.</returns>
1670 internal Section CreateSection(string id, SectionType type, int codepage)
1671 {
1672 Section newSection = new Section(id, type, codepage);
1673 this.intermediate.AddSection(newSection);
1674
1675 return newSection;
1676 }
1677
1678 /// <summary>
1679 /// Creates a WiX complex reference in the active section.
1680 /// </summary>
1681 /// <param name="sourceLineNumbers">Source line information.</param>
1682 /// <param name="parentType">The parent type.</param>
1683 /// <param name="parentId">The parent id.</param>
1684 /// <param name="parentLanguage">The parent language.</param>
1685 /// <param name="childType">The child type.</param>
1686 /// <param name="childId">The child id.</param>
1687 /// <param name="isPrimary">Whether the child is primary.</param>
1688 internal void CreateWixComplexReferenceRow(SourceLineNumber sourceLineNumbers, ComplexReferenceParentType parentType, string parentId, string parentLanguage, ComplexReferenceChildType childType, string childId, bool isPrimary)
1689 {
1690 if (!this.EncounteredError)
1691 {
1692 WixComplexReferenceRow wixComplexReferenceRow = (WixComplexReferenceRow)this.CreateRow(sourceLineNumbers, "WixComplexReference");
1693 wixComplexReferenceRow.ParentId = parentId;
1694 wixComplexReferenceRow.ParentType = parentType;
1695 wixComplexReferenceRow.ParentLanguage = parentLanguage;
1696 wixComplexReferenceRow.ChildId = childId;
1697 wixComplexReferenceRow.ChildType = childType;
1698 wixComplexReferenceRow.IsPrimary = isPrimary;
1699 }
1700 }
1701
1702 /// <summary>
1703 /// Creates WixComplexReference and WixGroup rows in the active section.
1704 /// </summary>
1705 /// <param name="sourceLineNumbers">Source line information.</param>
1706 /// <param name="parentType">The parent type.</param>
1707 /// <param name="parentId">The parent id.</param>
1708 /// <param name="parentLanguage">The parent language.</param>
1709 /// <param name="childType">The child type.</param>
1710 /// <param name="childId">The child id.</param>
1711 /// <param name="isPrimary">Whether the child is primary.</param>
1712 public void CreateComplexReference(SourceLineNumber sourceLineNumbers, ComplexReferenceParentType parentType, string parentId, string parentLanguage, ComplexReferenceChildType childType, string childId, bool isPrimary)
1713 {
1714 this.CreateWixComplexReferenceRow(sourceLineNumbers, parentType, parentId, parentLanguage, childType, childId, isPrimary);
1715 this.CreateWixGroupRow(sourceLineNumbers, parentType, parentId, childType, childId);
1716 }
1717
1718 /// <summary>
1719 /// Creates a directory row from a name.
1720 /// </summary>
1721 /// <param name="sourceLineNumbers">Source line information.</param>
1722 /// <param name="id">Optional identifier for the new row.</param>
1723 /// <param name="parentId">Optional identifier for the parent row.</param>
1724 /// <param name="name">Long name of the directory.</param>
1725 /// <param name="shortName">Optional short name of the directory.</param>
1726 /// <param name="sourceName">Optional source name for the directory.</param>
1727 /// <param name="shortSourceName">Optional short source name for the directory.</param>
1728 /// <returns>Identifier for the newly created row.</returns>
1729 internal Identifier CreateDirectoryRow(SourceLineNumber sourceLineNumbers, Identifier id, string parentId, string name, string shortName = null, string sourceName = null, string shortSourceName = null)
1730 {
1731 string defaultDir = null;
1732
1733 if (name.Equals("SourceDir") || this.IsValidShortFilename(name, false))
1734 {
1735 defaultDir = name;
1736 }
1737 else
1738 {
1739 if (String.IsNullOrEmpty(shortName))
1740 {
1741 shortName = this.CreateShortName(name, false, false, "Directory", parentId);
1742 }
1743
1744 defaultDir = String.Concat(shortName, "|", name);
1745 }
1746
1747 if (!String.IsNullOrEmpty(sourceName))
1748 {
1749 if (this.IsValidShortFilename(sourceName, false))
1750 {
1751 defaultDir = String.Concat(defaultDir, ":", sourceName);
1752 }
1753 else
1754 {
1755 if (String.IsNullOrEmpty(shortSourceName))
1756 {
1757 shortSourceName = this.CreateShortName(sourceName, false, false, "Directory", parentId);
1758 }
1759
1760 defaultDir = String.Concat(defaultDir, ":", shortSourceName, "|", sourceName);
1761 }
1762 }
1763
1764 // For anonymous directories, create the identifier. If this identifier already exists in the
1765 // active section, bail so we don't add duplicate anonymous directory rows (which are legal
1766 // but bloat the intermediate and ultimately make the linker do "busy work").
1767 if (null == id)
1768 {
1769 id = this.CreateIdentifier("dir", parentId, name, shortName, sourceName, shortSourceName);
1770
1771 if (!this.activeSectionInlinedDirectoryIds.Add(id.Id))
1772 {
1773 return id;
1774 }
1775 }
1776
1777 Row row = this.CreateRow(sourceLineNumbers, "Directory", id);
1778 row[1] = parentId;
1779 row[2] = defaultDir;
1780 return id;
1781 }
1782
1783 /// <summary>
1784 /// Gets the attribute value as inline directory syntax.
1785 /// </summary>
1786 /// <param name="sourceLineNumbers">Source line information.</param>
1787 /// <param name="attribute">Attribute containing the value to get.</param>
1788 /// <param name="resultUsedToCreateReference">Flag indicates whether the inline directory syntax should be processed to create a directory row or to create a directory reference.</param>
1789 /// <returns>Inline directory syntax split into array of strings or null if the syntax did not parse.</returns>
1790 internal string[] GetAttributeInlineDirectorySyntax(SourceLineNumber sourceLineNumbers, XAttribute attribute, bool resultUsedToCreateReference = false)
1791 {
1792 string[] result = null;
1793 string value = this.GetAttributeValue(sourceLineNumbers, attribute);
1794
1795 if (!String.IsNullOrEmpty(value))
1796 {
1797 int pathStartsAt = 0;
1798 result = value.Split(new char[] { '\\' }, StringSplitOptions.RemoveEmptyEntries);
1799 if (result[0].EndsWith(":", StringComparison.Ordinal))
1800 {
1801 string id = result[0].TrimEnd(':');
1802 if (1 == result.Length)
1803 {
1804 this.OnMessage(WixErrors.InlineDirectorySyntaxRequiresPath(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, id));
1805 return null;
1806 }
1807 else if (!this.IsValidIdentifier(id))
1808 {
1809 this.OnMessage(WixErrors.IllegalIdentifier(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, id));
1810 return null;
1811 }
1812
1813 pathStartsAt = 1;
1814 }
1815 else if (resultUsedToCreateReference && 1 == result.Length)
1816 {
1817 if (value.EndsWith("\\"))
1818 {
1819 if (!this.IsValidLongFilename(result[0]))
1820 {
1821 this.OnMessage(WixErrors.IllegalLongFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, result[0]));
1822 return null;
1823 }
1824 }
1825 else if (!this.IsValidIdentifier(result[0]))
1826 {
1827 this.OnMessage(WixErrors.IllegalIdentifier(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, result[0]));
1828 return null;
1829 }
1830
1831 return result; // return early to avoid additional checks below.
1832 }
1833
1834 // Check each part of the relative path to ensure that it is a valid directory name.
1835 for (int i = pathStartsAt; i < result.Length; ++i)
1836 {
1837 if (!this.IsValidLongFilename(result[i]))
1838 {
1839 this.OnMessage(WixErrors.IllegalLongFilename(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value, result[i]));
1840 return null;
1841 }
1842 }
1843
1844 if (1 < result.Length && !value.EndsWith("\\"))
1845 {
1846 this.OnMessage(WixWarnings.BackslashTerminateInlineDirectorySyntax(sourceLineNumbers, attribute.Parent.Name.LocalName, attribute.Name.LocalName, value));
1847 }
1848 }
1849
1850 return result;
1851 }
1852
1853 /// <summary>
1854 /// Finds a compiler extension by namespace URI.
1855 /// </summary>
1856 /// <param name="ns">Namespace the extension supports.</param>
1857 /// <returns>True if found compiler extension or false if nothing matches namespace URI.</returns>
1858 private bool TryFindExtension(XNamespace ns, out ICompilerExtension extension)
1859 {
1860 return this.extensions.TryGetValue(ns, out extension);
1861 }
1862
1863 private static string CreateValueList(ValueListKind kind, IEnumerable<string> values)
1864 {
1865 // Ideally, we could denote the list kind (and the list itself) directly in the
1866 // message XML, and detect and expand in the MessageHandler.GenerateMessageString()
1867 // method. Doing so would make vararg-style messages much easier, but impacts
1868 // every single message we format. For now, callers just have to know when a
1869 // message takes a list of values in a single string argument, the caller will
1870 // have to do the expansion themselves. (And, unfortunately, hard-code the knowledge
1871 // that the list is an 'and' or 'or' list.)
1872
1873 // For a localizable solution, we need to be able to get the list format string
1874 // from resources. We aren't currently localized right now, so the values are
1875 // just hard-coded.
1876 const string valueFormat = "'{0}'";
1877 const string valueSeparator = ", ";
1878 string terminalTerm = String.Empty;
1879
1880 switch (kind)
1881 {
1882 case ValueListKind.None:
1883 terminalTerm = "";
1884 break;
1885 case ValueListKind.And:
1886 terminalTerm = "and ";
1887 break;
1888 case ValueListKind.Or:
1889 terminalTerm = "or ";
1890 break;
1891 }
1892
1893 StringBuilder list = new StringBuilder();
1894
1895 // This weird construction helps us determine when we're adding the last value
1896 // to the list. Instead of adding them as we encounter them, we cache the current
1897 // value and append the *previous* one.
1898 string previousValue = null;
1899 bool haveValues = false;
1900 foreach (string value in values)
1901 {
1902 if (null != previousValue)
1903 {
1904 if (haveValues)
1905 {
1906 list.Append(valueSeparator);
1907 }
1908 list.AppendFormat(valueFormat, previousValue);
1909 haveValues = true;
1910 }
1911
1912 previousValue = value;
1913 }
1914
1915 // If we have no previous value, that means that the list contained no values, and
1916 // something has gone very wrong.
1917 Debug.Assert(null != previousValue);
1918 if (null != previousValue)
1919 {
1920 if (haveValues)
1921 {
1922 list.Append(valueSeparator);
1923 list.Append(terminalTerm);
1924 }
1925 list.AppendFormat(valueFormat, previousValue);
1926 haveValues = true;
1927 }
1928
1929 return list.ToString();
1930 }
1931 }
1932}
diff --git a/src/WixToolset.Core/Converter.cs b/src/WixToolset.Core/Converter.cs
new file mode 100644
index 00000000..6ae2f984
--- /dev/null
+++ b/src/WixToolset.Core/Converter.cs
@@ -0,0 +1,614 @@
1// Copyright (c) .NET Foundation and 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.Collections.Generic;
7 using System.Globalization;
8 using System.IO;
9 using System.Linq;
10 using System.Xml;
11 using System.Xml.Linq;
12 using WixToolset.Data;
13
14 /// <summary>
15 /// WiX source code converter.
16 /// </summary>
17 public class Converter
18 {
19 private const string XDocumentNewLine = "\n"; // XDocument normlizes "\r\n" to just "\n".
20 private static readonly XNamespace WixNamespace = "http://wixtoolset.org/schemas/v4/wxs";
21
22 private static readonly XName FileElementName = WixNamespace + "File";
23 private static readonly XName ExePackageElementName = WixNamespace + "ExePackage";
24 private static readonly XName MsiPackageElementName = WixNamespace + "MsiPackage";
25 private static readonly XName MspPackageElementName = WixNamespace + "MspPackage";
26 private static readonly XName MsuPackageElementName = WixNamespace + "MsuPackage";
27 private static readonly XName PayloadElementName = WixNamespace + "Payload";
28 private static readonly XName WixElementWithoutNamespaceName = XNamespace.None + "Wix";
29
30 private static readonly Dictionary<string, XNamespace> OldToNewNamespaceMapping = new Dictionary<string, XNamespace>()
31 {
32 { "http://schemas.microsoft.com/wix/BalExtension", "http://wixtoolset.org/schemas/v4/wxs/bal" },
33 { "http://schemas.microsoft.com/wix/ComPlusExtension", "http://wixtoolset.org/schemas/v4/wxs/complus" },
34 { "http://schemas.microsoft.com/wix/DependencyExtension", "http://wixtoolset.org/schemas/v4/wxs/dependency" },
35 { "http://schemas.microsoft.com/wix/DifxAppExtension", "http://wixtoolset.org/schemas/v4/wxs/difxapp" },
36 { "http://schemas.microsoft.com/wix/FirewallExtension", "http://wixtoolset.org/schemas/v4/wxs/firewall" },
37 { "http://schemas.microsoft.com/wix/GamingExtension", "http://wixtoolset.org/schemas/v4/wxs/gaming" },
38 { "http://schemas.microsoft.com/wix/IIsExtension", "http://wixtoolset.org/schemas/v4/wxs/iis" },
39 { "http://schemas.microsoft.com/wix/MsmqExtension", "http://wixtoolset.org/schemas/v4/wxs/msmq" },
40 { "http://schemas.microsoft.com/wix/NetFxExtension", "http://wixtoolset.org/schemas/v4/wxs/netfx" },
41 { "http://schemas.microsoft.com/wix/PSExtension", "http://wixtoolset.org/schemas/v4/wxs/powershell" },
42 { "http://schemas.microsoft.com/wix/SqlExtension", "http://wixtoolset.org/schemas/v4/wxs/sql" },
43 { "http://schemas.microsoft.com/wix/TagExtension", "http://wixtoolset.org/schemas/v4/wxs/tag" },
44 { "http://schemas.microsoft.com/wix/UtilExtension", "http://wixtoolset.org/schemas/v4/wxs/util" },
45 { "http://schemas.microsoft.com/wix/VSExtension", "http://wixtoolset.org/schemas/v4/wxs/vs" },
46 { "http://wixtoolset.org/schemas/thmutil/2010", "http://wixtoolset.org/schemas/v4/thmutil" },
47 { "http://schemas.microsoft.com/wix/2009/Lux", "http://wixtoolset.org/schemas/v4/lux" },
48 { "http://schemas.microsoft.com/wix/2006/wi", "http://wixtoolset.org/schemas/v4/wxs" },
49 { "http://schemas.microsoft.com/wix/2006/localization", "http://wixtoolset.org/schemas/v4/wxl" },
50 { "http://schemas.microsoft.com/wix/2006/libraries", "http://wixtoolset.org/schemas/v4/wixlib" },
51 { "http://schemas.microsoft.com/wix/2006/objects", "http://wixtoolset.org/schemas/v4/wixobj" },
52 { "http://schemas.microsoft.com/wix/2006/outputs", "http://wixtoolset.org/schemas/v4/wixout" },
53 { "http://schemas.microsoft.com/wix/2007/pdbs", "http://wixtoolset.org/schemas/v4/wixpdb" },
54 { "http://schemas.microsoft.com/wix/2003/04/actions", "http://wixtoolset.org/schemas/v4/wi/actions" },
55 { "http://schemas.microsoft.com/wix/2006/tables", "http://wixtoolset.org/schemas/v4/wi/tables" },
56 { "http://schemas.microsoft.com/wix/2006/WixUnit", "http://wixtoolset.org/schemas/v4/wixunit" },
57 };
58
59 private Dictionary<XName, Action<XElement>> ConvertElementMapping;
60
61 /// <summary>
62 /// Instantiate a new Converter class.
63 /// </summary>
64 /// <param name="indentationAmount">Indentation value to use when validating leading whitespace.</param>
65 /// <param name="errorsAsWarnings">Test errors to display as warnings.</param>
66 /// <param name="ignoreErrors">Test errors to ignore.</param>
67 public Converter(int indentationAmount, IEnumerable<string> errorsAsWarnings = null, IEnumerable<string> ignoreErrors = null)
68 {
69 this.ConvertElementMapping = new Dictionary<XName, Action<XElement>>()
70 {
71 { FileElementName, this.ConvertFileElement },
72 { ExePackageElementName, this.ConvertSuppressSignatureValidation },
73 { MsiPackageElementName, this.ConvertSuppressSignatureValidation },
74 { MspPackageElementName, this.ConvertSuppressSignatureValidation },
75 { MsuPackageElementName, this.ConvertSuppressSignatureValidation },
76 { PayloadElementName, this.ConvertSuppressSignatureValidation },
77 { WixElementWithoutNamespaceName, this.ConvertWixElementWithoutNamespace },
78 };
79
80 this.IndentationAmount = indentationAmount;
81
82 this.ErrorsAsWarnings = new HashSet<ConverterTestType>(this.YieldConverterTypes(errorsAsWarnings));
83
84 this.IgnoreErrors = new HashSet<ConverterTestType>(this.YieldConverterTypes(ignoreErrors));
85 }
86
87 private int Errors { get; set; }
88
89 private HashSet<ConverterTestType> ErrorsAsWarnings { get; set; }
90
91 private HashSet<ConverterTestType> IgnoreErrors { get; set; }
92
93 private int IndentationAmount { get; set; }
94
95 private string SourceFile { get; set; }
96
97 /// <summary>
98 /// Convert a file.
99 /// </summary>
100 /// <param name="sourceFile">The file to convert.</param>
101 /// <param name="saveConvertedFile">Option to save the converted errors that are found.</param>
102 /// <returns>The number of errors found.</returns>
103 public int ConvertFile(string sourceFile, bool saveConvertedFile)
104 {
105 XDocument document;
106
107 // Set the instance info.
108 this.Errors = 0;
109 this.SourceFile = sourceFile;
110
111 try
112 {
113 document = XDocument.Load(this.SourceFile, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
114 }
115 catch (XmlException e)
116 {
117 this.OnError(ConverterTestType.XmlException, (XObject)null, "The xml is invalid. Detail: '{0}'", e.Message);
118
119 return this.Errors;
120 }
121
122 this.ConvertDocument(document);
123
124 // Fix errors if requested and necessary.
125 if (saveConvertedFile && 0 < this.Errors)
126 {
127 try
128 {
129 using (StreamWriter writer = File.CreateText(this.SourceFile))
130 {
131 document.Save(writer, SaveOptions.DisableFormatting | SaveOptions.OmitDuplicateNamespaces);
132 }
133 }
134 catch (UnauthorizedAccessException)
135 {
136 this.OnError(ConverterTestType.UnauthorizedAccessException, (XObject)null, "Could not write to file.");
137 }
138 }
139
140 return this.Errors;
141 }
142
143 /// <summary>
144 /// Convert a document.
145 /// </summary>
146 /// <param name="document">The document to convert.</param>
147 /// <returns>The number of errors found.</returns>
148 public int ConvertDocument(XDocument document)
149 {
150 XDeclaration declaration = document.Declaration;
151
152 // Convert the declaration.
153 if (null != declaration)
154 {
155 if (!String.Equals("utf-8", declaration.Encoding, StringComparison.OrdinalIgnoreCase))
156 {
157 if (this.OnError(ConverterTestType.DeclarationEncodingWrong, document.Root, "The XML declaration encoding is not properly set to 'utf-8'."))
158 {
159 declaration.Encoding = "utf-8";
160 }
161 }
162 }
163 else // missing declaration
164 {
165 if (this.OnError(ConverterTestType.DeclarationMissing, (XNode)null, "This file is missing an XML declaration on the first line."))
166 {
167 document.Declaration = new XDeclaration("1.0", "utf-8", null);
168 document.Root.AddBeforeSelf(new XText(XDocumentNewLine));
169 }
170 }
171
172 // Start converting the nodes at the top.
173 this.ConvertNode(document.Root, 0);
174
175 return this.Errors;
176 }
177
178 /// <summary>
179 /// Convert a single xml node.
180 /// </summary>
181 /// <param name="node">The node to convert.</param>
182 /// <param name="level">The depth level of the node.</param>
183 /// <returns>The converted node.</returns>
184 private void ConvertNode(XNode node, int level)
185 {
186 // Convert this node's whitespace.
187 if ((XmlNodeType.Comment == node.NodeType && 0 > ((XComment)node).Value.IndexOf(XDocumentNewLine, StringComparison.Ordinal)) ||
188 XmlNodeType.CDATA == node.NodeType || XmlNodeType.Element == node.NodeType || XmlNodeType.ProcessingInstruction == node.NodeType)
189 {
190 this.ConvertWhitespace(node, level);
191 }
192
193 // Convert this node if it is an element.
194 XElement element = node as XElement;
195
196 if (null != element)
197 {
198 this.ConvertElement(element);
199
200 // Convert all children of this element.
201 IEnumerable<XNode> children = element.Nodes().ToList();
202
203 foreach (XNode child in children)
204 {
205 this.ConvertNode(child, level + 1);
206 }
207 }
208 }
209
210 private void ConvertElement(XElement element)
211 {
212 // Gather any deprecated namespaces, then update this element tree based on those deprecations.
213 Dictionary<XNamespace, XNamespace> deprecatedToUpdatedNamespaces = new Dictionary<XNamespace, XNamespace>();
214
215 foreach (XAttribute declaration in element.Attributes().Where(a => a.IsNamespaceDeclaration))
216 {
217 XNamespace ns;
218
219 if (Converter.OldToNewNamespaceMapping.TryGetValue(declaration.Value, out ns))
220 {
221 if (this.OnError(ConverterTestType.XmlnsValueWrong, declaration, "The namespace '{0}' is out of date. It must be '{1}'.", declaration.Value, ns.NamespaceName))
222 {
223 deprecatedToUpdatedNamespaces.Add(declaration.Value, ns);
224 }
225 }
226 }
227
228 if (deprecatedToUpdatedNamespaces.Any())
229 {
230 Converter.UpdateElementsWithDeprecatedNamespaces(element.DescendantsAndSelf(), deprecatedToUpdatedNamespaces);
231 }
232
233 // Convert the node in much greater detail.
234 Action<XElement> convert;
235
236 if (this.ConvertElementMapping.TryGetValue(element.Name, out convert))
237 {
238 convert(element);
239 }
240 }
241
242 private void ConvertFileElement(XElement element)
243 {
244 if (null == element.Attribute("Id"))
245 {
246 XAttribute attribute = element.Attribute("Name");
247
248 if (null == attribute)
249 {
250 attribute = element.Attribute("Source");
251 }
252
253 if (null != attribute)
254 {
255 string name = Path.GetFileName(attribute.Value);
256
257 if (this.OnError(ConverterTestType.AssignAnonymousFileId, element, "The file id is being updated to '{0}' to ensure it remains the same as the default", name))
258 {
259 IEnumerable<XAttribute> attributes = element.Attributes().ToList();
260 element.RemoveAttributes();
261 element.Add(new XAttribute("Id", Common.GetIdentifierFromName(name)));
262 element.Add(attributes);
263 }
264 }
265 }
266 }
267
268 private void ConvertSuppressSignatureValidation(XElement element)
269 {
270 XAttribute suppressSignatureValidation = element.Attribute("SuppressSignatureValidation");
271
272 if (null != suppressSignatureValidation)
273 {
274 if (this.OnError(ConverterTestType.SuppressSignatureValidationDeprecated, element, "The chain package element contains deprecated '{0}' attribute. Use the 'EnableSignatureValidation' instead.", suppressSignatureValidation))
275 {
276 if ("no" == suppressSignatureValidation.Value)
277 {
278 element.Add(new XAttribute("EnableSignatureValidation", "yes"));
279 }
280 }
281
282 suppressSignatureValidation.Remove();
283 }
284 }
285
286 /// <summary>
287 /// Converts a Wix element.
288 /// </summary>
289 /// <param name="element">The Wix element to convert.</param>
290 /// <returns>The converted element.</returns>
291 private void ConvertWixElementWithoutNamespace(XElement element)
292 {
293 if (this.OnError(ConverterTestType.XmlnsMissing, element, "The xmlns attribute is missing. It must be present with a value of '{0}'.", WixNamespace.NamespaceName))
294 {
295 element.Name = WixNamespace.GetName(element.Name.LocalName);
296
297 element.Add(new XAttribute("xmlns", WixNamespace.NamespaceName)); // set the default namespace.
298
299 foreach (XElement elementWithoutNamespace in element.Elements().Where(e => XNamespace.None == e.Name.Namespace))
300 {
301 elementWithoutNamespace.Name = WixNamespace.GetName(elementWithoutNamespace.Name.LocalName);
302 }
303 }
304 }
305
306 /// <summary>
307 /// Convert the whitespace adjacent to a node.
308 /// </summary>
309 /// <param name="node">The node to convert.</param>
310 /// <param name="level">The depth level of the node.</param>
311 private void ConvertWhitespace(XNode node, int level)
312 {
313 // Fix the whitespace before this node.
314 XText whitespace = node.PreviousNode as XText;
315
316 if (null != whitespace)
317 {
318 if (XmlNodeType.CDATA == node.NodeType)
319 {
320 if (this.OnError(ConverterTestType.WhitespacePrecedingCDATAWrong, node, "There should be no whitespace preceding a CDATA node."))
321 {
322 whitespace.Remove();
323 }
324 }
325 else
326 {
327 if (!Converter.IsLegalWhitespace(this.IndentationAmount, level, whitespace.Value))
328 {
329 if (this.OnError(ConverterTestType.WhitespacePrecedingNodeWrong, node, "The whitespace preceding this node is incorrect."))
330 {
331 Converter.FixWhitespace(this.IndentationAmount, level, whitespace);
332 }
333 }
334 }
335 }
336
337 // Fix the whitespace after CDATA nodes.
338 XCData cdata = node as XCData;
339
340 if (null != cdata)
341 {
342 whitespace = cdata.NextNode as XText;
343
344 if (null != whitespace)
345 {
346 if (this.OnError(ConverterTestType.WhitespaceFollowingCDATAWrong, node, "There should be no whitespace following a CDATA node."))
347 {
348 whitespace.Remove();
349 }
350 }
351 }
352 else
353 {
354 // Fix the whitespace inside and after this node (except for Error which may contain just whitespace).
355 XElement element = node as XElement;
356
357 if (null != element && "Error" != element.Name.LocalName)
358 {
359 if (!element.HasElements && !element.IsEmpty && String.IsNullOrEmpty(element.Value.Trim()))
360 {
361 if (this.OnError(ConverterTestType.NotEmptyElement, element, "This should be an empty element since it contains nothing but whitespace."))
362 {
363 element.RemoveNodes();
364 }
365 }
366
367 whitespace = node.NextNode as XText;
368
369 if (null != whitespace)
370 {
371 if (!Converter.IsLegalWhitespace(this.IndentationAmount, level - 1, whitespace.Value))
372 {
373 if (this.OnError(ConverterTestType.WhitespacePrecedingEndElementWrong, whitespace, "The whitespace preceding this end element is incorrect."))
374 {
375 Converter.FixWhitespace(this.IndentationAmount, level - 1, whitespace);
376 }
377 }
378 }
379 }
380 }
381 }
382
383 private IEnumerable<ConverterTestType> YieldConverterTypes(IEnumerable<string> types)
384 {
385 if (null != types)
386 {
387 foreach (string type in types)
388 {
389 ConverterTestType itt;
390
391 if (Enum.TryParse<ConverterTestType>(type, true, out itt))
392 {
393 yield return itt;
394 }
395 else // not a known ConverterTestType
396 {
397 this.OnError(ConverterTestType.ConverterTestTypeUnknown, (XObject)null, "Unknown error type: '{0}'.", type);
398 }
399 }
400 }
401 }
402
403 private static void UpdateElementsWithDeprecatedNamespaces(IEnumerable<XElement> elements, Dictionary<XNamespace, XNamespace> deprecatedToUpdatedNamespaces)
404 {
405 foreach (XElement element in elements)
406 {
407 XNamespace ns;
408
409 if (deprecatedToUpdatedNamespaces.TryGetValue(element.Name.Namespace, out ns))
410 {
411 element.Name = ns.GetName(element.Name.LocalName);
412 }
413
414 // Remove all the attributes and add them back to with their namespace updated (as necessary).
415 IEnumerable<XAttribute> attributes = element.Attributes().ToList();
416 element.RemoveAttributes();
417
418 foreach (XAttribute attribute in attributes)
419 {
420 XAttribute convertedAttribute = attribute;
421
422 if (attribute.IsNamespaceDeclaration)
423 {
424 if (deprecatedToUpdatedNamespaces.TryGetValue(attribute.Value, out ns))
425 {
426 convertedAttribute = ("xmlns" == attribute.Name.LocalName) ? new XAttribute(attribute.Name.LocalName, ns.NamespaceName) : new XAttribute(XNamespace.Xmlns + attribute.Name.LocalName, ns.NamespaceName);
427 }
428 }
429 else if (deprecatedToUpdatedNamespaces.TryGetValue(attribute.Name.Namespace, out ns))
430 {
431 convertedAttribute = new XAttribute(ns.GetName(attribute.Name.LocalName), attribute.Value);
432 }
433
434 element.Add(convertedAttribute);
435 }
436 }
437 }
438
439 /// <summary>
440 /// Determine if the whitespace preceding a node is appropriate for its depth level.
441 /// </summary>
442 /// <param name="indentationAmount">Indentation value to use when validating leading whitespace.</param>
443 /// <param name="level">The depth level that should match this whitespace.</param>
444 /// <param name="whitespace">The whitespace to validate.</param>
445 /// <returns>true if the whitespace is legal; false otherwise.</returns>
446 private static bool IsLegalWhitespace(int indentationAmount, int level, string whitespace)
447 {
448 // strip off leading newlines; there can be an arbitrary number of these
449 while (whitespace.StartsWith(XDocumentNewLine, StringComparison.Ordinal))
450 {
451 whitespace = whitespace.Substring(XDocumentNewLine.Length);
452 }
453
454 // check the length
455 if (whitespace.Length != level * indentationAmount)
456 {
457 return false;
458 }
459
460 // check the spaces
461 foreach (char character in whitespace)
462 {
463 if (' ' != character)
464 {
465 return false;
466 }
467 }
468
469 return true;
470 }
471
472 /// <summary>
473 /// Fix the whitespace in a Whitespace node.
474 /// </summary>
475 /// <param name="indentationAmount">Indentation value to use when validating leading whitespace.</param>
476 /// <param name="level">The depth level of the desired whitespace.</param>
477 /// <param name="whitespace">The whitespace node to fix.</param>
478 private static void FixWhitespace(int indentationAmount, int level, XText whitespace)
479 {
480 int newLineCount = 0;
481
482 for (int i = 0; i + 1 < whitespace.Value.Length; ++i)
483 {
484 if (XDocumentNewLine == whitespace.Value.Substring(i, 2))
485 {
486 ++i; // skip an extra character
487 ++newLineCount;
488 }
489 }
490
491 if (0 == newLineCount)
492 {
493 newLineCount = 1;
494 }
495
496 // reset the whitespace value
497 whitespace.Value = String.Empty;
498
499 // add the correct number of newlines
500 for (int i = 0; i < newLineCount; ++i)
501 {
502 whitespace.Value = String.Concat(whitespace.Value, XDocumentNewLine);
503 }
504
505 // add the correct number of spaces based on configured indentation amount
506 whitespace.Value = String.Concat(whitespace.Value, new string(' ', level * indentationAmount));
507 }
508
509 /// <summary>
510 /// Output an error message to the console.
511 /// </summary>
512 /// <param name="converterTestType">The type of converter test.</param>
513 /// <param name="node">The node that caused the error.</param>
514 /// <param name="message">Detailed error message.</param>
515 /// <param name="args">Additional formatted string arguments.</param>
516 /// <returns>Returns true indicating that action should be taken on this error, and false if it should be ignored.</returns>
517 private bool OnError(ConverterTestType converterTestType, XObject node, string message, params object[] args)
518 {
519 if (this.IgnoreErrors.Contains(converterTestType)) // ignore the error
520 {
521 return false;
522 }
523
524 // Increase the error count.
525 this.Errors++;
526
527 SourceLineNumber sourceLine = (null == node) ? new SourceLineNumber(this.SourceFile ?? "wixcop.exe") : new SourceLineNumber(this.SourceFile, ((IXmlLineInfo)node).LineNumber);
528 bool warning = this.ErrorsAsWarnings.Contains(converterTestType);
529 string display = String.Format(CultureInfo.CurrentCulture, message, args);
530
531 WixGenericMessageEventArgs ea = new WixGenericMessageEventArgs(sourceLine, (int)converterTestType, warning ? MessageLevel.Warning : MessageLevel.Error, "{0} ({1})", display, converterTestType.ToString());
532
533 Messaging.Instance.OnMessage(ea);
534
535 return true;
536 }
537
538 /// <summary>
539 /// Converter test types. These are used to condition error messages down to warnings.
540 /// </summary>
541 private enum ConverterTestType
542 {
543 /// <summary>
544 /// Internal-only: displayed when a string cannot be converted to an ConverterTestType.
545 /// </summary>
546 ConverterTestTypeUnknown,
547
548 /// <summary>
549 /// Displayed when an XML loading exception has occurred.
550 /// </summary>
551 XmlException,
552
553 /// <summary>
554 /// Displayed when a file cannot be accessed; typically when trying to save back a fixed file.
555 /// </summary>
556 UnauthorizedAccessException,
557
558 /// <summary>
559 /// Displayed when the encoding attribute in the XML declaration is not 'UTF-8'.
560 /// </summary>
561 DeclarationEncodingWrong,
562
563 /// <summary>
564 /// Displayed when the XML declaration is missing from the source file.
565 /// </summary>
566 DeclarationMissing,
567
568 /// <summary>
569 /// Displayed when the whitespace preceding a CDATA node is wrong.
570 /// </summary>
571 WhitespacePrecedingCDATAWrong,
572
573 /// <summary>
574 /// Displayed when the whitespace preceding a node is wrong.
575 /// </summary>
576 WhitespacePrecedingNodeWrong,
577
578 /// <summary>
579 /// Displayed when an element is not empty as it should be.
580 /// </summary>
581 NotEmptyElement,
582
583 /// <summary>
584 /// Displayed when the whitespace following a CDATA node is wrong.
585 /// </summary>
586 WhitespaceFollowingCDATAWrong,
587
588 /// <summary>
589 /// Displayed when the whitespace preceding an end element is wrong.
590 /// </summary>
591 WhitespacePrecedingEndElementWrong,
592
593 /// <summary>
594 /// Displayed when the xmlns attribute is missing from the document element.
595 /// </summary>
596 XmlnsMissing,
597
598 /// <summary>
599 /// Displayed when the xmlns attribute on the document element is wrong.
600 /// </summary>
601 XmlnsValueWrong,
602
603 /// <summary>
604 /// Assign an identifier to a File element when on Id attribute is specified.
605 /// </summary>
606 AssignAnonymousFileId,
607
608 /// <summary>
609 /// SuppressSignatureValidation attribute is deprecated and replaced with EnableSignatureValidation.
610 /// </summary>
611 SuppressSignatureValidationDeprecated,
612 }
613 }
614}
diff --git a/src/WixToolset.Core/Data/messages.xml b/src/WixToolset.Core/Data/messages.xml
new file mode 100644
index 00000000..edc98147
--- /dev/null
+++ b/src/WixToolset.Core/Data/messages.xml
@@ -0,0 +1,4010 @@
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
5<Messages Namespace="WixToolset" Resources="Core.Data.Messages" xmlns="http://schemas.microsoft.com/genmsgs/2004/07/messages">
6 <Class Name="WixErrors" ContainerName="WixErrorEventArgs" BaseContainerName="MessageEventArgs" Level="Error">
7 <Message Id="UnexpectedException" Number="1" SourceLineNumbers="no">
8 <Instance>
9 {0}&#13;&#10;&#13;&#10;Exception Type: {1}&#13;&#10;&#13;&#10;Stack Trace:&#13;&#10;{2}
10 <Parameter Type="System.String" Name="message" />
11 <Parameter Type="System.String" Name="type" />
12 <Parameter Type="System.String" Name="stackTrace" />
13 </Instance>
14 </Message>
15 <Message Id="UnexpectedAttribute" Number="4">
16 <Instance>
17 The {0} element contains an unexpected attribute '{1}'.
18 <Parameter Type="System.String" Name="elementName" />
19 <Parameter Type="System.String" Name="attributeName" />
20 </Instance>
21 </Message>
22 <Message Id="UnexpectedElement" Number="5">
23 <Instance>
24 The {0} element contains an unexpected child element '{1}'.
25 <Parameter Type="System.String" Name="elementName" />
26 <Parameter Type="System.String" Name="childElementName" />
27 </Instance>
28 </Message>
29 <Message Id="IllegalEmptyAttributeValue" Number="6">
30 <Instance>
31 The {0}/@{1} attribute's value cannot be an empty string. If a value is not required, simply remove the entire attribute.
32 <Parameter Type="System.String" Name="elementName" />
33 <Parameter Type="System.String" Name="attributeName" />
34 </Instance>
35 <Instance>
36 The {0}/@{1} attribute's value cannot be an empty string. To use the default value "{2}", simply remove the entire attribute.
37 <Parameter Type="System.String" Name="elementName" />
38 <Parameter Type="System.String" Name="attributeName" />
39 <Parameter Type="System.String" Name="defaultValue" />
40 </Instance>
41 </Message>
42 <Message Id="InsufficientVersion" Number="7">
43 <Instance>
44 The current version of the toolset is {0}, but version {1} is required.
45 <Parameter Type="System.Version" Name="currentVersion" />
46 <Parameter Type="System.Version" Name="requiredVersion" />
47 </Instance>
48 <Instance>
49 The current version of the extension '{2}' is {0}, but version {1} is required.
50 <Parameter Type="System.Version" Name="currentVersion" />
51 <Parameter Type="System.Version" Name="requiredVersion" />
52 <Parameter Type="System.String" Name="extension" />
53 </Instance>
54 </Message>
55 <Message Id="IllegalIntegerValue" Number="8">
56 <Instance>
57 The {0}/@{1} attribute's value, '{2}', is not a legal integer value. Legal integer values are from -2,147,483,648 to 2,147,483,647.
58 <Parameter Type="System.String" Name="elementName" />
59 <Parameter Type="System.String" Name="attributeName" />
60 <Parameter Type="System.String" Name="value" />
61 </Instance>
62 </Message>
63 <Message Id="IllegalGuidValue" Number="9">
64 <Instance>
65 The {0}/@{1} attribute's value, '{2}', is not a legal guid value.
66 <Parameter Type="System.String" Name="elementName" />
67 <Parameter Type="System.String" Name="attributeName" />
68 <Parameter Type="System.String" Name="value" />
69 </Instance>
70 </Message>
71 <Message Id="ExpectedAttribute" Number="10">
72 <Instance>
73 The {0}/@{1} attribute was not found; it is required.
74 <Parameter Type="System.String" Name="elementName" />
75 <Parameter Type="System.String" Name="attributeName" />
76 </Instance>
77 <Instance>
78 The {0} element must have a value for exactly one of the {1} or {2} attributes.
79 <Parameter Type="System.String" Name="elementName" />
80 <Parameter Type="System.String" Name="attribute1Name" />
81 <Parameter Type="System.String" Name="attribute2Name" />
82 <Parameter Type="System.Boolean" Name="eitherOr" />
83 </Instance>
84 <Instance>
85 The {0}/@{1} attribute was not found; it is required when attribute {2} is specified.
86 <Parameter Type="System.String" Name="elementName" />
87 <Parameter Type="System.String" Name="attributeName" />
88 <Parameter Type="System.String" Name="otherAttributeName" />
89 </Instance>
90 <Instance>
91 The {0}/@{1} attribute was not found; it is required when attribute {2} has a value of '{3}'.
92 <Parameter Type="System.String" Name="elementName" />
93 <Parameter Type="System.String" Name="attributeName" />
94 <Parameter Type="System.String" Name="otherAttributeName" />
95 <Parameter Type="System.String" Name="otherAttributeValue" />
96 </Instance>
97 <Instance>
98 The {0}/@{1} attribute was not found; it is required unless the attribute {2} has a value of '{3}'.
99 <Parameter Type="System.String" Name="elementName" />
100 <Parameter Type="System.String" Name="attributeName" />
101 <Parameter Type="System.String" Name="otherAttributeName" />
102 <Parameter Type="System.String" Name="otherAttributeValue" />
103 <Parameter Type="System.Boolean" Name="otherAttributeValueUnless" />
104 </Instance>
105 </Message>
106 <Message Id="SecurePropertyNotUppercase" Number="11">
107 <Instance>
108 The {0}/@{1} attribute's value, '{2}', cannot contain lowercase characters. Since this is a secure property, it must also be a public property. This means the Property/@Id value must be completely uppercase.
109 <Parameter Type="System.String" Name="elementName" />
110 <Parameter Type="System.String" Name="attributeName" />
111 <Parameter Type="System.String" Name="propertyId" />
112 </Instance>
113 </Message>
114 <Message Id="SearchPropertyNotUppercase" Number="12">
115 <Instance>
116 The {0}/@{1} attribute's value, '{2}', cannot contain lowercase characters. Since this is a search property, it must also be a public property. This means the Property/@Id value must be completely uppercase.
117 <Parameter Type="System.String" Name="elementName" />
118 <Parameter Type="System.String" Name="attributeName" />
119 <Parameter Type="System.String" Name="value" />
120 </Instance>
121 </Message>
122 <Message Id="StreamNameTooLong" Number="13">
123 <Instance>
124 The {0}/@{1} attribute's value, '{2}', is {3} characters long. This is too long because it will be used to create a stream name. It cannot be more than than {4} characters long.
125 <Parameter Type="System.String" Name="elementName" />
126 <Parameter Type="System.String" Name="attributeName" />
127 <Parameter Type="System.String" Name="value" />
128 <Parameter Type="System.Int32" Name="length" />
129 <Parameter Type="System.Int32" Name="maximumLength" />
130 </Instance>
131 <Instance>
132 The binary value in table '{0}' will be stored with a stream name, '{1}', that is {2} characters long. This is too long because the maximum allowed length for a stream name is 62 characters long. Since the stream name is created by concatenating the table name and values of the primary key for a row (delimited by periods), this error can be resolved by shortening a value that is part of the primary key.
133 <Parameter Type="System.String" Name="tableName" />
134 <Parameter Type="System.String" Name="streamName" />
135 <Parameter Type="System.Int32" Name="streamLength" />
136 </Instance>
137 </Message>
138 <Message Id="IllegalIdentifier" Number="14">
139 <Instance>
140 The {0} element's value, '{1}', is not a legal identifier. Identifiers may contain ASCII characters A-Z, a-z, digits, underscores (_), or periods (.). Every identifier must begin with either a letter or an underscore.
141 <Parameter Type="System.String" Name="elementName" />
142 <Parameter Type="System.String" Name="value" />
143 </Instance>
144 <Instance>
145 The {0}/@{1} attribute's value is not a legal identifier. Identifiers may contain ASCII characters A-Z, a-z, digits, underscores (_), or periods (.). Every identifier must begin with either a letter or an underscore.
146 <Parameter Type="System.String" Name="elementName" />
147 <Parameter Type="System.String" Name="attributeName" />
148 <Parameter Type="System.Int32" Name="disambiguator" />
149 </Instance>
150 <Instance>
151 The {0}/@{1} attribute's value, '{2}', is not a legal identifier. Identifiers may contain ASCII characters A-Z, a-z, digits, underscores (_), or periods (.). Every identifier must begin with either a letter or an underscore.
152 <Parameter Type="System.String" Name="elementName" />
153 <Parameter Type="System.String" Name="attributeName" />
154 <Parameter Type="System.String" Name="value" />
155 </Instance>
156 <Instance>
157 The {0}/@{1} attribute's value '{2}' contains an illegal identifier '{3}'. Identifiers may contain ASCII characters A-Z, a-z, digits, underscores (_), or periods (.). Every identifier must begin with either a letter or an underscore.
158 <Parameter Type="System.String" Name="elementName" />
159 <Parameter Type="System.String" Name="attributeName" />
160 <Parameter Type="System.String" Name="value" />
161 <Parameter Type="System.String" Name="identifier" />
162 </Instance>
163 </Message>
164 <Message Id="IllegalYesNoValue" Number="15">
165 <Instance>
166 The {0}/@{1} attribute's value, '{2}', is not a legal yes/no value. The only legal values are 'no' and 'yes'.
167 <Parameter Type="System.String" Name="elementName" />
168 <Parameter Type="System.String" Name="attributeName" />
169 <Parameter Type="System.String" Name="value" />
170 </Instance>
171 </Message>
172 <Message Id="CabCreationFailed" Number="16" SourceLineNumbers="no">
173 <Instance>
174 Failed to create cab '{0}' while compressing file '{1}' with error 0x{2:X8}.
175 <Parameter Type="System.String" Name="cabName" />
176 <Parameter Type="System.String" Name="fileName" />
177 <Parameter Type="System.Int32" Name="error" />
178 </Instance>
179 <Instance>
180 Failed to create cab '{0}' with error 0x{1:X8}.
181 <Parameter Type="System.String" Name="cabName" />
182 <Parameter Type="System.Int32" Name="error" />
183 </Instance>
184 </Message>
185 <Message Id="CabExtractionFailed" Number="17" SourceLineNumbers="no">
186 <Instance>
187 Failed to extract cab '{0}' to directory '{1}'. This is most likely due to a lack of available disk space on the destination drive.
188 <Parameter Type="System.String" Name="cabName" />
189 <Parameter Type="System.String" Name="directoryName" />
190 </Instance>
191 <Instance>
192 Failed to extract cab '{0}' from merge module '{1}' to directory '{2}'. This is most likely due to a lack of available disk space on the destination drive.
193 <Parameter Type="System.String" Name="cabName" />
194 <Parameter Type="System.String" Name="mergeModulePath" />
195 <Parameter Type="System.String" Name="directoryName" />
196 </Instance>
197 </Message>
198 <Message Id="AppIdIncompatibleAdvertiseState" Number="18">
199 <Instance>
200 The {0}/@(1) attribute's value, '{2}' does not match the advertise state on its parent element: '{3}'. (Note: AppIds nested under Fragment, Module, or Product elements must be advertised.)
201 <Parameter Type="System.String" Name="elementName" />
202 <Parameter Type="System.String" Name="attributeName" />
203 <Parameter Type="System.String" Name="value" />
204 <Parameter Type="System.String" Name="parentValue" />
205 </Instance>
206 </Message>
207 <Message Id="IllegalAttributeWhenAdvertised" Number="19">
208 <Instance>
209 The {0}/@{1} attribute cannot be specified because the element is advertised.
210 <Parameter Type="System.String" Name="elementName" />
211 <Parameter Type="System.String" Name="attributeName" />
212 </Instance>
213 </Message>
214 <Message Id="ConditionExpected" Number="20">
215 <Instance>
216 The {0} element's inner text cannot be an empty string or completely whitespace. If you don't want a condition, then simply remove the entire {0} element.
217 <Parameter Type="System.String" Name="elementName" />
218 </Instance>
219 </Message>
220 <Message Id="IllegalAttributeValue" Number="21">
221 <Instance>
222 The {0}/@{1} attribute's value, '{2}', is not one of the legal options: '{3}'.
223 <Parameter Type="System.String" Name="elementName" />
224 <Parameter Type="System.String" Name="attributeName" />
225 <Parameter Type="System.String" Name="value" />
226 <Parameter Type="System.String" Name="legalValue1" />
227 </Instance>
228 <Instance>
229 The {0}/@{1} attribute's value, '{2}', is not one of the legal options: '{3}', or '{4}'.
230 <Parameter Type="System.String" Name="elementName" />
231 <Parameter Type="System.String" Name="attributeName" />
232 <Parameter Type="System.String" Name="value" />
233 <Parameter Type="System.String" Name="legalValue1" />
234 <Parameter Type="System.String" Name="legalValue2" />
235 </Instance>
236 <Instance>
237 The {0}/@{1} attribute's value, '{2}', is not one of the legal options: '{3}', '{4}', or '{5}'.
238 <Parameter Type="System.String" Name="elementName" />
239 <Parameter Type="System.String" Name="attributeName" />
240 <Parameter Type="System.String" Name="value" />
241 <Parameter Type="System.String" Name="legalValue1" />
242 <Parameter Type="System.String" Name="legalValue2" />
243 <Parameter Type="System.String" Name="legalValue3" />
244 </Instance>
245 <Instance>
246 The {0}/@{1} attribute's value, '{2}', is not one of the legal options: '{3}', '{4}', '{5}', or '{6}'.
247 <Parameter Type="System.String" Name="elementName" />
248 <Parameter Type="System.String" Name="attributeName" />
249 <Parameter Type="System.String" Name="value" />
250 <Parameter Type="System.String" Name="legalValue1" />
251 <Parameter Type="System.String" Name="legalValue2" />
252 <Parameter Type="System.String" Name="legalValue3" />
253 <Parameter Type="System.String" Name="legalValue4" />
254 </Instance>
255 <Instance>
256 The {0}/@{1} attribute's value, '{2}', is not one of the legal options: '{3}', '{4}', '{5}', '{6}', or '{7}'.
257 <Parameter Type="System.String" Name="elementName" />
258 <Parameter Type="System.String" Name="attributeName" />
259 <Parameter Type="System.String" Name="value" />
260 <Parameter Type="System.String" Name="legalValue1" />
261 <Parameter Type="System.String" Name="legalValue2" />
262 <Parameter Type="System.String" Name="legalValue3" />
263 <Parameter Type="System.String" Name="legalValue4" />
264 <Parameter Type="System.String" Name="legalValue5" />
265 </Instance>
266 <Instance>
267 The {0}/@{1} attribute's value, '{2}', is not one of the legal options: '{3}', '{4}', '{5}', '{6}', '{7}', or '{8}'.
268 <Parameter Type="System.String" Name="elementName" />
269 <Parameter Type="System.String" Name="attributeName" />
270 <Parameter Type="System.String" Name="value" />
271 <Parameter Type="System.String" Name="legalValue1" />
272 <Parameter Type="System.String" Name="legalValue2" />
273 <Parameter Type="System.String" Name="legalValue3" />
274 <Parameter Type="System.String" Name="legalValue4" />
275 <Parameter Type="System.String" Name="legalValue5" />
276 <Parameter Type="System.String" Name="legalValue6" />
277 </Instance>
278 <Instance>
279 The {0}/@{1} attribute's value, '{2}', is not one of the legal options: '{3}', '{4}', '{5}', '{6}', '{7}', '{8}', or '{9}'.
280 <Parameter Type="System.String" Name="elementName" />
281 <Parameter Type="System.String" Name="attributeName" />
282 <Parameter Type="System.String" Name="value" />
283 <Parameter Type="System.String" Name="legalValue1" />
284 <Parameter Type="System.String" Name="legalValue2" />
285 <Parameter Type="System.String" Name="legalValue3" />
286 <Parameter Type="System.String" Name="legalValue4" />
287 <Parameter Type="System.String" Name="legalValue5" />
288 <Parameter Type="System.String" Name="legalValue6" />
289 <Parameter Type="System.String" Name="legalValue7" />
290 </Instance>
291 <Instance>
292 The {0}/@{1} attribute's value, '{2}', is not one of the legal options: '{3}', '{4}', '{5}', '{6}', '{7}', '{8}', '{9}', or '{10}'.
293 <Parameter Type="System.String" Name="elementName" />
294 <Parameter Type="System.String" Name="attributeName" />
295 <Parameter Type="System.String" Name="value" />
296 <Parameter Type="System.String" Name="legalValue1" />
297 <Parameter Type="System.String" Name="legalValue2" />
298 <Parameter Type="System.String" Name="legalValue3" />
299 <Parameter Type="System.String" Name="legalValue4" />
300 <Parameter Type="System.String" Name="legalValue5" />
301 <Parameter Type="System.String" Name="legalValue6" />
302 <Parameter Type="System.String" Name="legalValue7" />
303 <Parameter Type="System.String" Name="legalValue8" />
304 </Instance>
305 <Instance>
306 The {0}/@{1} attribute's value, '{2}', is not one of the legal options: '{3}', '{4}', '{5}', '{6}', '{7}', '{8}', '{9}', '{10}', '{11}', '{12}', '{13}', '{14}', '{15}', '{16}', '{17}', '{18}', '{19}', '{20}', '{21}', '{22}', '{23}', '{24}', '{25}', '{26}', '{27}', or '{28}'.
307 <Parameter Type="System.String" Name="elementName" />
308 <Parameter Type="System.String" Name="attributeName" />
309 <Parameter Type="System.String" Name="value" />
310 <Parameter Type="System.String" Name="legalValue1" />
311 <Parameter Type="System.String" Name="legalValue2" />
312 <Parameter Type="System.String" Name="legalValue3" />
313 <Parameter Type="System.String" Name="legalValue4" />
314 <Parameter Type="System.String" Name="legalValue5" />
315 <Parameter Type="System.String" Name="legalValue6" />
316 <Parameter Type="System.String" Name="legalValue7" />
317 <Parameter Type="System.String" Name="legalValue8" />
318 <Parameter Type="System.String" Name="legalValue9" />
319 <Parameter Type="System.String" Name="legalValue10" />
320 <Parameter Type="System.String" Name="legalValue11" />
321 <Parameter Type="System.String" Name="legalValue12" />
322 <Parameter Type="System.String" Name="legalValue13" />
323 <Parameter Type="System.String" Name="legalValue14" />
324 <Parameter Type="System.String" Name="legalValue15" />
325 <Parameter Type="System.String" Name="legalValue16" />
326 <Parameter Type="System.String" Name="legalValue17" />
327 <Parameter Type="System.String" Name="legalValue18" />
328 <Parameter Type="System.String" Name="legalValue19" />
329 <Parameter Type="System.String" Name="legalValue20" />
330 <Parameter Type="System.String" Name="legalValue21" />
331 <Parameter Type="System.String" Name="legalValue22" />
332 <Parameter Type="System.String" Name="legalValue23" />
333 <Parameter Type="System.String" Name="legalValue24" />
334 <Parameter Type="System.String" Name="legalValue25" />
335 <Parameter Type="System.String" Name="legalValue26" />
336 </Instance>
337 </Message>
338 <Message Id="CustomActionMultipleSources" Number="22">
339 <Instance>
340 The {0}/@{1} attribute cannot coexist with a previously specified attribute on this element. The {0} element may only have one of the following source attributes specified at a time: {2}, {3}, {4}, {5}, or {6}.
341 <Parameter Type="System.String" Name="elementName" />
342 <Parameter Type="System.String" Name="attributeName" />
343 <Parameter Type="System.String" Name="attributeName1" />
344 <Parameter Type="System.String" Name="attributeName2" />
345 <Parameter Type="System.String" Name="attributeName3" />
346 <Parameter Type="System.String" Name="attributeName4" />
347 <Parameter Type="System.String" Name="attributeName5" />
348 </Instance>
349 </Message>
350 <Message Id="CustomActionMultipleTargets" Number="23">
351 <Instance>
352 The {0}/@{1} attribute cannot coexist with a previously specified attribute on this element. The {0} element may only have one of the following target attributes specified at a time: {2}, {3}, {4}, {5}, {6}, {7}, or {8}.
353 <Parameter Type="System.String" Name="elementName" />
354 <Parameter Type="System.String" Name="attributeName" />
355 <Parameter Type="System.String" Name="attributeName1" />
356 <Parameter Type="System.String" Name="attributeName2" />
357 <Parameter Type="System.String" Name="attributeName3" />
358 <Parameter Type="System.String" Name="attributeName4" />
359 <Parameter Type="System.String" Name="attributeName5" />
360 <Parameter Type="System.String" Name="attributeName6" />
361 <Parameter Type="System.String" Name="attributeName7" />
362 </Instance>
363 </Message>
364 <Message Id="CustomActionIllegalInnerText" Number="24">
365 <Instance>
366 The {0} element contains illegal inner text: '{1}'. It may not contain inner text unless the {2} attribute is specified.
367 <Parameter Type="System.String" Name="elementName" />
368 <Parameter Type="System.String" Name="innerText" />
369 <Parameter Type="System.String" Name="attributeName" />
370 </Instance>
371 </Message>
372 <Message Id="DirectoryRootWithoutName" Number="25">
373 <Instance>
374 The {0} element requires the {1} attribute because there is no parent {0} element.
375 <Parameter Type="System.String" Name="elementName" />
376 <Parameter Type="System.String" Name="attributeName" />
377 </Instance>
378 </Message>
379 <Message Id="IllegalShortFilename" Number="26">
380 <Instance>
381 The {0}/@{1} attribute's value, '{2}', is not a valid 8.3-compliant name. Legal names contain no more than 8 non-period characters followed by an optional period and extension of no more than 3 non-period characters. Any character except for the follow may be used: \ ? | &gt; &lt; : / * " + , ; = [ ] (space).
382 <Parameter Type="System.String" Name="elementName" />
383 <Parameter Type="System.String" Name="attributeName" />
384 <Parameter Type="System.String" Name="value" />
385 </Instance>
386 </Message>
387 <Message Id="IllegalLongFilename" Number="27">
388 <Instance>
389 The {0}/@{1} attribute's value, '{2}', is not a valid filename because it contains illegal characters. Legal filenames contain no more than 260 characters and must contain at least one non-period character. Any character except for the follow may be used: \ ? | &gt; &lt; : / * ".
390 <Parameter Type="System.String" Name="elementName" />
391 <Parameter Type="System.String" Name="attributeName" />
392 <Parameter Type="System.String" Name="value" />
393 </Instance>
394 <Instance>
395 The {0}/@{1} attribute's value '{2}' contains a invalid filename '{3}'. Legal filenames contain no more than 260 characters and must contain at least one non-period character. Any character except for the follow may be used: \ ? | &gt; &lt; : / * ".
396 <Parameter Type="System.String" Name="elementName" />
397 <Parameter Type="System.String" Name="attributeName" />
398 <Parameter Type="System.String" Name="value" />
399 <Parameter Type="System.String" Name="filename" />
400 </Instance>
401 </Message>
402 <Message Id="TableNameTooLong" Number="28">
403 <Instance>
404 The {0}/@{1} attribute's value, '{2}', is too long for a table name. It cannot be more than than 31 characters long.
405 <Parameter Type="System.String" Name="elementName" />
406 <Parameter Type="System.String" Name="attributeName" />
407 <Parameter Type="System.String" Name="value" />
408 </Instance>
409 </Message>
410 <Message Id="FeatureConfigurableDirectoryNotUppercase" Number="29">
411 <Instance>
412 The {0}/@{1} attribute's value, '{2}', contains lowercase characters. Since this directory is user-configurable, it needs to be a public property. This means the value must be completely uppercase.
413 <Parameter Type="System.String" Name="elementName" />
414 <Parameter Type="System.String" Name="attributeName" />
415 <Parameter Type="System.String" Name="value" />
416 </Instance>
417 </Message>
418 <Message Id="FeatureCannotFavorAndDisallowAdvertise" Number="30">
419 <Instance>
420 The {0}/@{1} attribute's value, '{2}', cannot coexist with the {3} attribute's value of '{4}'. These options would ask the installer to disallow the advertised state for this feature while at the same time favoring it.
421 <Parameter Type="System.String" Name="elementName" />
422 <Parameter Type="System.String" Name="attributeName" />
423 <Parameter Type="System.String" Name="value" />
424 <Parameter Type="System.String" Name="otherAttributeName" />
425 <Parameter Type="System.String" Name="otherValue" />
426 </Instance>
427 </Message>
428 <Message Id="FeatureCannotFollowParentAndFavorLocalOrSource" Number="31">
429 <Instance>
430 The {0}/@{1} attribute cannot be specified if the {2} attribute's value is '{3}'. These options would ask the installer to force this feature to follow the parent installation state and simultaneously favor a particular installation state just for this feature.
431 <Parameter Type="System.String" Name="elementName" />
432 <Parameter Type="System.String" Name="attributeName" />
433 <Parameter Type="System.String" Name="otherAttributeName" />
434 <Parameter Type="System.String" Name="otherValue" />
435 </Instance>
436 </Message>
437 <Message Id="MediaEmbeddedCabinetNameTooLong" Number="32">
438 <Instance>
439 The {0}/@{1} attribute's value, '{2}', is {3} characters long. The name is too long for an embedded cabinet. It cannot be more than than 62 characters long.
440 <Parameter Type="System.String" Name="elementName" />
441 <Parameter Type="System.String" Name="attributeName" />
442 <Parameter Type="System.String" Name="value" />
443 <Parameter Type="System.Int32" Name="length" />
444 </Instance>
445 </Message>
446 <Message Id="RegistrySubElementCannotBeRemoved" Number="33">
447 <Instance>
448 The {0}/{1} element cannot be specified if the {2} attribute's value is '{3}' or '{4}'.
449 <Parameter Type="System.String" Name="registryElementName" />
450 <Parameter Type="System.String" Name="registryValueElementName" />
451 <Parameter Type="System.String" Name="actionAttributeName" />
452 <Parameter Type="System.String" Name="removeValue" />
453 <Parameter Type="System.String" Name="removeKeyOnInstallValue" />
454 </Instance>
455 </Message>
456 <Message Id="RegistryMultipleValuesWithoutMultiString" Number="34">
457 <Instance>
458 The {0}/@{1} attribute and a {0}/{2} element cannot both be specified. Only one may be specified if the {3} attribute's value is not 'multiString'.
459 <Parameter Type="System.String" Name="registryElementName" />
460 <Parameter Type="System.String" Name="valueAttributeName" />
461 <Parameter Type="System.String" Name="registryValueElementName" />
462 <Parameter Type="System.String" Name="typeAttributeName" />
463 </Instance>
464 </Message>
465 <Message Id="IllegalAttributeWithOtherAttribute" Number="35">
466 <Instance>
467 The {0}/@{1} attribute cannot be specified when attribute {2} is present.
468 <Parameter Type="System.String" Name="elementName" />
469 <Parameter Type="System.String" Name="attributeName" />
470 <Parameter Type="System.String" Name="otherAttributeName" />
471 </Instance>
472 <Instance>
473 The {0}/@{1} attribute cannot be specified when attribute {2} is present with value '{3}'.
474 <Parameter Type="System.String" Name="elementName" />
475 <Parameter Type="System.String" Name="attributeName" />
476 <Parameter Type="System.String" Name="otherAttributeName" />
477 <Parameter Type="System.String" Name="otherAttributeValue" />
478 </Instance>
479 </Message>
480 <Message Id="IllegalAttributeWithOtherAttributes" Number="36">
481 <Instance>
482 The {0}/@{1} attribute cannot be specified when attribute {2} or {3} is also present.
483 <Parameter Type="System.String" Name="elementName" />
484 <Parameter Type="System.String" Name="attributeName" />
485 <Parameter Type="System.String" Name="otherAttributeName1" />
486 <Parameter Type="System.String" Name="otherAttributeName2" />
487 </Instance>
488 <Instance>
489 The {0}/@{1} attribute cannot be specified when attribute {2}, {3}, or {4} is also present.
490 <Parameter Type="System.String" Name="elementName" />
491 <Parameter Type="System.String" Name="attributeName" />
492 <Parameter Type="System.String" Name="otherAttributeName1" />
493 <Parameter Type="System.String" Name="otherAttributeName2" />
494 <Parameter Type="System.String" Name="otherAttributeName3" />
495 </Instance>
496 <Instance>
497 The {0}/@{1} attribute cannot be specified when attribute {2}, {3}, {4}, or {5} is also present.
498 <Parameter Type="System.String" Name="elementName" />
499 <Parameter Type="System.String" Name="attributeName" />
500 <Parameter Type="System.String" Name="otherAttributeName1" />
501 <Parameter Type="System.String" Name="otherAttributeName2" />
502 <Parameter Type="System.String" Name="otherAttributeName3" />
503 <Parameter Type="System.String" Name="otherAttributeName4" />
504 </Instance>
505 </Message>
506 <Message Id="IllegalAttributeWithoutOtherAttributes" Number="37">
507 <Instance>
508 The {0}/@{1} attribute can only be specified with the following attribute {2} present.
509 <Parameter Type="System.String" Name="elementName" />
510 <Parameter Type="System.String" Name="attributeName" />
511 <Parameter Type="System.String" Name="otherAttributeName" />
512 </Instance>
513 <Instance>
514 The {0}/@{1} attribute can only be specified with one of the following attributes: {2} or {3} present.
515 <Parameter Type="System.String" Name="elementName" />
516 <Parameter Type="System.String" Name="attributeName" />
517 <Parameter Type="System.String" Name="otherAttributeName1" />
518 <Parameter Type="System.String" Name="otherAttributeName2" />
519 </Instance>
520 <Instance>
521 The {0}/@{1} attribute can only be specified with one of the following attributes: {2} or {3} present with value '{4}'.
522 <Parameter Type="System.String" Name="elementName" />
523 <Parameter Type="System.String" Name="attributeName" />
524 <Parameter Type="System.String" Name="otherAttributeName1" />
525 <Parameter Type="System.String" Name="otherAttributeName2" />
526 <Parameter Type="System.String" Name="otherAttributeValue" />
527 <Parameter Type="System.Boolean" Name="uniquifier" />
528 </Instance>
529 <Instance>
530 The {0}/@{1} attribute can only be specified with one of the following attributes: {2}, {3}, or {4} present.
531 <Parameter Type="System.String" Name="elementName" />
532 <Parameter Type="System.String" Name="attributeName" />
533 <Parameter Type="System.String" Name="otherAttributeName1" />
534 <Parameter Type="System.String" Name="otherAttributeName2" />
535 <Parameter Type="System.String" Name="otherAttributeName3" />
536 </Instance>
537 <Instance>
538 The {0}/@{1} attribute can only be specified with one of the following attributes: {2}, {3}, {4}, or {5} present.
539 <Parameter Type="System.String" Name="elementName" />
540 <Parameter Type="System.String" Name="attributeName" />
541 <Parameter Type="System.String" Name="otherAttributeName1" />
542 <Parameter Type="System.String" Name="otherAttributeName2" />
543 <Parameter Type="System.String" Name="otherAttributeName3" />
544 <Parameter Type="System.String" Name="otherAttributeName4" />
545 </Instance>
546 </Message>
547 <Message Id="IllegalAttributeValueWithoutOtherAttribute" Number="38">
548 <Instance>
549 The {0}/@{1} attribute's value, '{2}', can only be specified with attribute {3} present with value '{4}'.
550 <Parameter Type="System.String" Name="elementName" />
551 <Parameter Type="System.String" Name="attributeName" />
552 <Parameter Type="System.String" Name="attributeValue" />
553 <Parameter Type="System.String" Name="otherAttributeName" />
554 <Parameter Type="System.String" Name="otherAttributeValue" />
555 </Instance>
556 <Instance>
557 The {0}/@{1} attribute's value, '{2}', cannot be specified without attribute {3} present.
558 <Parameter Type="System.String" Name="elementName" />
559 <Parameter Type="System.String" Name="attributeName" />
560 <Parameter Type="System.String" Name="attributeValue" />
561 <Parameter Type="System.String" Name="otherAttributeName" />
562 </Instance>
563 </Message>
564 <Message Id="IntegralValueSentinelCollision" Number="39">
565 <Instance>
566 The integer value {0} collides with a sentinel value in the compiler code.
567 <Parameter Type="System.Int32" Name="value" />
568 </Instance>
569 <Instance>
570 The long integral value {0} collides with a sentinel value in the compiler code.
571 <Parameter Type="System.Int64" Name="value" />
572 </Instance>
573 </Message>
574 <Message Id="ExampleGuid" Number="40">
575 <Instance>
576 The {0}/@{1} attribute's value, '{2}', is not a legal Guid value. A Guid needs to be generated and put in place of '{2}' in the source file.
577 <Parameter Type="System.String" Name="elementName" />
578 <Parameter Type="System.String" Name="attributeName" />
579 <Parameter Type="System.String" Name="value" />
580 </Instance>
581 </Message>
582 <Message Id="TooManyChildren" Number="41">
583 <Instance>
584 The {0} element contains multiple {1} child elements. There can only be one {1} child element per {0} element.
585 <Parameter Type="System.String" Name="elementName" />
586 <Parameter Type="System.String" Name="childElementName" />
587 </Instance>
588 </Message>
589 <Message Id="ComponentMultipleKeyPaths" Number="42">
590 <Instance>
591 The {0} element has multiple key paths set. The key path may only be set to '{2}' in extension elements that support it or one of the following locations: {0}/@{1}, {3}/@{1}, {4}/@{1}, or {5}/@{1}.
592 <Parameter Type="System.String" Name="elementName" />
593 <Parameter Type="System.String" Name="attributeName" />
594 <Parameter Type="System.String" Name="value" />
595 <Parameter Type="System.String" Name="fileElementName" />
596 <Parameter Type="System.String" Name="registryElementName" />
597 <Parameter Type="System.String" Name="odbcDataSourceElementName" />
598 </Instance>
599 </Message>
600 <Message Id="CabClosureFailed" Number="43" SourceLineNumbers="no">
601 <Instance>
602 Failed to close cab '{0}'.
603 <Parameter Type="System.String" Name="cabinet" />
604 </Instance>
605 <Instance>
606 Failed to close cab '{0}', error: {1}.
607 <Parameter Type="System.String" Name="cabinet" />
608 <Parameter Type="System.Int32" Name="error" />
609 </Instance>
610 </Message>
611 <Message Id="ExpectedAttributes" Number="44">
612 <Instance>
613 The {0} element's {1} or {2} attribute was not found; one of these is required.
614 <Parameter Type="System.String" Name="elementName" />
615 <Parameter Type="System.String" Name="attributeName1" />
616 <Parameter Type="System.String" Name="attributeName2" />
617 </Instance>
618 <Instance>
619 The {0} element's {1}, {2}, or {3} attribute was not found; one of these is required.
620 <Parameter Type="System.String" Name="elementName" />
621 <Parameter Type="System.String" Name="attributeName1" />
622 <Parameter Type="System.String" Name="attributeName2" />
623 <Parameter Type="System.String" Name="attributeName3" />
624 </Instance>
625 <Instance>
626 The {0} element's {1}, {2}, {3}, or {4} attribute was not found; one of these is required.
627 <Parameter Type="System.String" Name="elementName" />
628 <Parameter Type="System.String" Name="attributeName1" />
629 <Parameter Type="System.String" Name="attributeName2" />
630 <Parameter Type="System.String" Name="attributeName3" />
631 <Parameter Type="System.String" Name="attributeName4" />
632 </Instance>
633 <Instance>
634 The {0} element's {1}, {2}, {3}, {4}, or {5} attribute was not found; one of these is required.
635 <Parameter Type="System.String" Name="elementName" />
636 <Parameter Type="System.String" Name="attributeName1" />
637 <Parameter Type="System.String" Name="attributeName2" />
638 <Parameter Type="System.String" Name="attributeName3" />
639 <Parameter Type="System.String" Name="attributeName4" />
640 <Parameter Type="System.String" Name="attributeName5" />
641 </Instance>
642 <Instance>
643 The {0} element's {1}, {2}, {3}, {4}, {5}, or {6} attribute was not found; one of these is required.
644 <Parameter Type="System.String" Name="elementName" />
645 <Parameter Type="System.String" Name="attributeName1" />
646 <Parameter Type="System.String" Name="attributeName2" />
647 <Parameter Type="System.String" Name="attributeName3" />
648 <Parameter Type="System.String" Name="attributeName4" />
649 <Parameter Type="System.String" Name="attributeName5" />
650 <Parameter Type="System.String" Name="attributeName6" />
651 </Instance>
652 <Instance>
653 The {0} element's {1}, {2}, {3}, {4}, {5}, {6}, or {7} attribute was not found; one of these is required.
654 <Parameter Type="System.String" Name="elementName" />
655 <Parameter Type="System.String" Name="attributeName1" />
656 <Parameter Type="System.String" Name="attributeName2" />
657 <Parameter Type="System.String" Name="attributeName3" />
658 <Parameter Type="System.String" Name="attributeName4" />
659 <Parameter Type="System.String" Name="attributeName5" />
660 <Parameter Type="System.String" Name="attributeName6" />
661 <Parameter Type="System.String" Name="attributeName7" />
662 </Instance>
663 </Message>
664 <Message Id="ExpectedAttributesWithOtherAttribute" Number="45">
665 <Instance>
666 The {0} element's {1} or {2} attribute was not found; at least one of these attributes must be specified.
667 <Parameter Type="System.String" Name="elementName" />
668 <Parameter Type="System.String" Name="attributeName1" />
669 <Parameter Type="System.String" Name="attributeName2" />
670 </Instance>
671 <Instance>
672 The {0} element's {1} or {2} attribute was not found; one of these is required when attribute {3} is present.
673 <Parameter Type="System.String" Name="elementName" />
674 <Parameter Type="System.String" Name="attributeName1" />
675 <Parameter Type="System.String" Name="attributeName2" />
676 <Parameter Type="System.String" Name="otherAttributeName" />
677 </Instance>
678 <Instance>
679 The {0} element's {1} or {2} attribute was not found; one of these is required when attribute {3} has a value of '{4}'.
680 <Parameter Type="System.String" Name="elementName" />
681 <Parameter Type="System.String" Name="attributeName1" />
682 <Parameter Type="System.String" Name="attributeName2" />
683 <Parameter Type="System.String" Name="otherAttributeName" />
684 <Parameter Type="System.String" Name="otherAttributeValue" />
685 </Instance>
686 </Message>
687 <Message Id="ExpectedAttributesWithoutOtherAttribute" Number="46">
688 <Instance>
689 The {0} element's {1} or {2} attribute was not found; one of these is required without attribute {3} present.
690 <Parameter Type="System.String" Name="elementName" />
691 <Parameter Type="System.String" Name="attributeName1" />
692 <Parameter Type="System.String" Name="attributeName2" />
693 <Parameter Type="System.String" Name="otherAttributeName" />
694 </Instance>
695 </Message>
696 <Message Id="MissingTypeLibFile" Number="47">
697 <Instance>
698 The {0} element is non-advertised and therefore requires a parent {1} element.
699 <Parameter Type="System.String" Name="elementName" />
700 <Parameter Type="System.String" Name="fileElementName" />
701 </Instance>
702 </Message>
703 <Message Id="InvalidDocumentElement" Number="48">
704 <Instance>
705 The document element name '{0}' is invalid. A WiX {1} file must use '{2}' as the document element name.
706 <Parameter Type="System.String" Name="elementName" />
707 <Parameter Type="System.String" Name="fileType" />
708 <Parameter Type="System.String" Name="expectedElementName" />
709 </Instance>
710 </Message>
711 <Message Id="ExpectedAttributeInElementOrParent" Number="49">
712 <Instance>
713 The {0}/@{1} attribute was not found or empty; it is required, or it can be specified in the parent {2} element.
714 <Parameter Type="System.String" Name="elementName" />
715 <Parameter Type="System.String" Name="attributeName" />
716 <Parameter Type="System.String" Name="parentElementName" />
717 </Instance>
718 <Instance>
719 The {0}/@{1} attribute was not found or empty; it is required, or it can be specified in the parent {2}/@{3} attribute.
720 <Parameter Type="System.String" Name="elementName" />
721 <Parameter Type="System.String" Name="attributeName" />
722 <Parameter Type="System.String" Name="parentElementName" />
723 <Parameter Type="System.String" Name="parentAttributeName" />
724 </Instance>
725 </Message>
726 <Message Id="UnauthorizedAccess" Number="50" SourceLineNumbers="no">
727 <Instance>
728 Access to the path '{0}' is denied.
729 <Parameter Type="System.String" Name="filePath" />
730 </Instance>
731 </Message>
732 <Message Id="IllegalModuleExclusionLanguageAttributes" Number="51">
733 <Instance>Cannot set both ExcludeLanguage and ExcludeExceptLanguage attributes on a ModuleExclusion element.</Instance>
734 </Message>
735 <Message Id="NoFirstControlSpecified" Number="52">
736 <Instance>
737 The '{0}' dialog element does not have a valid tabbable control. You must either have a tabbable control that is not marked TabSkip='yes', or you must mark a control TabSkip='no'. If you have a page with no tabbable controls (a progress page, for example), you might want to set the first Text control to be TabSkip='no'.
738 <Parameter Type="System.String" Name="dialogName" />
739 </Instance>
740 </Message>
741 <Message Id="NoDataForColumn" Number="53">
742 <Instance>
743 There is no data for column '{0}' in a contained row of custom table '{1}'. A non-null value must be supplied for this column.
744 <Parameter Type="System.String" Name="columnName" />
745 <Parameter Type="System.String" Name="tableName" />
746 </Instance>
747 </Message>
748 <Message Id="ValueAndMaskMustBeSameLength" Number="54">
749 <Instance>
750 The FileTypeMask/@Value and FileTypeMask/@Mask attributes must be the same length.
751 </Instance>
752 </Message>
753 <Message Id="TooManySearchElements" Number="55">
754 <Instance>
755 Only one search element can appear under a '{0}' element.
756 <Parameter Type="System.String" Name="elementName" />
757 </Instance>
758 </Message>
759 <Message Id="IllegalAttributeExceptOnElement" Number="56">
760 <Instance>
761 The {1} attribute can only be specified on the {2} element.
762 <Parameter Type="System.String" Name="elementName" />
763 <Parameter Type="System.String" Name="attributeName" />
764 <Parameter Type="System.String" Name="expectedElementName" />
765 </Instance>
766 </Message>
767 <Message Id="SearchElementRequired" Number="57">
768 <Instance>
769 A '{0}' element must have a search element as a child.
770 <Parameter Type="System.String" Name="elementName" />
771 </Instance>
772 </Message>
773 <Message Id="MultipleIdentifiersFound" Number="58">
774 <Instance>
775 Under a '{0}' element, multiple identifiers were found: '{1}' and '{2}'. All search elements under this element must have the same id.
776 <Parameter Type="System.String" Name="elementName" />
777 <Parameter Type="System.String" Name="identifier" />
778 <Parameter Type="System.String" Name="mismatchIdentifier" />
779 </Instance>
780 </Message>
781 <Message Id="AdvertiseStateMustMatch" Number="59">
782 <Instance>
783 The advertise state of this element: '{0}', does not match the advertise state set on the parent element: '{1}'.
784 <Parameter Type="System.String" Name="advertiseState" />
785 <Parameter Type="System.String" Name="parentAdvertiseState" />
786 </Instance>
787 </Message>
788 <Message Id="DuplicateContextValue" Number="60">
789 <Instance>
790 The context value '{0}' was duplicated. Context values must be distinct.
791 <Parameter Type="System.String" Name="contextValue" />
792 </Instance>
793 </Message>
794 <Message Id="RelativePathForRegistryElement" Number="61">
795 <Instance>
796 Cannot convert RelativePath into Registry elements.
797 </Instance>
798 </Message>
799 <Message Id="IllegalAttributeWhenNested" Number="62">
800 <Instance>
801 The {0}/@{1} attribute cannot be specified when the {0} element is nested underneath a {2} element. If this {0} is a member of a ComponentGroup where ComponentGroup/@{1} is set, then the {0}/@{1} attribute should be removed.
802 <Parameter Type="System.String" Name="elementName" />
803 <Parameter Type="System.String" Name="attributeName" />
804 <Parameter Type="System.String" Name="parentElement" />
805 </Instance>
806 </Message>
807 <Message Id="ExpectedElement" Number="63">
808 <Instance>
809 A {0} element must have at least one child element of type {1}.
810 <Parameter Type="System.String" Name="elementName" />
811 <Parameter Type="System.String" Name="childName" />
812 </Instance>
813 <Instance>
814 A {0} element must have at least one child element of type {1} or {2}.
815 <Parameter Type="System.String" Name="elementName" />
816 <Parameter Type="System.String" Name="childName1" />
817 <Parameter Type="System.String" Name="childName2" />
818 </Instance>
819 <Instance>
820 A {0} element must have at least one child element of type {1}, {2}, or {3}.
821 <Parameter Type="System.String" Name="elementName" />
822 <Parameter Type="System.String" Name="childName1" />
823 <Parameter Type="System.String" Name="childName2" />
824 <Parameter Type="System.String" Name="childName3" />
825 </Instance>
826 <Instance>
827 A {0} element must have at least one child element of type {1}, {2}, {3}, or {4}.
828 <Parameter Type="System.String" Name="elementName" />
829 <Parameter Type="System.String" Name="childName1" />
830 <Parameter Type="System.String" Name="childName2" />
831 <Parameter Type="System.String" Name="childName3" />
832 <Parameter Type="System.String" Name="childName4" />
833 </Instance>
834 </Message>
835 <Message Id="RegistryRootInvalid" Number="64">
836 <Instance>
837 Registry/@Root attribute is invalid on a nested Registry element. Either remove the Root attribute or move the Registry element so it is not nested under another Registry element.
838 </Instance>
839 </Message>
840 <Message Id="IllegalYesNoDefaultValue" Number="65">
841 <Instance>
842 The {0}/@{1} attribute's value, '{2}', is not a legal yes/no/default value. The only legal values are 'default', 'no' or 'yes'.
843 <Parameter Type="System.String" Name="elementName" />
844 <Parameter Type="System.String" Name="attributeName" />
845 <Parameter Type="System.String" Name="value" />
846 </Instance>
847 </Message>
848 <Message Id="IllegalAttributeInMergeModule" Number="66">
849 <Instance>
850 The {0}/@{1} attribute cannot be specified in a merge module.
851 <Parameter Type="System.String" Name="elementName" />
852 <Parameter Type="System.String" Name="attributeName" />
853 </Instance>
854 </Message>
855 <Message Id="GenericReadNotAllowed" Number="67">
856 <Instance>Permission elements cannot have GenericRead as the only permission specified. Include at least one other permission.</Instance>
857 </Message>
858 <Message Id="IllegalAttributeWithInnerText" Number="68">
859 <Instance>
860 The {0}/@{1} attribute cannot be specified when the element has body text as well. Specify either the attribute or the body, but not both.
861 <Parameter Type="System.String" Name="elementName" />
862 <Parameter Type="System.String" Name="attributeName" />
863 </Instance>
864 </Message>
865 <Message Id="SearchElementRequiredWithAttribute" Number="69">
866 <Instance>
867 A {0} element must have a search element as a child when the {0}/@{1} attribute has the value '{2}'.
868 <Parameter Type="System.String" Name="elementName" />
869 <Parameter Type="System.String" Name="attributeName" />
870 <Parameter Type="System.String" Name="attributeValue" />
871 </Instance>
872 </Message>
873 <Message Id="CannotAuthorSpecialProperties" Number="70">
874 <Instance>
875 The {0} property was specified. Special MSI properties cannot be authored. Use the attributes on the Property element instead.
876 <Parameter Type="System.String" Name="propertyName" />
877 </Instance>
878 </Message>
879 <Message Id="NeedSequenceBeforeOrAfter" Number="72">
880 <Instance>
881 A {0} element must have a Before attribute, After attribute, or a Sequence attribute.
882 <Parameter Type="System.String" Name="elementName" />
883 </Instance>
884 </Message>
885 <Message Id="ValueNotSupported" Number="73">
886 <Instance>
887 The {0}/@{1} attribute's value, '{2}, is not supported by the Windows Installer.
888 <Parameter Type="System.String" Name="elementName" />
889 <Parameter Type="System.String" Name="attributeName" />
890 <Parameter Type="System.String" Name="attributeValue" />
891 </Instance>
892 </Message>
893 <Message Id="TabbableControlNotAllowedInBillboard" Number="74">
894 <Instance>
895 A {0} element was specified with Type='{1}' and TabSkip='no'. Tabbable controls are not allowed in Billboards.
896 <Parameter Type="System.String" Name="elementName" />
897 <Parameter Type="System.String" Name="controlType" />
898 </Instance>
899 </Message>
900 <Message Id="CheckBoxValueOnlyValidWithCheckBox" Number="75">
901 <Instance>
902 A {0} element was specified with Type='{1}' and a CheckBoxValue. Check box values can only be specified with Type='CheckBox'.
903 <Parameter Type="System.String" Name="elementName" />
904 <Parameter Type="System.String" Name="controlType" />
905 </Instance>
906 </Message>
907 <Message Id="CabFileDoesNotExist" Number="76" SourceLineNumbers="no">
908 <Instance>
909 Attempted to extract cab '{0}' from merge module '{1}' to directory '{2}'. The cab file was not found. This usually means that you have a merge module without a cabinet inside it.
910 <Parameter Type="System.String" Name="cabName" />
911 <Parameter Type="System.String" Name="mergeModulePath" />
912 <Parameter Type="System.String" Name="directoryName" />
913 </Instance>
914 </Message>
915 <Message Id="RadioButtonTypeInconsistent" Number="77">
916 <Instance>All RadioButton elements in a RadioButtonGroup must be consistent with their use of the Bitmap, Icon, and Text attributes. Ensure all of the RadioButton elements in this group have the same attribute specified.</Instance>
917 </Message>
918 <Message Id="RadioButtonBitmapAndIconDisallowed" Number="78">
919 <Instance>RadioButtonGroup elements that contain RadioButton elements with Bitmap or Icon attributes set to "yes" can only be specified under a Control element. Move your RadioButtonGroup element as a child of the appropriate Control element.</Instance>
920 </Message>
921 <Message Id="IllegalSuppressWarningId" Number="79" SourceLineNumbers="no">
922 <Instance>
923 Illegal value '{0}' for the -sw&lt;N&gt; command line option. Specify a particular warning number, like '-sw6' to suppress the warning with ID 6, or '-sw' alone to suppress all warnings.
924 <Parameter Type="System.String" Name="suppressedId" />
925 </Instance>
926 </Message>
927 <Message Id="PreprocessorIllegalForeachVariable" Number="80">
928 <Instance>
929 The variable named '{0}' is not allowed in a foreach expression.
930 <Parameter Type="System.String" Name="variableName" />
931 </Instance>
932 </Message>
933 <Message Id="PreprocessorMissingParameterPrefix" Number="81">
934 <Instance>
935 Could not find the prefix in parameter name: '{0}'.
936 <Parameter Type="System.String" Name="parameterName" />
937 </Instance>
938 </Message>
939 <Message Id="PreprocessorExtensionForParameterMissing" Number="82">
940 <Instance>
941 Could not find the preprocessor extension for parameter '{0}'. A preprocessor extension is expected because the parameter prefix, '{1}', is not one of the standard types: 'env', 'res', 'sys', or 'var'.
942 <Parameter Type="System.String" Name="parameterName" />
943 <Parameter Type="System.String" Name="parameterPrefix" />
944 </Instance>
945 </Message>
946 <Message Id="CannotFindFile" Number="83">
947 <Instance>
948 The file with id '{0}' and name '{1}' could not be found with source path: '{2}'.
949 <Parameter Type="System.String" Name="fileId" />
950 <Parameter Type="System.String" Name="fileName" />
951 <Parameter Type="System.String" Name="filePath" />
952 </Instance>
953 </Message>
954 <Message Id="BinderFileManagerMissingFile" Number="84">
955 <Instance>
956 {0}
957 <Parameter Type="System.String" Name="exceptionMessage" />
958 </Instance>
959 </Message>
960 <Message Id="InvalidFileName" Number="85">
961 <Instance>
962 Invalid file name '{0}'.
963 <Parameter Type="System.String" Name="fileName" />
964 </Instance>
965 </Message>
966 <Message Id="ReferenceLoopDetected" Number="86">
967 <Instance>
968 A circular reference of groups was detected. The infinite loop includes: {0}. Group references must form a directed acyclic graph.
969 <Parameter Type="System.String" Name="loopList" />
970 </Instance>
971 </Message>
972 <Message Id="GuidContainsLowercaseLetters" Number="87">
973 <Instance>
974 The {0}/@{1} attribute's value, '{2}', is a mixed-case guid. All letters in a guid value should be uppercase.
975 <Parameter Type="System.String" Name="elementName" />
976 <Parameter Type="System.String" Name="attributeName" />
977 <Parameter Type="System.String" Name="value" />
978 </Instance>
979 </Message>
980 <Message Id="InvalidDateTimeFormat" Number="88">
981 <Instance>
982 The {0}/@{1} attribute's value '{2}' is not a valid date/time value. A date/time value should follow the format YYYY-MM-DDTHH:mm:ss.
983 <Parameter Type="System.String" Name="elementName" />
984 <Parameter Type="System.String" Name="attributeName" />
985 <Parameter Type="System.String" Name="value" />
986 </Instance>
987 </Message>
988 <Message Id="MultipleEntrySections" Number="89">
989 <Instance>
990 Multiple entry sections '{0}' and '{1}' found. Only one entry section may be present in a single target.
991 <Parameter Type="System.String" Name="sectionName1" />
992 <Parameter Type="System.String" Name="sectionName2" />
993 </Instance>
994 </Message>
995 <Message Id="MultipleEntrySections2" Number="90">
996 <Instance>Location of entry section related to previous error.</Instance>
997 </Message>
998 <Message Id="DuplicateSymbol" Number="91">
999 <Instance>
1000 Duplicate symbol '{0}' found. This typically means that an Id is duplicated. Access modifiers (internal, protected, private) cannot prevent these conflicts. Ensure all your identifiers of a given type (File, Component, Feature) are unique.
1001 <Parameter Type="System.String" Name="symbolName" />
1002 </Instance>
1003 <Instance>
1004 Duplicate symbol '{0}' referenced by {1}. This typically means that an Id is duplicated. Ensure all your identifiers of a given type (File, Component, Feature) are unique or use an access modifier to scope the identfier.
1005 <Parameter Type="System.String" Name="symbolName" />
1006 <Parameter Type="System.String" Name="referencingSourceLineNumber" />
1007 </Instance>
1008 </Message>
1009 <Message Id="DuplicateSymbol2" Number="92">
1010 <Instance>Location of symbol related to previous error.</Instance>
1011 </Message>
1012 <Message Id="MissingEntrySection" Number="93" SourceLineNumbers="no">
1013 <Instance>
1014 Could not find entry section in provided list of intermediates. Expected section of type '{0}'.
1015 <Parameter Type="System.String" Name="sectionType" />
1016 </Instance>
1017 </Message>
1018 <Message Id="UnresolvedReference" Number="94">
1019 <Instance>
1020 The identifier '{0}' could not be found. Ensure you have typed the reference correctly and that all the necessary inputs are provided to the linker.
1021 <Parameter Type="System.String" Name="symbolName" />
1022 </Instance>
1023 <Instance>
1024 The identifier '{0}' is inaccessible due to its protection level.
1025 <Parameter Type="System.String" Name="symbolName" />
1026 <Parameter Type="WixToolset.Data.AccessModifier" Name="accessModifier" />
1027 </Instance>
1028 </Message>
1029 <Message Id="MultiplePrimaryReferences" Number="95">
1030 <Instance>
1031 Multiple primary references were found for {0} '{1}' in {2} '{3}' and {4} '{5}'.
1032 <Parameter Type="System.String" Name="crefChildType" />
1033 <Parameter Type="System.String" Name="crefChildId" />
1034 <Parameter Type="System.String" Name="crefParentType" />
1035 <Parameter Type="System.String" Name="crefParentId" />
1036 <Parameter Type="System.String" Name="conflictParentType" />
1037 <Parameter Type="System.String" Name="conflictParentId" />
1038 </Instance>
1039 </Message>
1040 <Message Id="ComponentReferencedTwice" Number="96">
1041 <Instance>
1042 Component {0} cannot be contained in a Module twice.
1043 <Parameter Type="System.String" Name="crefChildId" />
1044 </Instance>
1045 </Message>
1046 <Message Id="DuplicateModuleFileIdentifier" Number="97">
1047 <Instance>
1048 The merge module '{0}' contains a file identifier, '{1}', that is duplicated either in another merge module or in a File/@Id attribute. File identifiers must be unique. Please change one of the file identifiers to a different value.
1049 <Parameter Type="System.String" Name="moduleId" />
1050 <Parameter Type="System.String" Name="fileId" />
1051 </Instance>
1052 </Message>
1053 <Message Id="DuplicateModuleCaseInsensitiveFileIdentifier" Number="98">
1054 <Instance>
1055 The merge module '{0}' contains 2 or more file identifiers that only differ by case: '{1}' and '{2}'. The WiX toolset extracts merge module files to the file system using these identifiers. Since most file systems are not case-sensitive a collision is likely. Please contact the owner of the merge module for a fix.
1056 <Parameter Type="System.String" Name="moduleId" />
1057 <Parameter Type="System.String" Name="fileId1" />
1058 <Parameter Type="System.String" Name="fileId2" />
1059 </Instance>
1060 </Message>
1061 <Message Id="ImplicitComponentKeyPath" Number="99">
1062 <Instance>
1063 The component '{0}' does not have an explicit key path specified. If the ordering of the elements under the Component element changes, the key path will also change. To prevent accidental changes, the key path should be set to 'yes' in one of the following locations: Component/@KeyPath, File/@KeyPath, ODBCDataSource/@KeyPath, or Registry/@KeyPath.
1064 <Parameter Type="System.String" Name="componentId" />
1065 </Instance>
1066 </Message>
1067 <Message Id="DuplicateLocalizationIdentifier" Number="100">
1068 <Instance>
1069 The localization identifier '{0}' has been duplicated in multiple locations. Please resolve the conflict.
1070 <Parameter Type="System.String" Name="localizationId" />
1071 </Instance>
1072 </Message>
1073 <Message Id="LocalizationVariableUnknown" Number="102">
1074 <Instance>
1075 The localization variable !(loc.{0}) is unknown. Please ensure the variable is defined.
1076 <Parameter Type="System.String" Name="variableId" />
1077 </Instance>
1078 </Message>
1079 <Message Id="FileNotFound" Number="103">
1080 <Instance>
1081 The system cannot find the file '{0}'.
1082 <Parameter Type="System.String" Name="file" />
1083 </Instance>
1084 <Instance>
1085 The system cannot find the file '{0}' with type '{1}'.
1086 <Parameter Type="System.String" Name="file" />
1087 <Parameter Type="System.String" Name="fileType" />
1088 </Instance>
1089 </Message>
1090 <Message Id="InvalidXml" Number="104">
1091 <Instance>
1092 Not a valid {0} file; detail: {1}
1093 <Parameter Type="System.String" Name="fileType" />
1094 <Parameter Type="System.String" Name="detail" />
1095 </Instance>
1096 </Message>
1097 <Message Id="ProgIdNestedTooDeep" Number="105">
1098 <Instance>ProgId elements may not be nested more than 1 level deep.</Instance>
1099 </Message>
1100 <Message Id="CanNotHaveTwoParents" Number="106">
1101 <Instance>
1102 The DirectorySearchRef {0} can not have a Parent attribute {1} and also be nested under parent element {2}
1103 <Parameter Type="System.String" Name="directorySearch" />
1104 <Parameter Type="System.String" Name="parentAttribute" />
1105 <Parameter Type="System.String" Name="parentElement" />
1106 </Instance>
1107 </Message>
1108 <Message Id="SchemaValidationFailed" Number="107">
1109 <Instance>
1110 Schema validation failed with the following error at line {1}, column {2}: {0}
1111 <Parameter Type="System.String" Name="validationError" />
1112 <Parameter Type="System.Int32" Name="lineNumber" />
1113 <Parameter Type="System.Int32" Name="linePosition" />
1114 </Instance>
1115 </Message>
1116 <Message Id="IllegalVersionValue" Number="108">
1117 <Instance>
1118 The {0}/@{1} attribute's value, '{2}', is not a valid version. Legal version values should look like 'x.x.x.x' where x is an integer from 0 to 65534.
1119 <Parameter Type="System.String" Name="elementName" />
1120 <Parameter Type="System.String" Name="attributeName" />
1121 <Parameter Type="System.String" Name="value" />
1122 </Instance>
1123 </Message>
1124 <Message Id="CustomTableNameTooLong" Number="109">
1125 <Instance>
1126 The {0}/@{1} attribute's value, '{2}', is too long for a table name. It cannot be more than than 31 characters long.
1127 <Parameter Type="System.String" Name="elementName" />
1128 <Parameter Type="System.String" Name="attributeName" />
1129 <Parameter Type="System.String" Name="value" />
1130 </Instance>
1131 </Message>
1132 <Message Id="CustomTableIllegalColumnWidth" Number="110">
1133 <Instance>
1134 The {0}/@{1} attribute's value, '{2}', is not a valid column width. Valid column widths are 2 or 4.
1135 <Parameter Type="System.String" Name="elementName" />
1136 <Parameter Type="System.String" Name="attributeName" />
1137 <Parameter Type="System.Int32" Name="value" />
1138 </Instance>
1139 </Message>
1140 <Message Id="CustomTableMissingPrimaryKey" Number="111">
1141 <Instance>The CustomTable is missing a Column element with the PrimaryKey attribute set to 'yes'. At least one column must be marked as the primary key.</Instance>
1142 </Message>
1143 <Message Id="TypeSpecificationForExtensionRequired" Number="113" SourceLineNumbers="no">
1144 <Instance>
1145 The parameter '{0}' must be followed by the extension's type specification. The type specification should be a fully qualified class and assembly identity, for example: "MyNamespace.MyClass,myextension.dll".
1146 <Parameter Type="System.String" Name="parameter" />
1147 </Instance>
1148 </Message>
1149 <Message Id="FilePathRequired" SourceLineNumbers="no" Number="114">
1150 <Instance>
1151 The parameter '{0}' must be followed by a file path.
1152 <Parameter Type="System.String" Name="parameter" />
1153 </Instance>
1154 </Message>
1155 <Message Id="DirectoryPathRequired" Number="115" SourceLineNumbers="no">
1156 <Instance>
1157 The parameter '{0}' must be followed by a directory path.
1158 <Parameter Type="System.String" Name="parameter" />
1159 </Instance>
1160 </Message>
1161 <Message Id="FileOrDirectoryPathRequired" Number="116" SourceLineNumbers="no">
1162 <Instance>
1163 The parameter '{0}' must be followed by a file or directory path. To specify a directory path the string must end with a backslash, for example: "C:\Path\".
1164 <Parameter Type="System.String" Name="parameter" />
1165 </Instance>
1166 </Message>
1167 <Message Id="PathCannotContainQuote" Number="117" SourceLineNumbers="no">
1168 <Instance>
1169 Path '{0}' contains a literal quote character. Quotes are often accidentally introduced when trying to refer to a directory path with spaces in it, such as "C:\Out Directory\" -- the backslash before the quote acts an escape character. The correct representation for that path is: "C:\Out Directory\\".
1170 <Parameter Type="System.String" Name="fileName" />
1171 </Instance>
1172 </Message>
1173 <Message Id="AdditionalArgumentUnexpected" Number="118" SourceLineNumbers="no">
1174 <Instance>
1175 Additional argument '{0}' was unexpected. Remove the argument and add the '-?' switch for more information.
1176 <Parameter Type="System.String" Name="argument" />
1177 </Instance>
1178 </Message>
1179 <Message Id="RegistryNameValueIncorrect" Number="119">
1180 <Instance>
1181 The {0}/@{1} attribute's value, '{2}', is incorrect. It should not contain values of '+', '-', or '*' when the {0}/@Value attribute is empty. Instead, use the proper element and attributes: for Name='+' use RegistryKey/@Action='createKey', for Name='-' use RemoveRegistryKey/@Action='removeOnUninstall', for Name='*' use RegistryKey/@Action='createAndRemoveOnUninstall'.
1182 <Parameter Type="System.String" Name="elementName" />
1183 <Parameter Type="System.String" Name="attributeName" />
1184 <Parameter Type="System.String" Name="value" />
1185 </Instance>
1186 </Message>
1187 <Message Id="FamilyNameTooLong" Number="120">
1188 <Instance>
1189 The {0}/@{1} attribute's value, '{2}', is {3} characters long. This is too long for a family name because the maximum allowed length is 8 characters long.
1190 <Parameter Type="System.String" Name="elementName" />
1191 <Parameter Type="System.String" Name="attributeName" />
1192 <Parameter Type="System.String" Name="value" />
1193 <Parameter Type="System.Int32" Name="length" />
1194 </Instance>
1195 </Message>
1196 <Message Id="IllegalFamilyName" Number="121">
1197 <Instance>
1198 The {0}/@{1} attribute's value, '{2}', contains illegal characters for a family name. Legal values include letters, numbers, and underscores.
1199 <Parameter Type="System.String" Name="elementName" />
1200 <Parameter Type="System.String" Name="attributeName" />
1201 <Parameter Type="System.String" Name="value" />
1202 </Instance>
1203 </Message>
1204 <Message Id="IllegalLongValue" Number="122">
1205 <Instance>
1206 The {0}/@{1} attribute's value, '{2}', is not a legal long value. Legal long values are from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807.
1207 <Parameter Type="System.String" Name="elementName" />
1208 <Parameter Type="System.String" Name="attributeName" />
1209 <Parameter Type="System.String" Name="value" />
1210 </Instance>
1211 </Message>
1212 <Message Id="IntegralValueOutOfRange" Number="123">
1213 <Instance>
1214 The {0}/@{1} attribute's value, '{2}', is not in the range of legal values. Legal values for this attribute are from {3} to {4}.
1215 <Parameter Type="System.String" Name="elementName" />
1216 <Parameter Type="System.String" Name="attributeName" />
1217 <Parameter Type="System.Int32" Name="value" />
1218 <Parameter Type="System.Int32" Name="minimum" />
1219 <Parameter Type="System.Int32" Name="maximum" />
1220 </Instance>
1221 <Instance>
1222 The {0}/@{1} attribute's value, '{2}', is not in the range of legal values. Legal values for this attribute are from {3} to {4}.
1223 <Parameter Type="System.String" Name="elementName" />
1224 <Parameter Type="System.String" Name="attributeName" />
1225 <Parameter Type="System.Int64" Name="value" />
1226 <Parameter Type="System.Int64" Name="minimum" />
1227 <Parameter Type="System.Int64" Name="maximum" />
1228 </Instance>
1229 </Message>
1230 <Message Id="DuplicateExtensionXmlSchemaNamespace" Number="125" SourceLineNumbers="no">
1231 <Instance>
1232 The extension '{0}' uses the same xml schema namespace, '{1}', as previously loaded extension '{2}'. Please either remove one of the extensions or rename the xml schema namespace to avoid the collision.
1233 <Parameter Type="System.String" Name="extension" />
1234 <Parameter Type="System.String" Name="extensionXmlSchemaNamespace" />
1235 <Parameter Type="System.String" Name="collidingExtension" />
1236 </Instance>
1237 </Message>
1238 <Message Id="DuplicateExtensionTable" Number="126" SourceLineNumbers="no">
1239 <Instance>
1240 The extension '{0}' contains a definition for table '{1}' that collides with a previously loaded table definition. Please remove one of the conflicting extensions or rename one of the tables to avoid the collision.
1241 <Parameter Type="System.String" Name="extension" />
1242 <Parameter Type="System.String" Name="tableName" />
1243 </Instance>
1244 </Message>
1245 <Message Id="DuplicateExtensionPreprocessorType" Number="127" SourceLineNumbers="no">
1246 <Instance>
1247 The extension '{0}' uses the same preprocessor variable prefix, '{1}', as previously loaded extension '{2}'. Please remove one of the extensions or rename the prefix to avoid the collision.
1248 <Parameter Type="System.String" Name="extension" />
1249 <Parameter Type="System.String" Name="variablePrefix" />
1250 <Parameter Type="System.String" Name="collidingExtension" />
1251 </Instance>
1252 </Message>
1253 <Message Id="FileInUse" Number="128">
1254 <Instance>
1255 The process can not access the file '{0}' because it is being used by another process.
1256 <Parameter Type="System.String" Name="file" />
1257 </Instance>
1258 </Message>
1259 <Message Id="CannotOpenMergeModule" Number="129">
1260 <Instance>
1261 Cannot open the merge module '{0}' from file '{1}'.
1262 <Parameter Type="System.String" Name="mergeModuleIdentifier" />
1263 <Parameter Type="System.String" Name="mergeModuleFile" />
1264 </Instance>
1265 </Message>
1266 <Message Id="DuplicatePrimaryKey" Number="130">
1267 <Instance>
1268 The primary key '{0}' is duplicated in table '{1}'. Please remove one of the entries or rename a part of the primary key to avoid the collision.
1269 <Parameter Type="System.String" Name="primaryKey" />
1270 <Parameter Type="System.String" Name="tableName" />
1271 </Instance>
1272 </Message>
1273 <Message Id="FileIdentifierNotFound" Number="131">
1274 <Instance>
1275 The file row with identifier '{0}' could not be found.
1276 <Parameter Type="System.String" Name="fileIdentifier" />
1277 </Instance>
1278 </Message>
1279 <Message Id="InvalidAssemblyFile" Number="132">
1280 <Instance>
1281 The assembly file '{0}' appears to be invalid. Please ensure this is a valid assembly file and that the user has the appropriate access rights to this file. More information: {1}
1282 <Parameter Type="System.String" Name="assemblyFile" />
1283 <Parameter Type="System.String" Name="moreInformation" />
1284 </Instance>
1285 </Message>
1286 <Message Id="ExpectedEndElement" Number="133">
1287 <Instance>
1288 The end element matching the '{0}' start element was not found.
1289 <Parameter Type="System.String" Name="elementName" />
1290 </Instance>
1291 </Message>
1292 <Message Id="IllegalCodepage" Number="134" SourceLineNumbers="no">
1293 <Instance>
1294 The code page '{0}' is not a valid Windows code page. Update the database's code page by modifying one of the following attributes: Product/@Codepage, Module/@Codepage, Patch/@Codepage, PatchCreation/@Codepage, or WixLocalization/@Codepage.
1295 <Parameter Type="System.Int32" Name="codepage" />
1296 </Instance>
1297 </Message>
1298 <Message Id="ExpectedMediaCabinet" Number="135">
1299 <Instance>
1300 The file '{0}' should be compressed but is not part of a compressed media. Files will be compressed if either the File/@Compressed or Package/@Compressed attributes are set to 'yes'. This can be fixed by setting the Media/@Cabinet attribute for media '{1}'.
1301 <Parameter Type="System.String" Name="fileId" />
1302 <Parameter Type="System.Int32" Name="diskId" />
1303 </Instance>
1304 </Message>
1305 <Message Id="InvalidIdt" Number="136">
1306 <Instance>
1307 There was an error importing the file '{0}'.
1308 <Parameter Type="System.String" Name="idtFile" />
1309 </Instance>
1310 <Instance>
1311 There was an error importing table '{1}' from file '{0}'.
1312 <Parameter Type="System.String" Name="idtFile" />
1313 <Parameter Type="System.String" Name="tableName" />
1314 </Instance>
1315 </Message>
1316 <Message Id="InvalidSequenceTable" Number="137" SourceLineNumbers="no">
1317 <Instance>
1318 Found an invalid sequence table '{0}'.
1319 <Parameter Type="System.String" Name="sequenceTableName" />
1320 </Instance>
1321 </Message>
1322 <Message Id="ExpectedDirectory" Number="138" SourceLineNumbers="no">
1323 <Instance>
1324 The directory '{0}' could not be found.
1325 <Parameter Type="System.String" Name="directory" />
1326 </Instance>
1327 </Message>
1328 <Message Id="ComponentExpectedFeature" Number="139">
1329 <Instance>
1330 The component '{0}' is not assigned to a feature. The component's {1} '{2}' requires it to be assigned to at least one feature.
1331 <Parameter Type="System.String" Name="component" />
1332 <Parameter Type="System.String" Name="type" />
1333 <Parameter Type="System.String" Name="target" />
1334 </Instance>
1335 </Message>
1336 <Message Id="RecursiveAction" Number="140" SourceLineNumbers="no">
1337 <Instance>
1338 The action '{0}' is recursively placed in the '{1}' table.
1339 <Parameter Type="System.String" Name="action" />
1340 <Parameter Type="System.String" Name="tableName" />
1341 </Instance>
1342 </Message>
1343 <Message Id="VersionMismatch" Number="141">
1344 <Instance>
1345 The {0} file format version {1} is not compatible with the expected {0} file format version {2}.
1346 <Parameter Type="System.String" Name="fileType" />
1347 <Parameter Type="System.String" Name="version" />
1348 <Parameter Type="System.String" Name="expectedVersion" />
1349 </Instance>
1350 </Message>
1351 <Message Id="UnexpectedContentNode" Number="142">
1352 <Instance>
1353 The {0} element contains an unexpected xml node of type {1}.
1354 <Parameter Type="System.String" Name="elementName" />
1355 <Parameter Type="System.String" Name="unexpectedNodeType" />
1356 </Instance>
1357 </Message>
1358 <Message Id="UnexpectedColumnCount" Number="143">
1359 <Instance>
1360 A parsed row has more fields that contain data for table '{0}' than are defined. This is potentially because a standard table is being redefined as a custom table or is based on an older table schema.
1361 <Parameter Type="System.String" Name="tableName" />
1362 </Instance>
1363 </Message>
1364 <Message Id="InvalidExtension" Number="144" SourceLineNumbers="no">
1365 <Instance>
1366 The extension '{0}' could not be loaded.
1367 <Parameter Type="System.String" Name="extension" />
1368 </Instance>
1369 <Instance>
1370 The extension '{0}' could not be loaded because of the following reason: {1}
1371 <Parameter Type="System.String" Name="extension" />
1372 <Parameter Type="System.String" Name="invalidReason" />
1373 </Instance>
1374 <Instance>
1375 The extension '{0}' is the wrong type: '{1}'. The expected type was '{2}'.
1376 <Parameter Type="System.String" Name="extension" />
1377 <Parameter Type="System.String" Name="extensionType" />
1378 <Parameter Type="System.String" Name="expectedType" />
1379 </Instance>
1380 <Instance>
1381 The extension '{0}' is the wrong type: '{1}'. The expected type was '{2}' or '{3}'.
1382 <Parameter Type="System.String" Name="extension" />
1383 <Parameter Type="System.String" Name="extensionType" />
1384 <Parameter Type="System.String" Name="expectedType1" />
1385 <Parameter Type="System.String" Name="expectedType2" />
1386 </Instance>
1387 </Message>
1388 <Message Id="InvalidSubExpression" Number="145">
1389 <Instance>
1390 Found invalid subexpression '{0}' in expression '{1}'.
1391 <Parameter Type="System.String" Name="subExpression" />
1392 <Parameter Type="System.String" Name="expression" />
1393 </Instance>
1394 </Message>
1395 <Message Id="UnmatchedPreprocessorInstruction" Number="146">
1396 <Instance>
1397 Found a &lt;?{1}?&gt; processing instruction without a matching &lt;?{0}?&gt; before it.
1398 <Parameter Type="System.String" Name="beginInstruction" />
1399 <Parameter Type="System.String" Name="endInstruction" />
1400 </Instance>
1401 </Message>
1402 <Message Id="NonterminatedPreprocessorInstruction" Number="147">
1403 <Instance>
1404 Found a &lt;?{0}?&gt; processing instruction without a matching &lt;?{1}?&gt; after it.
1405 <Parameter Type="System.String" Name="beginInstruction" />
1406 <Parameter Type="System.String" Name="endInstruction" />
1407 </Instance>
1408 </Message>
1409 <Message Id="ExpectedExpressionAfterNot" Number="148">
1410 <Instance>
1411 Expecting an argument for 'NOT' in expression '{0}'.
1412 <Parameter Type="System.String" Name="expression" />
1413 </Instance>
1414 </Message>
1415 <Message Id="InvalidPreprocessorVariable" Number="149">
1416 <Instance>
1417 Ill-formed preprocessor variable '$({0})'. Variables must have a prefix (like 'var.', 'env.', or 'sys.') and a name at least 1 character long. If the literal string '$({0})' is desired, use '$$({0})'.
1418 <Parameter Type="System.String" Name="variable" />
1419 </Instance>
1420 </Message>
1421 <Message Id="UndefinedPreprocessorVariable" Number="150">
1422 <Instance>
1423 Undefined preprocessor variable '$({0})'.
1424 <Parameter Type="System.String" Name="variableName" />
1425 </Instance>
1426 </Message>
1427 <Message Id="IllegalDefineStatement" Number="151">
1428 <Instance>
1429 The define statement '&lt;?define {0}?&gt;' is not well-formed. Define statements should be in the form &lt;?define variableName = "variable value"?&gt;.
1430 <Parameter Type="System.String" Name="defineStatement" />
1431 </Instance>
1432 </Message>
1433 <Message Id="VariableDeclarationCollision" Number="152">
1434 <Instance>
1435 The variable '{0}' with value '{1}' was previously declared with value '{2}'.
1436 <Parameter Type="System.String" Name="variableName" />
1437 <Parameter Type="System.String" Name="variableValue" />
1438 <Parameter Type="System.String" Name="variableCollidingValue" />
1439 </Instance>
1440 </Message>
1441 <Message Id="CannotReundefineVariable" Number="153">
1442 <Instance>
1443 The variable '{0}' cannot be undefined because its already undefined.
1444 <Parameter Type="System.String" Name="variableName" />
1445 </Instance>
1446 </Message>
1447 <Message Id="IllegalForeach" Number="154">
1448 <Instance>
1449 The foreach statement '{0}' is illegal. The proper format for foreach is &lt;?foreach varName in valueList?&gt;.
1450 <Parameter Type="System.String" Name="foreachStatement" />
1451 </Instance>
1452 </Message>
1453 <Message Id="IllegalParentAttributeWhenNested" Number="155">
1454 <Instance>
1455 The {0}/@{1} attribute cannot be specified when a {2} element is nested underneath the {0} element.
1456 <Parameter Type="System.String" Name="parentElementName" />
1457 <Parameter Type="System.String" Name="parentAttributeName" />
1458 <Parameter Type="System.String" Name="childElement" />
1459 </Instance>
1460 </Message>
1461 <Message Id="ExpectedEndforeach" Number="156">
1462 <Instance>A &lt;?foreach?&gt; statement was found that had no matching &lt;?endforeach?&gt;.</Instance>
1463 </Message>
1464 <Message Id="UnmatchedQuotesInExpression" Number="158">
1465 <Instance>
1466 The quotes don't match in the expression '{0}'.
1467 <Parameter Type="System.String" Name="expression" />
1468 </Instance>
1469 </Message>
1470 <Message Id="UnmatchedParenthesisInExpression" Number="159">
1471 <Instance>
1472 The parenthesis don't match in the expression '{0}'.
1473 <Parameter Type="System.String" Name="expression" />
1474 </Instance>
1475 </Message>
1476 <Message Id="ExpectedVariable" Number="160">
1477 <Instance>
1478 A required variable was missing in the expression '{0}'.
1479 <Parameter Type="System.String" Name="expression" />
1480 </Instance>
1481 </Message>
1482 <Message Id="UnexpectedLiteral" Number="161">
1483 <Instance>
1484 An unexpected literal was found in the expression '{0}'.
1485 <Parameter Type="System.String" Name="expression" />
1486 </Instance>
1487 </Message>
1488 <Message Id="IllegalIntegerInExpression" Number="162">
1489 <Instance>
1490 An illegal number was found in the expression '{0}'.
1491 <Parameter Type="System.String" Name="expression" />
1492 </Instance>
1493 </Message>
1494 <Message Id="UnexpectedPreprocessorOperator" Number="163">
1495 <Instance>
1496 The operator '{0}' is unexpected.
1497 <Parameter Type="System.String" Name="operator" />
1498 </Instance>
1499 </Message>
1500 <Message Id="UnexpectedEmptySubexpression" Number="164">
1501 <Instance>
1502 The empty subexpression is unexpected in the expression '{0}'.
1503 <Parameter Type="System.String" Name="expression" />
1504 </Instance>
1505 </Message>
1506 <Message Id="UnexpectedCustomTableColumn" Number="165">
1507 <Instance>
1508 The custom table column '{0}' is unknown.
1509 <Parameter Type="System.String" Name="column" />
1510 </Instance>
1511 </Message>
1512 <Message Id="UnknownCustomTableColumnType" Number="166">
1513 <Instance>
1514 Encountered an unknown custom table column type '{0}'.
1515 <Parameter Type="System.String" Name="columnType" />
1516 </Instance>
1517 </Message>
1518 <Message Id="IllegalFileCompressionAttributes" Number="167">
1519 <Instance>Cannot have both the MsidbFileAttributesCompressed and MsidbFileAttributesNoncompressed options set in a file attributes column.</Instance>
1520 </Message>
1521 <Message Id="OverridableActionCollision" Number="168">
1522 <Instance>
1523 The {0} table contains an action '{1}' that is declared overridable in two different locations. Please remove one of the actions or the Overridable='yes' attribute from one of the actions.
1524 <Parameter Type="System.String" Name="sequenceTableName" />
1525 <Parameter Type="System.String" Name="actionName" />
1526 </Instance>
1527 </Message>
1528 <Message Id="OverridableActionCollision2" Number="169">
1529 <Instance>The location of the action related to previous error.</Instance>
1530 </Message>
1531 <Message Id="ActionCollision" Number="170">
1532 <Instance>
1533 The {0} table contains an action '{1}' that is declared in two different locations. Please remove one of the actions or set the Overridable='yes' attribute on one of their elements.
1534 <Parameter Type="System.String" Name="sequenceTableName" />
1535 <Parameter Type="System.String" Name="actionName" />
1536 </Instance>
1537 </Message>
1538 <Message Id="ActionCollision2" Number="171">
1539 <Instance>The location of the action related to previous error.</Instance>
1540 </Message>
1541 <Message Id="SuppressNonoverridableAction" Number="172">
1542 <Instance>
1543 The {0} table contains an action '{1}' that cannot be suppressed because it is not declared overridable in the base definition. Please stop suppressing the action or make it overridable in its base declaration.
1544 <Parameter Type="System.String" Name="sequenceTableName" />
1545 <Parameter Type="System.String" Name="actionName" />
1546 </Instance>
1547 </Message>
1548 <Message Id="SuppressNonoverridableAction2" Number="173">
1549 <Instance>The location of the non-overridable definition of the action related to previous error.</Instance>
1550 </Message>
1551 <Message Id="CustomActionSequencedInModule" Number="174">
1552 <Instance>
1553 The {0} table contains a custom action '{1}' that has a sequence number specified. The Sequence attribute is not allowed for custom actions in a merge module. Please remove the action or use the Before or After attributes to specify where this action should be sequenced relative to another action.
1554 <Parameter Type="System.String" Name="sequenceTableName" />
1555 <Parameter Type="System.String" Name="actionName" />
1556 </Instance>
1557 </Message>
1558 <Message Id="StandardActionRelativelyScheduledInModule" Number="175">
1559 <Instance>
1560 The {0} table contains a standard action '{1}' that does not have a sequence number specified. The Sequence attribute is required for standard actions in a merge module. Please remove the action or use the Sequence attribute.
1561 <Parameter Type="System.String" Name="sequenceTableName" />
1562 <Parameter Type="System.String" Name="actionName" />
1563 </Instance>
1564 </Message>
1565 <Message Id="ActionCircularDependency" Number="176">
1566 <Instance>
1567 The {0} table contains an action '{1}' that is scheduled to come before or after action '{2}', which is also scheduled to come before or after action '{1}'. Please remove this circular dependency by changing the Before or After attribute for one of the actions.
1568 <Parameter Type="System.String" Name="sequenceTableName" />
1569 <Parameter Type="System.String" Name="actionName1" />
1570 <Parameter Type="System.String" Name="actionName2" />
1571 </Instance>
1572 </Message>
1573 <Message Id="ActionScheduledRelativeToTerminationAction" Number="177">
1574 <Instance>
1575 The {0} table contains an action '{1}' that is scheduled to come before or after action '{2}', which is a special action which only occurs when the installer terminates. These special actions can be identified by their negative sequence numbers. Please schedule the action '{1}' to come before or after a different action.
1576 <Parameter Type="System.String" Name="sequenceTableName" />
1577 <Parameter Type="System.String" Name="actionName1" />
1578 <Parameter Type="System.String" Name="actionName2" />
1579 </Instance>
1580 </Message>
1581 <Message Id="ActionScheduledRelativeToTerminationAction2" Number="178">
1582 <Instance>The location of the special termination action related to previous error(s).</Instance>
1583 </Message>
1584 <Message Id="NoUniqueActionSequenceNumber" Number="179">
1585 <Instance>
1586 The {0} table contains an action '{1}' which cannot have a unique sequence number because it is scheduled before or after action '{2}'. There is not enough room before or after this action to assign a unique sequence number. Please schedule one of the actions differently so that it will be in a position with more sequence numbers available. Please note that sequence numbers must be an integer in the range 1 - 32767 (inclusive).
1587 <Parameter Type="System.String" Name="sequenceTableName" />
1588 <Parameter Type="System.String" Name="actionName1" />
1589 <Parameter Type="System.String" Name="actionName2" />
1590 </Instance>
1591 </Message>
1592 <Message Id="NoUniqueActionSequenceNumber2" Number="180">
1593 <Instance>The location of the sequenced action related to previous error.</Instance>
1594 </Message>
1595 <Message Id="ActionScheduledRelativeToItself" Number="181">
1596 <Instance>
1597 The {0}/@{1} attribute's value '{2}' is invalid because it would make this action dependent upon itself. Please change the value to the name of a different action.
1598 <Parameter Type="System.String" Name="elementName" />
1599 <Parameter Type="System.String" Name="attributeName" />
1600 <Parameter Type="System.String" Name="attributeValue" />
1601 </Instance>
1602 </Message>
1603 <Message Id="MissingTableDefinition" Number="182" SourceLineNumbers="no">
1604 <Instance>
1605 Cannot find the table definitions for the '{0}' table. This is likely due to a typing error or missing extension. Please ensure all the necessary extensions are supplied on the command line with the -ext parameter.
1606 <Parameter Type="System.String" Name="tableName" />
1607 </Instance>
1608 </Message>
1609 <Message Id="ExpectedRowInPatchCreationPackage" Number="183" SourceLineNumbers="no">
1610 <Instance>
1611 Could not find a row in the '{0}' table for this patch creation package. Patch creation packages must contain at least one row in the '{0}' table.
1612 <Parameter Type="System.String" Name="tableName" />
1613 </Instance>
1614 </Message>
1615 <Message Id="UnexpectedTableInMergeModule" Number="184">
1616 <Instance>
1617 An unexpected row in the '{0}' table was found in this merge module. Merge modules cannot contain the '{0}' table.
1618 <Parameter Type="System.String" Name="tableName" />
1619 </Instance>
1620 </Message>
1621 <Message Id="UnexpectedTableInPatchCreationPackage" Number="185">
1622 <Instance>
1623 An unexpected row in the '{0}' table was found in this patch creation package. Patch creation packages cannot contain the '{0}' table.
1624 <Parameter Type="System.String" Name="tableName" />
1625 </Instance>
1626 </Message>
1627 <Message Id="MergeExcludedModule" Number="186">
1628 <Instance>
1629 The module '{0}' cannot be merged because it excludes or is excluded by the merge module with signature '{1}'.
1630 <Parameter Type="System.String" Name="mergeId" />
1631 <Parameter Type="System.String" Name="otherMergeId" />
1632 </Instance>
1633 </Message>
1634 <Message Id="MergeFeatureRequired" Number="187">
1635 <Instance>
1636 The {0} table contains a row with primary key(s) '{1}' which requires a feature to properly merge from the merge module '{2}'. Nest a MergeRef element with an Id attribute set to the value '{3}' under a Feature element to fix this error.
1637 <Parameter Type="System.String" Name="tableName" />
1638 <Parameter Type="System.String" Name="primaryKeys" />
1639 <Parameter Type="System.String" Name="mergeModuleFile" />
1640 <Parameter Type="System.String" Name="mergeId" />
1641 </Instance>
1642 </Message>
1643 <Message Id="MergeLanguageFailed" Number="188">
1644 <Instance>
1645 The language '{0}' is supported but uses an invalid language transform in the merge module '{1}'.
1646 <Parameter Type="System.Int16" Name="language" />
1647 <Parameter Type="System.String" Name="mergeModuleFile" />
1648 </Instance>
1649 </Message>
1650 <Message Id="MergeLanguageUnsupported" Number="189">
1651 <Instance>
1652 Could not locate language '{0}' (or a transform for this language) in the merge module '{1}'. This is likely due to an incorrectly authored Merge/@Language attribute.
1653 <Parameter Type="System.Int16" Name="language" />
1654 <Parameter Type="System.String" Name="mergeModuleFile" />
1655 </Instance>
1656 </Message>
1657 <Message Id="TableDecompilationUnimplemented" Number="190" SourceLineNumbers="no">
1658 <Instance>
1659 Decompilation of the {0} table has not been implemented by its extension.
1660 <Parameter Type="System.String" Name="tableName" />
1661 </Instance>
1662 </Message>
1663 <Message Id="CannotDefaultMismatchedAdvertiseStates" Number="191">
1664 <Instance>
1665 MIME element cannot be marked as the default when its advertise state differs from its parent element. Ensure that the advertise state of the MIME element matches its parents element or remove the Mime/@Advertise attribute completely.
1666 </Instance>
1667 </Message>
1668 <Message Id="VersionIndependentProgIdsCannotHaveIcons" Number="192">
1669 <Instance>
1670 Version independent ProgIds cannot have Icons. Remove the Icon and/or IconIndex attributes from your ProgId element.
1671 </Instance>
1672 </Message>
1673 <Message Id="IllegalAttributeValueWithOtherAttribute" Number="193">
1674 <Instance>
1675 The {0}/@{1} attribute's value, '{2}', cannot be specified with attribute {3} present.
1676 <Parameter Type="System.String" Name="elementName" />
1677 <Parameter Type="System.String" Name="attributeName" />
1678 <Parameter Type="System.String" Name="attributeValue" />
1679 <Parameter Type="System.String" Name="otherAttributeName" />
1680 </Instance>
1681 <Instance>
1682 The {0}/@{1} attribute's value, '{2}', cannot be specified with attribute {3} present with value '{4}'.
1683 <Parameter Type="System.String" Name="elementName" />
1684 <Parameter Type="System.String" Name="attributeName" />
1685 <Parameter Type="System.String" Name="attributeValue" />
1686 <Parameter Type="System.String" Name="otherAttributeName" />
1687 <Parameter Type="System.String" Name="otherAttributeValue" />
1688 </Instance>
1689 </Message>
1690 <Message Id="InvalidMergeLanguage" Number="194">
1691 <Instance>
1692 The Merge element '{0}' specified an invalid language '{1}'. Verify that localization tokens are being properly resolved to a numeric LCID.
1693 <Parameter Type="System.String" Name="mergeId" />
1694 <Parameter Type="System.String" Name="mergeLanguage" />
1695 </Instance>
1696 </Message>
1697 <Message Id="WixVariableCollision" Number="195">
1698 <Instance>
1699 The WiX variable '{0}' is declared in more than one location. Please remove one of the declarations.
1700 <Parameter Type="System.String" Name="variableId" />
1701 </Instance>
1702 </Message>
1703 <Message Id="ExpectedWixVariableValue" Number="196" SourceLineNumbers="no">
1704 <Instance>
1705 The WiX variable '{0}' was declared without a value. Please specify a value for the variable.
1706 <Parameter Type="System.String" Name="variableId" />
1707 </Instance>
1708 </Message>
1709 <Message Id="WixVariableUnknown" Number="197">
1710 <Instance>
1711 The WiX variable !(wix.{0}) is unknown. Please ensure the variable is declared on the command line for light.exe, via a WixVariable element, or inline using the syntax !(wix.{0}=some value which doesn't contain parenthesis).
1712 <Parameter Type="System.String" Name="variableId" />
1713 </Instance>
1714 </Message>
1715 <Message Id="IllegalWixVariablePrefix" Number="198">
1716 <Instance>
1717 The WiX variable $(wix.{0}) uses an illegal prefix '$'. Please use the '!' prefix instead.
1718 <Parameter Type="System.String" Name="variableId" />
1719 </Instance>
1720 </Message>
1721 <Message Id="InvalidWixXmlNamespace" Number="199">
1722 <Instance>
1723 The {0} element has no namespace. Please make the {0} element look like the following: &lt;{0} xmlns="{1}"&gt;.
1724 <Parameter Type="System.String" Name="wixElementName" />
1725 <Parameter Type="System.String" Name="wixNamespace" />
1726 </Instance>
1727 <Instance>
1728 The {0} element has an incorrect namespace of '{1}'. Please make the {0} element look like the following: &lt;{0} xmlns="{2}"&gt;.
1729 <Parameter Type="System.String" Name="wixElementName" />
1730 <Parameter Type="System.String" Name="elementNamespace" />
1731 <Parameter Type="System.String" Name="wixNamespace" />
1732 </Instance>
1733 </Message>
1734 <Message Id="UnhandledExtensionElement" Number="200">
1735 <Instance>
1736 The {0} element contains an unhandled extension element '{1}'. Please ensure that the extension for elements in the '{2}' namespace has been provided.
1737 <Parameter Type="System.String" Name="elementName" />
1738 <Parameter Type="System.String" Name="extensionElementName" />
1739 <Parameter Type="System.String" Name="extensionNamespace" />
1740 </Instance>
1741 </Message>
1742 <Message Id="UnhandledExtensionAttribute" Number="201">
1743 <Instance>
1744 The {0} element contains an unhandled extension attribute '{1}'. Please ensure that the extension for attributes in the '{2}' namespace has been provided.
1745 <Parameter Type="System.String" Name="elementName" />
1746 <Parameter Type="System.String" Name="extensionAttributeName" />
1747 <Parameter Type="System.String" Name="extensionNamespace" />
1748 </Instance>
1749 </Message>
1750 <Message Id="UnsupportedExtensionAttribute" Number="202">
1751 <Instance>
1752 The {0} element contains an unsupported extension attribute '{1}'. The {0} element does not currently support extension attributes. Is the {1} attribute using the correct XML namespace?
1753 <Parameter Type="System.String" Name="elementName" />
1754 <Parameter Type="System.String" Name="extensionElementName" />
1755 </Instance>
1756 </Message>
1757 <Message Id="UnsupportedExtensionElement" Number="203">
1758 <Instance>
1759 The {0} element contains an unsupported extension element '{1}'. The {0} element does not currently support extension elements. Is the {1} element using the correct XML namespace?
1760 <Parameter Type="System.String" Name="elementName" />
1761 <Parameter Type="System.String" Name="extensionElementName" />
1762 </Instance>
1763 </Message>
1764 <Message Id="ValidationError" Number="204">
1765 <Instance>
1766 {0}: {1}
1767 <Parameter Type="System.String" Name="ice" />
1768 <Parameter Type="System.String" Name="message" />
1769 </Instance>
1770 </Message>
1771 <Message Id="IllegalRootDirectory" Number="205">
1772 <Instance>
1773 The Directory with Id '{0}' is not a valid root directory. There may only be a single root directory per product or module and its Id attribute value must be 'TARGETDIR' and its Name attribute value must be 'SourceDir'.
1774 <Parameter Type="System.String" Name="directoryId" />
1775 </Instance>
1776 </Message>
1777 <Message Id="IllegalTargetDirDefaultDir" Number="206">
1778 <Instance>
1779 The 'TARGETDIR' directory has an illegal DefaultDir value of '{0}'. The DefaultDir value is created from the *Name attributes of the Directory element. The TARGETDIR directory is a special directory which must have its Name attribute set to 'SourceDir'.
1780 <Parameter Type="System.String" Name="defaultDir" />
1781 </Instance>
1782 </Message>
1783 <Message Id="TooManyElements" Number="207">
1784 <Instance>
1785 The {0} element contains an unexpected child element '{1}'. The '{1}' element may only occur {2} time(s) under the {0} element.
1786 <Parameter Type="System.String" Name="elementName" />
1787 <Parameter Type="System.String" Name="childElementName" />
1788 <Parameter Type="System.Int32" Name="expectedInstances" />
1789 </Instance>
1790 </Message>
1791 <Message Id="ExpectedBinaryCategory" Number="208">
1792 <Instance>The Column element specifies a binary column but does not have the correct Category specified. Windows Installer requires binary columns to specify their category as binary. Please set the Category attribute's value to 'Binary'.</Instance>
1793 </Message>
1794 <Message Id="RootFeatureCannotFollowParent" Number="209">
1795 <Instance>The Feature element specifies a root feature with an illegal InstallDefault value of 'followParent'. Root features cannot follow their parent feature's install state because they don't have a parent feature. Please remove or change the value of the InstallDefault attribute.</Instance>
1796 </Message>
1797 <Message Id="FeatureNameTooLong" Number="210">
1798 <Instance>
1799 The {0}/@{1} attribute with value '{2}', is too long for a feature name. Due to limitations in the Windows Installer, feature names cannot be longer than 38 characters in length.
1800 <Parameter Type="System.String" Name="elementName" />
1801 <Parameter Type="System.String" Name="attributeName" />
1802 <Parameter Type="System.String" Name="attributeValue" />
1803 </Instance>
1804 </Message>
1805 <Message Id="SignedEmbeddedCabinet" Number="211">
1806 <Instance>The DigitalSignature element cannot be nested under a Media element which specifies EmbedCab='yes'. This is because Windows Installer can only verify the digital signatures of external cabinets. Please either remove the DigitalSignature element or change the value of the Media/@EmbedCab attribute to 'no'.</Instance>
1807 </Message>
1808 <Message Id="ExpectedSignedCabinetName" Number="212">
1809 <Instance>The Media/@Cabinet attribute was not found; it is required when this element contains a DigitalSignature child element. This is because Windows Installer can only verify the digital signatures of external cabinets. Please either remove the DigitalSignature element or specify a valid external cabinet name via the Cabinet attribute.</Instance>
1810 </Message>
1811 <Message Id="IllegalInlineLocVariable" Number="213">
1812 <Instance>
1813 The localization variable '{0}' specifies an illegal inline default value of '{1}'. Localization variables cannot specify default values inline, instead the value should be specified in a WiX localization (.wxl) file.
1814 <Parameter Type="System.String" Name="variableName" />
1815 <Parameter Type="System.String" Name="variableValue" />
1816 </Instance>
1817 </Message>
1818 <Message Id="MergeModuleExpectedFeature" Number="215">
1819 <Instance>
1820 The merge module '{0}' is not assigned to a feature. All merge modules must be assigned to at least one feature.
1821 <Parameter Type="System.String" Name="mergeId" />
1822 </Instance>
1823 </Message>
1824 <Message Id="Win32Exception" Number="216" SourceLineNumbers="no">
1825 <Instance>
1826 An unexpected Win32 exception with error code 0x{0:X} occurred: {1}
1827 <Parameter Type="System.Int32" Name="nativeErrorCode"/>
1828 <Parameter Type="System.String" Name="message" />
1829 </Instance>
1830 <Instance>
1831 An unexpected Win32 exception with error code 0x{0:X} occurred while accessing file '{1}': {2}
1832 <Parameter Type="System.Int32" Name="nativeErrorCode"/>
1833 <Parameter Type="System.String" Name="file"/>
1834 <Parameter Type="System.String" Name="message" />
1835 </Instance>
1836 </Message>
1837 <Message Id="UnexpectedExternalUIMessage" Number="217" SourceLineNumbers="no">
1838 <Instance>
1839 Error executing unknown ICE action. The most common cause of this kind of ICE failure is an incorrectly registered scripting engine. See http://wixtoolset.org/documentation/error217/ for details and how to solve this problem. The following string format was not expected by the external UI message logger: &quot;{0}&quot;.
1840 <Parameter Type="System.String" Name="message" />
1841 </Instance>
1842 <Instance>
1843 Error executing ICE action '{1}'. The most common cause of this kind of ICE failure is an incorrectly registered scripting engine. See http://wixtoolset.org/documentation/error217/ for details and how to solve this problem. The following string format was not expected by the external UI message logger: &quot;{0}&quot;.
1844 <Parameter Type="System.String" Name="message" />
1845 <Parameter Type="System.String" Name="action" />
1846 </Instance>
1847 </Message>
1848 <Message Id="IllegalCabbingThreadCount" Number="218" SourceLineNumbers="no">
1849 <Instance>
1850 Illegal number of threads to create cabinets: '{0}' for -ct &lt;N&gt; command line option. Specify the number of threads to use like -ct 2.
1851 <Parameter Type="System.String" Name="numThreads" />
1852 </Instance>
1853 </Message>
1854 <Message Id="IllegalEnvironmentVariable" Number="219" SourceLineNumbers="no">
1855 <Instance>
1856 The {0} environment variable is set to an invalid value of '{1}'.
1857 <Parameter Type="System.String" Name="environmentVariable" />
1858 <Parameter Type="System.String" Name="value" />
1859 </Instance>
1860 </Message>
1861 <Message Id="InvalidKeyColumn" Number="220" SourceLineNumbers="no">
1862 <Instance>
1863 The definition for the '{0}' table's '{1}' column is an invalid foreign key relationship to the {2} table's column number {3}. It is not a valid foreign key table column number because it is too small (less than 1) or greater than the count of columns in the foreign table's definition.
1864 <Parameter Type="System.String" Name="tableName" />
1865 <Parameter Type="System.String" Name="columnName" />
1866 <Parameter Type="System.String" Name="foreignTableName" />
1867 <Parameter Type="System.Int32" Name="foreignColumnNumber" />
1868 </Instance>
1869 </Message>
1870 <Message Id="CollidingModularizationTypes" Number="221" SourceLineNumbers="no">
1871 <Instance>
1872 The definition for the '{0}' table's '{1}' column is a foreign key relationship to the '{2}' table's column number {3}. The modularization types of the two column definitions differ: one is {4} and the other is {5}. Change one of the modularization types so that they match.
1873 <Parameter Type="System.String" Name="tableName" />
1874 <Parameter Type="System.String" Name="columnName" />
1875 <Parameter Type="System.String" Name="foreignTableName" />
1876 <Parameter Type="System.Int32" Name="foreignColumnNumber" />
1877 <Parameter Type="System.String" Name="modularizationType" />
1878 <Parameter Type="System.String" Name="foreignModularizationType" />
1879 </Instance>
1880 </Message>
1881 <Message Id="CubeFileNotFound" Number="222" SourceLineNumbers="no">
1882 <Instance>
1883 The cube file '{0}' cannot be found. This file is required for MSI validation.
1884 <Parameter Type="System.String" Name="cubeFile" />
1885 </Instance>
1886 </Message>
1887 <Message Id="OpenDatabaseFailed" Number="223" SourceLineNumbers="no">
1888 <Instance>
1889 Failed to open database '{0}'. Ensure it is a valid database, and it is not open by another process.
1890 <Parameter Type="System.String" Name="databaseFile" />
1891 </Instance>
1892 </Message>
1893 <Message Id="OutputTypeMismatch" Number="224">
1894 <Instance>
1895 The types of the outputs do not match. One output's type is '{0}' while the other is '{1}'.
1896 <Parameter Type="System.String" Name="beforeOutputType" />
1897 <Parameter Type="System.String" Name="afterOutputType" />
1898 </Instance>
1899 </Message>
1900 <Message Id="RealTableMissingPrimaryKeyColumn" Number="225">
1901 <Instance>
1902 The table '{0}' does not contain any primary key columns. At least one column must be marked as the primary key to ensure this table can be patched.
1903 <Parameter Type="System.String" Name="tableName" />
1904 </Instance>
1905 </Message>
1906 <Message Id="IllegalColumnName" Number="226">
1907 <Instance>
1908 The {0}/@{1} attribute's value, '{2}', is not a legal column name. It will collide with the sentinel values used in the _TransformView table.
1909 <Parameter Type="System.String" Name="elementName" />
1910 <Parameter Type="System.String" Name="attributeName" />
1911 <Parameter Type="System.String" Name="value" />
1912 </Instance>
1913 </Message>
1914 <Message Id="NoDifferencesInTransform" Number="227">
1915 <Instance>
1916 The transform being built did not contain any differences so it could not be created.
1917 </Instance>
1918 </Message>
1919 <Message Id="OutputCodepageMismatch" Number="228">
1920 <Instance>
1921 The code pages of the outputs do not match. One output's code page is '{0}' while the other is '{1}'.
1922 <Parameter Type="System.Int32" Name="beforeCodepage" />
1923 <Parameter Type="System.Int32" Name="afterCodepage" />
1924 </Instance>
1925 </Message>
1926 <Message Id="OutputCodepageMismatch2" Number="229">
1927 <Instance>
1928 The location of the mismatched code page related to the previous warning.
1929 </Instance>
1930 </Message>
1931 <Message Id="IllegalComponentWithAutoGeneratedGuid" Number="230">
1932 <Instance>
1933 The Component/@Guid attribute's value '*' is not valid for this component because it does not meet the criteria for having an automatically generated guid. Components using a Directory as a KeyPath or containing ODBCDataSource child elements cannot use an automatically generated guid. Make sure your component doesn't have a Directory as the KeyPath and move any ODBCDataSource child elements to components with explicit component guids.
1934 </Instance>
1935 <Instance>
1936 The Component/@Guid attribute's value '*' is not valid for this component because it does not meet the criteria for having an automatically generated guid. Components with registry keypaths and files cannot use an automatically generated guid. Create multiple components, each with one file and/or one registry value keypath, to use automatically generated guids.
1937 <Parameter Type="System.Boolean" Name="registryKeyPath" />
1938 </Instance>
1939 </Message>
1940 <Message Id="IllegalPathForGeneratedComponentGuid" Number="231">
1941 <Instance>
1942 The component '{0}' has a key file with path '{1}'. Since this path is not rooted in one of the standard directories (like ProgramFilesFolder), this component does not fit the criteria for having an automatically generated guid. (This error may also occur if a path contains a likely standard directory such as nesting a directory with name "Common Files" under ProgramFilesFolder.)
1943 <Parameter Type="System.String" Name="componentName" />
1944 <Parameter Type="System.String" Name="keyFilePath" />
1945 </Instance>
1946 </Message>
1947 <Message Id="IllegalTerminalServerCustomActionAttributes" Number="232">
1948 <Instance>
1949 The CustomAction/@TerminalServerAware attribute's value is 'yes' but the Execute attribute is not 'deferred,' 'rollback,' or 'commit.' Terminal-Server-aware custom actions must be deferred, rollback, or commit custom actions. For more information, see http://msdn.microsoft.com/library/aa372071.aspx."
1950 </Instance>
1951 </Message>
1952 <Message Id="IllegalPropertyCustomActionAttributes" Number="233">
1953 <Instance>
1954 The CustomAction sets a property but its Execute attribute is not 'immediate' (the default). Property-setting custom actions cannot be deferred."
1955 </Instance>
1956 </Message>
1957 <Message Id="InvalidPreprocessorFunction" Number="234">
1958 <Instance>
1959 Ill-formed preprocessor function '${0}'. Functions must have a prefix (like 'fun.'), a name at least 1 character long, and matching opening and closing parentheses.
1960 <Parameter Type="System.String" Name="variable" />
1961 </Instance>
1962 </Message>
1963 <Message Id="UndefinedPreprocessorFunction" Number="235">
1964 <Instance>
1965 Undefined preprocessor function '$({0})'.
1966 <Parameter Type="System.String" Name="variableName" />
1967 </Instance>
1968 </Message>
1969 <Message Id="PreprocessorExtensionEvaluateFunctionFailed" Number="236">
1970 <Instance>
1971 In the preprocessor extension that handles prefix '{0}' while trying to call function '{1}({2})' and exception has occurred : {3}
1972 <Parameter Type="System.String" Name="prefix" />
1973 <Parameter Type="System.String" Name="function" />
1974 <Parameter Type="System.String" Name="args" />
1975 <Parameter Type="System.String" Name="message" />
1976 </Instance>
1977 </Message>
1978 <Message Id="PreprocessorExtensionGetVariableValueFailed" Number="237">
1979 <Instance>
1980 In the preprocessor extension that handles prefix '{0}' while trying to get the value for variable '{1}' and exception has occured : {2}
1981 <Parameter Type="System.String" Name="prefix" />
1982 <Parameter Type="System.String" Name="variable" />
1983 <Parameter Type="System.String" Name="message" />
1984 </Instance>
1985 </Message>
1986 <Message Id="InvalidManifestContent" Number="238">
1987 <Instance>
1988 The manifest '{0}' does not have the required assembly/assemblyIdentity element.
1989 <Parameter Type="System.String" Name="fileName" />
1990 </Instance>
1991 </Message>
1992 <Message Id="InvalidWixTransform" Number="239" SourceLineNumbers="no">
1993 <Instance>
1994 The file '{0}' is not a valid WiX Transform.
1995 <Parameter Type="System.String" Name="fileName" />
1996 </Instance>
1997 </Message>
1998 <Message Id="UnexpectedFileExtension" Number="240" SourceLineNumbers="no">
1999 <Instance>
2000 The file '{0}' has an unexpected extension. Expected one of the following: '{1}'.
2001 <Parameter Type="System.String" Name="fileName" />
2002 <Parameter Type="System.String" Name="expectedExtensions" />
2003 </Instance>
2004 </Message>
2005 <Message Id="UnexpectedTableInPatch" Number="241">
2006 <Instance>
2007 An unexpected row in the '{0}' table was found in this patch. Patches cannot contain the '{0}' table.
2008 <Parameter Type="System.String" Name="tableName" />
2009 </Instance>
2010 </Message>
2011 <Message Id="InvalidProductVersion" Number="242">
2012 <Instance>
2013 Invalid product version '{0}'. Product version must have a major version less than 256, a minor version less than 256, and a build version less than 65536.
2014 <Parameter Type="System.String" Name="version" />
2015 </Instance>
2016 <Instance>
2017 Invalid product version '{0}' in package '{1}'. When included in a bundle, all product version fields in an MSI package must be less than 65536.
2018 <Parameter Type="System.String" Name="version" />
2019 <Parameter Type="System.String" Name="packagePath" />
2020 </Instance>
2021 </Message>
2022 <Message Id="InvalidKeypathChange" Number="243">
2023 <Instance>
2024 Component '{0}' has a changed keypath in the transform '{1}'. Patches cannot change the keypath of a component.
2025 <Parameter Type="System.String" Name="component" />
2026 <Parameter Type="System.String" Name="transformPath" />
2027 </Instance>
2028 </Message>
2029 <Message Id="MissingValidatorExtension" Number="244" SourceLineNumbers="no">
2030 <Instance>
2031 The validator requires at least one extension. Add "ValidatorExtension, Wix" for the default implementation.
2032 </Instance>
2033 </Message>
2034 <Message Id="InvalidValidatorMessageType" Number="245" SourceLineNumbers="no">
2035 <Instance>
2036 Unknown validation message type '{0}'.
2037 <Parameter Type="System.String" Name="type" />
2038 </Instance>
2039 </Message>
2040 <Message Id="PatchWithoutTransforms" Number="246" SourceLineNumbers="no">
2041 <Instance>
2042 No transforms were provided to attach to the patch.
2043 </Instance>
2044 </Message>
2045 <Message Id="SingleExtensionSupported" Number="247" SourceLineNumbers="no">
2046 <Instance>
2047 Multiple extensions were specified on the command line, only a single extension is supported.
2048 </Instance>
2049 </Message>
2050 <Message Id="DuplicateTransform" Number="248" SourceLineNumbers="no">
2051 <Instance>
2052 The transform {0} was included twice on the command line. Each transform can be applied to a patch only once.
2053 <Parameter Type="System.String" Name="transform" />
2054 </Instance>
2055 </Message>
2056 <Message Id="BaselineRequired" Number="249" SourceLineNumbers="no">
2057 <Instance>
2058 No baseline was specified for one of the transforms specified. A baseline is required for all transforms in a patch.
2059 </Instance>
2060 </Message>
2061 <Message Id="PreprocessorError" Number="250">
2062 <Instance>
2063 {0}
2064 <Parameter Type="System.String" Name="message" />
2065 </Instance>
2066 </Message>
2067 <Message Id="ExpectedArgument" Number="251" SourceLineNumbers="no">
2068 <Instance>
2069 {0} is expected to be followed by a value argument.
2070 <Parameter Type="System.String" Name="argument" />
2071 </Instance>
2072 </Message>
2073 <Message Id="PatchWithoutValidTransforms" Number="252" SourceLineNumbers="no">
2074 <Instance>
2075 No valid transforms were provided to attach to the patch. Check to make sure the transforms you passed on the command line have a matching baseline authored in the patch. Also, make sure there are differences between your target and upgrade.
2076 </Instance>
2077 </Message>
2078 <Message Id="ExpectedDecompiler" Number="253" SourceLineNumbers="no">
2079 <Instance>
2080 No decompiler was provided. {0} requires a decompiler.
2081 <Parameter Type="System.String" Name="identifier" />
2082 </Instance>
2083 </Message>
2084 <Message Id="ExpectedTableInMergeModule" Number="254" SourceLineNumbers="no">
2085 <Instance>
2086 The table '{0}' was expected but was missing.
2087 <Parameter Type="System.String" Name="identifier" />
2088 </Instance>
2089 </Message>
2090 <Message Id="UnexpectedElementWithAttributeValue" Number="255">
2091 <Instance>
2092 The {0} element cannot have a child element '{1}' unless attribute '{2}' is set to '{3}'.
2093 <Parameter Type="System.String" Name="elementName" />
2094 <Parameter Type="System.String" Name="childElementName" />
2095 <Parameter Type="System.String" Name="attribute" />
2096 <Parameter Type="System.String" Name="attributeValue" />
2097 </Instance>
2098 <Instance>
2099 The {0} element cannot have a child element '{1}' unless attribute '{2}' is set to '{3}' or '{4}'.
2100 <Parameter Type="System.String" Name="elementName" />
2101 <Parameter Type="System.String" Name="childElementName" />
2102 <Parameter Type="System.String" Name="attribute" />
2103 <Parameter Type="System.String" Name="attributeValue1" />
2104 <Parameter Type="System.String" Name="attributeValue2" />
2105 </Instance>
2106 </Message>
2107 <Message Id="ExpectedPatchIdInWixMsp" Number="256" SourceLineNumbers="no">
2108 <Instance>
2109 The WixMsp is missing the patch ID.
2110 </Instance>
2111 </Message>
2112 <Message Id="ExpectedMediaRowsInWixMsp" Number="257" SourceLineNumbers="no">
2113 <Instance>
2114 The WixMsp has no media rows defined.
2115 </Instance>
2116 </Message>
2117 <Message Id="WixFileNotFound" Number="258" SourceLineNumbers="no">
2118 <Instance>
2119 The file '{0}' cannot be found.
2120 <Parameter Type="System.String" Name="file" />
2121 </Instance>
2122 </Message>
2123 <Message Id="ExpectedClientPatchIdInWixMsp" Number="259" SourceLineNumbers="no">
2124 <Instance>
2125 The WixMsp is missing the client patch ID. Recompile the patch source files with the latest WiX toolset.
2126 </Instance>
2127 </Message>
2128 <Message Id="NewRowAddedInTable" Number="260">
2129 <Instance>
2130 Product '{0}': Table '{1}' has a new row '{2}' added. This makes the patch not uninstallable.
2131 <Parameter Type="System.String" Name="productCode" />
2132 <Parameter Type="System.String" Name="tableName" />
2133 <Parameter Type="System.String" Name="rowId" />
2134 </Instance>
2135 </Message>
2136 <Message Id="PatchNotRemovable" Number="261" SourceLineNumbers="no">
2137 <Instance>
2138 This patch is not uninstallable. The 'Patch' element's attribute 'AllowRemoval' should be set to 'no'.
2139 </Instance>
2140 </Message>
2141 <Message Id="PathTooLong" Number="262">
2142 <Instance>
2143 '{0}' is too long, the fully qualified file name must be less than 260 characters, and the directory name must be less than 248 characters.
2144 <Parameter Type="System.String" Name="fileName" />
2145 </Instance>
2146 </Message>
2147 <Message Id="FileTooLarge" Number="263">
2148 <Instance>
2149 '{0}' is too large, file size must be less than 2147483648.
2150 <Parameter Type="System.String" Name="fileName" />
2151 </Instance>
2152 </Message>
2153 <Message Id="InvalidPlatformParameter" Number="264" SourceLineNumbers="no">
2154 <Instance>
2155 The parameter '{0}' is missing or has an invalid value {1}. Possible values are x86, x64, or ia64.
2156 <Parameter Type="System.String" Name="name" />
2157 <Parameter Type="System.String" Name="value" />
2158 </Instance>
2159 </Message>
2160 <Message Id="InvalidPlatformValue" Number="265">
2161 <Instance>
2162 The Platform attribute has an invalid value {0}. Possible values are x86, x64, or ia64.
2163 <Parameter Type="System.String" Name="value" />
2164 </Instance>
2165 </Message>
2166 <Message Id="IllegalValidationArguments" Number="266" SourceLineNumbers="no">
2167 <Instance>
2168 You may only specify a single default type using -t or specify custom validation using -serr and -val.
2169 </Instance>
2170 </Message>
2171 <Message Id="OrphanedComponent" Number="267">
2172 <Instance>
2173 Found orphaned Component '{0}'. If this is a Product, every Component must have at least one parent Feature. To include a Component in a Module, you must include it directly as a Component element of the Module element or indirectly via ComponentRef, ComponentGroup, or ComponentGroupRef elements.
2174 <Parameter Type="System.String" Name="componentName" />
2175 </Instance>
2176 </Message>
2177 <Message Id="IllegalCommandlineArgumentCombination" Number="268" SourceLineNumbers="no">
2178 <Instance>
2179 '-{0}' cannot be specfied in combination with '-{1}'.
2180 <Parameter Type="System.String" Name="arg1" />
2181 <Parameter Type="System.String" Name="arg2" />
2182 </Instance>
2183 </Message>
2184 <Message Id="ProductCodeInvalidForTransform" Number="269">
2185 <Instance>
2186 The value '*' is not valid for the ProductCode when used in a transform or in a patch. Copy the ProductCode from your target product MSI into the Product/@Id attribute value for your product authoring.
2187 </Instance>
2188 </Message>
2189 <Message Id="InsertInvalidSequenceActionOrder" Number="270">
2190 <Instance>
2191 Invalid order of actions {1} and {2} in sequence table {0}. Action {3} must occur after {1} and before {2}, but {2} is currently sequenced after {1}. Please fix the ordering or explicitly supply a location for the action {3}.
2192 <Parameter Type="System.String" Name="sequenceTableName" />
2193 <Parameter Type="System.String" Name="actionNameBefore" />
2194 <Parameter Type="System.String" Name="actionNameAfter" />
2195 <Parameter Type="System.String" Name="actionNameNew" />
2196 </Instance>
2197 </Message>
2198 <Message Id="InsertSequenceNoSpace" Number="271">
2199 <Instance>
2200 Not enough space exists to sequence action {3} in table {0}. It must be sequenced after {1} and before {2}, but those two actions are currently sequenced next to each other. Please move one of those actions to allow {3} to be inserted between them.
2201 <Parameter Type="System.String" Name="sequenceTableName" />
2202 <Parameter Type="System.String" Name="actionNameBefore" />
2203 <Parameter Type="System.String" Name="actionNameAfter" />
2204 <Parameter Type="System.String" Name="actionNameNew" />
2205 </Instance>
2206 </Message>
2207 <Message Id="MissingManifestForWin32Assembly" Number="272">
2208 <Instance>
2209 File '{0}' is marked as a Win32 assembly but it refers to assembly manifest '{1}' that is not present in this product.
2210 <Parameter Type="System.String" Name="file" />
2211 <Parameter Type="System.String" Name="manifest" />
2212 </Instance>
2213 </Message>
2214 <Message Id="UnableToOpenModule" Number="273">
2215 <Instance>
2216 Unable to open merge module '{0}'. Check to make sure the module language is correct. '{1}'
2217 <Parameter Type="System.String" Name="modulePath" />
2218 <Parameter Type="System.String" Name="message" />
2219 </Instance>
2220 </Message>
2221 <Message Id="ExpectedAttributeWhenElementNotUnderElement" Number="274">
2222 <Instance>
2223 The '{0}/@{1}' attribute was not found; it is required when element '{0}' is not nested under a '{2}' element.
2224 <Parameter Type="System.String" Name="elementName" />
2225 <Parameter Type="System.String" Name="attributeName" />
2226 <Parameter Type="System.String" Name="parentElementName" />
2227 </Instance>
2228 </Message>
2229 <Message Id="IllegalIdentifierLooksLikeFormatted" Number="275">
2230 <Instance>
2231 The {0}/@{1} attribute's value, '{2}', is not a legal identifier. The {0}/@{1} attribute does not support formatted string values, such as property names enclosed in brackets ([LIKETHIS]). The value must be the identifier of another element, such as the Directory/@Id attribute value.
2232 <Parameter Type="System.String" Name="elementName" />
2233 <Parameter Type="System.String" Name="attributeName" />
2234 <Parameter Type="System.String" Name="value" />
2235 </Instance>
2236 </Message>
2237 <Message Id="IllegalCodepageAttribute" Number="276">
2238 <Instance>
2239 The code page '{0}' is not a valid Windows code page. Please check the {1}/@{2} attribute value in your source file.
2240 <Parameter Type="System.String" Name="codepage" />
2241 <Parameter Type="System.String" Name="elementName" />
2242 <Parameter Type="System.String" Name="attributeName" />
2243 </Instance>
2244 </Message>
2245 <Message Id="IllegalCompressionLevel" Number="277" SourceLineNumbers="no">
2246 <Instance>
2247 The compression level '{0}' is not valid. Valid values are 'none', 'low', 'medium', 'high', and 'mszip'.
2248 <Parameter Type="System.String" Name="compressionLevel" />
2249 </Instance>
2250 </Message>
2251 <Message Id="TransformSchemaMismatch" Number="278" SourceLineNumbers="no">
2252 <Instance>The transform schema does not match the database schema. The transform may have been generated from a different database.</Instance>
2253 </Message>
2254 <Message Id="DatabaseSchemaMismatch" Number="279">
2255 <Instance>
2256 The table definition of '{0}' in the target database does not match the table definition in the updated database. A transform requires that the target database schema match the updated database schema.
2257 <Parameter Type="System.String" Name="tableName" />
2258 </Instance>
2259 </Message>
2260 <Message Id="ExpectedDirectoryGotFile" Number="280" SourceLineNumbers="no">
2261 <Instance>
2262 The {0} option requires a directory, but the provided path is a file: {1}
2263 <Parameter Type="System.String" Name="option" />
2264 <Parameter Type="System.String" Name="path" />
2265 </Instance>
2266 </Message>
2267 <Message Id="ExpectedFileGotDirectory" Number="281" SourceLineNumbers="no">
2268 <Instance>
2269 The {0} option requires a file, but the provided path is a directory: {1}
2270 <Parameter Type="System.String" Name="option" />
2271 <Parameter Type="System.String" Name="path" />
2272 </Instance>
2273 </Message>
2274 <Message Id="GacAssemblyNoStrongName" Number="282">
2275 <Instance>
2276 Assembly {0} in component {1} has no strong name and has been marked to be placed in the GAC. All assemblies installed to the GAC must have a valid strong name.
2277 <Parameter Type="System.String" Name="assemblyName" />
2278 <Parameter Type="System.String" Name="componentName" />
2279 </Instance>
2280 </Message>
2281 <Message Id="FileWriteError" Number="283" SourceLineNumbers="no">
2282 <Instance>
2283 Error writing to the path: '{0}'. Error message: '{1}'
2284 <Parameter Type="System.String" Name="path" />
2285 <Parameter Type="System.String" Name="error" />
2286 </Instance>
2287 </Message>
2288 <Message Id="InvalidCommandLineFileName" Number="284" SourceLineNumbers="no">
2289 <Instance>
2290 Invalid file name specified on the command line: '{0}'. Error message: '{1}'
2291 <Parameter Type="System.String" Name="fileName" />
2292 <Parameter Type="System.String" Name="error" />
2293 </Instance>
2294 </Message>
2295 <Message Id="ExpectedParentWithAttribute" Number="285">
2296 <Instance>
2297 When the {0}/@{1} attribute is specified, the {0} element must be nested under a {2} element.
2298 <Parameter Type="System.String" Name="parentElement" />
2299 <Parameter Type="System.String" Name="attribute" />
2300 <Parameter Type="System.String" Name="grandparentElement" />
2301 </Instance>
2302 </Message>
2303 <Message Id="IllegalWarningIdAsError" Number="286" SourceLineNumbers="no">
2304 <Instance>
2305 Illegal value '{0}' for the -wx&lt;N&gt; command line option. Specify a particular warning number, like '-wx6' to display the warning with ID 6 as an error, or '-wx' alone to suppress all warnings.
2306 <Parameter Type="System.String" Name="warningId" />
2307 </Instance>
2308 </Message>
2309 <Message Id="ExpectedAttributeOrElement" Number="287">
2310 <Instance>
2311 Element '{0}' missing attribute '{1}' or child element '{2}'. Exactly one of those is required.
2312 <Parameter Type="System.String" Name="parentElement" />
2313 <Parameter Type="System.String" Name="attribute" />
2314 <Parameter Type="System.String" Name="childElement" />
2315 </Instance>
2316 </Message>
2317 <Message Id="DuplicateVariableDefinition" Number="288" SourceLineNumbers="no">
2318 <Instance>
2319 The variable '{0}' with value '{1}' was previously declared with value '{2}'.
2320 <Parameter Type="System.String" Name="variableName" />
2321 <Parameter Type="System.String" Name="variableValue" />
2322 <Parameter Type="System.String" Name="variableCollidingValue" />
2323 </Instance>
2324 </Message>
2325 <Message Id="InvalidVariableDefinition" Number="289" SourceLineNumbers="no">
2326 <Instance>
2327 The variable definition '{0}' is not valid. Variable definitions should be in the form -dname=value where the value is optional.
2328 <Parameter Type="System.String" Name="variableDefinition" />
2329 </Instance>
2330 </Message>
2331 <Message Id="DuplicateCabinetName" Number="290">
2332 <Instance>
2333 Duplicate cabinet name '{0}' found.
2334 <Parameter Type="System.String" Name="cabinetName" />
2335 </Instance>
2336 </Message>
2337 <Message Id="DuplicateCabinetName2" Number="291">
2338 <Instance>
2339 Duplicate cabinet name '{0}' error related to previous error.
2340 <Parameter Type="System.String" Name="cabinetName" />
2341 </Instance>
2342 </Message>
2343 <Message Id="InvalidAddedFileRowWithoutSequence" Number="292">
2344 <Instance>
2345 A row has been added to the File table with id '{1}' that does not have a sequence number assigned to it. Create your transform from a pair of msi's instead of xml outputs to get sequences assigned to your File table's rows.
2346 <Parameter Type="System.String" Name="fileRowId" />
2347 </Instance>
2348 </Message>
2349 <Message Id="DuplicateFileId" Number="293" SourceLineNumbers="no">
2350 <Instance>
2351 Multiple files with ID '{0}' exist. Windows Installer does not support file IDs that differ only by case. Change the file IDs to be unique.
2352 <Parameter Type="System.String" Name="fileId" />
2353 </Instance>
2354 </Message>
2355 <Message Id="FullTempDirectory" Number="294" SourceLineNumbers="no">
2356 <Instance>
2357 Unable to create temporary file. A common cause is that too many files that have names beginning with '{0}' are present. Delete any unneeded files in the '{1}' directory and try again.
2358 <Parameter Type="System.String" Name="prefix" />
2359 <Parameter Type="System.String" Name="directory" />
2360 </Instance>
2361 </Message>
2362 <Message Id="CreateCabAddFileFailed" Number="296" SourceLineNumbers="no">
2363 <Instance>
2364 An error (E_FAIL) was returned while adding files to a CAB file. This most commonly happens when creating a CAB file 2 GB or larger. Either reduce the size of your installation package, raise Media/@CompressionLevel to a higher compression level, or split your installation package's files into more than one CAB file.
2365 </Instance>
2366 </Message>
2367 <Message Id="CreateCabInsufficientDiskSpace" Number="297" SourceLineNumbers="no">
2368 <Instance>
2369 An error (ERROR_DISK_FULL) was returned while creating a CAB file. This means you have insufficient disk space - please clear more disk space and try this operation again.
2370 </Instance>
2371 </Message>
2372 <Message Id="UnresolvedBindReference" Number="298" SourceLineNumbers="yes">
2373 <Instance>
2374 Unresolved bind-time variable {0}.
2375 <Parameter Type="System.String" Name="BindRef" />
2376 </Instance>
2377 </Message>
2378 <Message Id="GACAssemblyIdentityWarning" Number="299" SourceLineNumbers="yes">
2379 <Instance>
2380 The destination name of file '{0}' does not match its assembly name '{1}' in your authoring. This will cause an installation failure for this assembly, because it will be installed to the Global Assembly Cache. To fix this error, update File/@Name of file '{0}' to be the actual name of the assembly.
2381 <Parameter Type="System.String" Name="fileName" />
2382 <Parameter Type="System.String" Name="assemblyName" />
2383 </Instance>
2384 </Message>
2385 <Message Id="IllegalCharactersInPath" Number="300" SourceLineNumbers="no">
2386 <Instance>
2387 Illegal characters in path '{0}'. Ensure you provided a valid path to the file.
2388 <Parameter Type="System.String" Name="pathName" />
2389 </Instance>
2390 </Message>
2391 <Message Id="ValidationFailedToOpenDatabase" Number="301" SourceLineNumbers="no">
2392 <Instance>
2393 Failed to open the database. During validation, this most commonly happens when attempting to open a database using an unsupported code page or a file that is not a valid Windows Installer database. Please use a different code page in Module/@Codepage, Package/@SummaryCodepage, Product/@Codepage, or WixLocalization/@Codepage; or make sure you provide the path to a valid Windows Installer database.
2394 </Instance>
2395 </Message>
2396 <Message Id="MustSpecifyOutputWithMoreThanOneInput" Number="302" SourceLineNumbers="no">
2397 <Instance>
2398 You must specify an output file using the "-o" or "-out" switch when you provide more than one input file.
2399 </Instance>
2400 </Message>
2401 <Message Id="IllegalSearchIdForParentDepth" Number="303">
2402 <Instance>
2403 When the parent DirectorySearch/@Depth attribute is greater than 1 for the DirectorySearch '{1}', the FileSearch/@Id attribute must be absent for FileSearch '{0}' unless the parent DirectorySearch/@AssignToProperty attribute value is 'yes'. Remove the FileSearch/@Id attribute for '{0}' to resolve this issue.
2404 <Parameter Type="System.String" Name="id" />
2405 <Parameter Type="System.String" Name="parentId" />
2406 </Instance>
2407 </Message>
2408 <Message Id="IdentifierTooLongError" Number="304">
2409 <Instance>
2410 The {0}/@{1} attribute's value, '{2}', is too long. {0}/@{1} attribute's must be {3} characters long or less.
2411 <Parameter Type="System.String" Name="elementName" />
2412 <Parameter Type="System.String" Name="attributeName" />
2413 <Parameter Type="System.String" Name="value" />
2414 <Parameter Type="System.Int32" Name="maxLength" />
2415 </Instance>
2416 </Message>
2417 <Message Id="InvalidRemoveComponent" Number="305">
2418 <Instance>
2419 Removing component '{0}' from feature '{1}' is not supported. Either the component was removed or the guid changed in the transform '{2}'. Add the component back, undo the change to the component guid, or remove the entire feature.
2420 <Parameter Type="System.String" Name="component" />
2421 <Parameter Type="System.String" Name="feature" />
2422 <Parameter Type="System.String" Name="transformPath" />
2423 </Instance>
2424 </Message>
2425 <Message Id="FinishCabFailed" Number="306" SourceLineNumbers="no">
2426 <Instance>
2427 An error (E_FAIL) was returned while finalizing a CAB file. This most commonly happens when creating a CAB file with more than 65535 files in it. Either reduce the number of files in your installation package or split your installation package's files into more than one CAB file using the Media element.
2428 </Instance>
2429 </Message>
2430 <Message Id="InvalidExtensionType" Number="307" SourceLineNumbers="no">
2431 <Instance>
2432 Either '{1}' was not defined in the assembly or the type defined in extension '{0}' could not be loaded.
2433 <Parameter Type="System.String" Name="extension" />
2434 <Parameter Type="System.String" Name="attributeType" />
2435 </Instance>
2436 <Instance>
2437 The extension type '{1}' in extension '{0}' does not inherit from the expected class '{2}'.
2438 <Parameter Type="System.String" Name="extension" />
2439 <Parameter Type="System.String" Name="className" />
2440 <Parameter Type="System.String" Name="expectedType" />
2441 </Instance>
2442 <Instance>
2443 The type '{1}' in extension '{0}' could not be loaded. Exception type '{2}' returned the following message: {3}
2444 <Parameter Type="System.String" Name="extension" />
2445 <Parameter Type="System.String" Name="className" />
2446 <Parameter Type="System.String" Name="exceptionType" />
2447 <Parameter Type="System.String" Name="exceptionMessage"/>
2448 </Instance>
2449 </Message>
2450 <Message Id="ValidationFailedDueToMultilanguageMergeModule" Number="309" SourceLineNumbers="no">
2451 <Instance>
2452 Failed to open merge module for validation. The most common cause of this error is specifying that the merge module supports multiple languages (using the Package/@Languages attribute) but not including language-specific embedded transforms. To fix this error, make the merge module language-neutral, make it language-specific, embed language transforms as specified in the MSI SDK at http://msdn.microsoft.com/library/aa367799.aspx, or disable validation.
2453 </Instance>
2454 </Message>
2455 <Message Id="ValidationFailedDueToInvalidPackage" Number="310" SourceLineNumbers="no">
2456 <Instance>
2457 Failed to open package for validation. The most common cause of this error is validating an x64 package on an x86 system. To fix this error, run validation on an x64 system or disable validation.
2458 </Instance>
2459 </Message>
2460 <Message Id="InvalidStringForCodepage" Number="311">
2461 <Instance>
2462 A string was provided with characters that are not available in the specified database code page '{0}'. Either change these characters to ones that exist in the database's code page, or update the database's code page by modifying one of the following attributes: Product/@Codepage, Module/@Codepage, Patch/@Codepage, PatchCreation/@Codepage, or WixLocalization/@Codepage.
2463 <Parameter Type="System.String" Name="codepage" />
2464 </Instance>
2465 </Message>
2466 <Message Id="InvalidEmbeddedUIFileName" Number="312">
2467 <Instance>
2468 The EmbeddedUI/@Name attribute value, '{0}', does not contain an extension. Windows Installer will not load an embedded UI DLL without an extension. Include an extension or just omit the Name attribute so it defaults to the file name portion of the Source attribute value.
2469 <Parameter Type="System.String" Name="codepage" />
2470 </Instance>
2471 </Message>
2472 <Message Id="UniqueFileSearchIdRequired" Number="313">
2473 <Instance>
2474 The DirectorySearch element '{0}' requires that the child {1} element has a unique Id when the DirectorySearch/@AssignToProperty attribute is set to 'yes'.
2475 <Parameter Type="System.String" Name="id" />
2476 <Parameter Type="System.String" Name="elementName" />
2477 </Instance>
2478 </Message>
2479 <Message Id="IllegalAttributeValueWhenNested" Number="314">
2480 <Instance>
2481 The {0}/@{1} attribute value, '{2}', cannot be specified when the {0} element is nested underneath a {3} element.
2482 <Parameter Type="System.String" Name="elementName" />
2483 <Parameter Type="System.String" Name="attributeName" />
2484 <Parameter Type="System.String" Name="attrivuteValue" />
2485 <Parameter Type="System.String" Name="parentElementName" />
2486 </Instance>
2487 </Message>
2488 <Message Id="AdminImageRequired" Number="315" SourceLineNumbers="no">
2489 <Instance>
2490 Source information is required for the product '{0}'. If you ran torch.exe with both target and updated .msi files, you must first perform an administrative installation of both .msi files then pass -a when running torch.exe.
2491 <Parameter Type="System.String" Name="productCode" />
2492 </Instance>
2493 </Message>
2494 <Message Id="SamePatchBaselineId" Number="316">
2495 <Instance>
2496 The PatchBaseline/@Id attribute value '{0}' is a child of multiple Media elements. This prevents transforms from being resolved to distinct media. Change the PatchBaseline/@Id attribute values to be unique.
2497 <Parameter Type="System.String" Name="id" />
2498 </Instance>
2499 </Message>
2500 <Message Id="SameFileIdDifferentSource" Number="317">
2501 <Instance>
2502 Two different source paths '{1}' and '{2}' were detected for the same file identifier '{0}'. You must either author these under Media elements with different Id attribute values or in different patches.
2503 <Parameter Type="System.String" Name="fileId" />
2504 <Parameter Type="System.String" Name="sourcePath1" />
2505 <Parameter Type="System.String" Name="sourcePath2" />
2506 </Instance>
2507 </Message>
2508 <Message Id="HarvestSourceNotSpecified" Number="318" SourceLineNumbers="no">
2509 <Instance>
2510 A harvest source must be specified after the harvest type and can be followed by harvester arguments.
2511 </Instance>
2512 </Message>
2513 <Message Id="OutputTargetNotSpecified" Number="319" SourceLineNumbers="no">
2514 <Instance>
2515 The '-out' or '-o' parameter must specify a file path.
2516 </Instance>
2517 </Message>
2518 <Message Id="DuplicateCommandLineOptionInExtension" Number="320" SourceLineNumbers="no">
2519 <Instance>
2520 The command line option '{0}' has already been loaded by another Heat extension.
2521 <Parameter Type="System.String" Name="switch" />
2522 </Instance>
2523 </Message>
2524 <Message Id="HarvestTypeNotFound" Number="321" SourceLineNumbers="no">
2525 <Instance>
2526 The harvest type was not found in the list of loaded Heat extensions.
2527 </Instance>
2528 <Instance>
2529 The harvest type '{0}' was specified. Harvest types cannot start with a '-'. Remove the '-' to specify a valid harvest type.
2530 <Parameter Type="System.String" Name="harvestType" />
2531 </Instance>
2532 </Message>
2533 <Message Id="BothUpgradeCodesRequired" Number="322" SourceLineNumbers="no">
2534 <Instance>
2535 Both the target and updated product authoring must define the Product/@UpgradeCode attribute if the transform validates the UpgradeCode (default). Either define the Product/@UpgradeCode attribute in both the target and updated authoring, or set the Validate/@UpgradeCode attribute to 'no' in the patch authoring.
2536 </Instance>
2537 </Message>
2538 <Message Id="IllegalBinderClassName" Number="323" SourceLineNumbers="no">
2539 <Instance>
2540 Illegal binder class name specified for -binder command line option.
2541 </Instance>
2542 </Message>
2543 <Message Id="SpecifiedBinderNotFound" Number="324" SourceLineNumbers="no">
2544 <Instance>
2545 The specified binder class '{0}' was not found in any extensions.
2546 <Parameter Type="System.String" Name="binderClass" />
2547 </Instance>
2548 </Message>
2549 <Message Id="CannotLoadBinderFileManager" Number="325" SourceLineNumbers="no">
2550 <Instance>
2551 Cannot load binder file manager: {0}. Light can only load one binder file manager and has already loaded binder file manager: {1}.
2552 <Parameter Type="System.String" Name="binderFileManager" />
2553 <Parameter Type="System.String" Name="currentBinderFileManager" />
2554 </Instance>
2555 </Message>
2556 <Message Id="CannotLoadLinkerExtension" Number="326" SourceLineNumbers="no">
2557 <Instance>
2558 Cannot load linker extension: {0}. Light can only load one link extension and has already loaded link extension: {1}.
2559 <Parameter Type="System.String" Name="linkerExtension" />
2560 <Parameter Type="System.String" Name="currentLinkerExtension" />
2561 </Instance>
2562 </Message>
2563 <Message Id="UnableToGetAuthenticodeCertOfFile" Number="327" SourceLineNumbers="no">
2564 <Instance>
2565 Unable to get the authenticode certificate of '{0}'. More information: {1}
2566 <Parameter Type="System.String" Name="filePath" />
2567 <Parameter Type="System.String" Name="moreInformation" />
2568 </Instance>
2569 </Message>
2570 <Message Id="UnableToGetAuthenticodeCertOfFileDownlevelOS" Number="328" SourceLineNumbers="no">
2571 <Instance>
2572 Unable to get the authenticode certificate of '{0}'. The cryptography API has limitations on Windows XP and Windows Server 2003. More information: {1}
2573 <Parameter Type="System.String" Name="filePath" />
2574 <Parameter Type="System.String" Name="moreInformation" />
2575 </Instance>
2576 </Message>
2577 <Message Id="ReadOnlyOutputFile" Number="329" SourceLineNumbers="no">
2578 <Instance>
2579 Unable to output to file '{0}' because it is marked as read-only.
2580 <Parameter Type="System.String" Name="filePath" />
2581 </Instance>
2582 </Message>
2583 <Message Id="CannotDefaultComponentId" Number="330">
2584 <Instance>
2585 The Component/@Id attribute was not found; it is required when there is no valid keypath to use as the default id value.
2586 </Instance>
2587 </Message>
2588 <Message Id="ParentElementAttributeRequired" Number="331">
2589 <Instance>
2590 The parent {0} element is missing the {1} attribute that is required for the {2} child element.
2591 <Parameter Type="System.String" Name="parentElement" />
2592 <Parameter Type="System.String" Name="parentAttribute" />
2593 <Parameter Type="System.String" Name="childElement" />
2594 </Instance>
2595 </Message>
2596 <Message Id="PreprocessorExtensionPragmaFailed" Number="333">
2597 <Instance>
2598 Exception thrown while processing pragma '{0}'. The exception's message is: {1}
2599 <Parameter Type="System.String" Name="pragma" />
2600 <Parameter Type="System.String" Name="message" />
2601 </Instance>
2602 </Message>
2603 <Message Id="InvalidPreprocessorPragma" Number="334">
2604 <Instance>
2605 Malformed preprocessor pragma '{0}'. Pragmas must have a prefix, a name of at least 1 character long, and be followed by optional arguments.
2606 <Parameter Type="System.String" Name="variable" />
2607 </Instance>
2608 </Message>
2609 <Message Id="SmokeUnknownFileExtension" Number="335" SourceLineNumbers="no">
2610 <Instance>
2611 Unknown input file format - expected a .msi or .msm file.
2612 </Instance>
2613 </Message>
2614 <Message Id="SmokeUnsupportedFileExtension" Number="336" SourceLineNumbers="no">
2615 <Instance>
2616 Files with an extension of .msp are not currently supported.
2617 </Instance>
2618 </Message>
2619 <Message Id="SmokeMalformedPath" Number="337" SourceLineNumbers="no">
2620 <Instance>
2621 Path contains one or more invalid characters.
2622 </Instance>
2623 </Message>
2624 <Message Id="InvalidStubExe" Number="338" SourceLineNumbers="no">
2625 <Instance>
2626 Stub executable '{0}' is not a valid Win32 executable.
2627 <Parameter Type="System.String" Name="filename" />
2628 </Instance>
2629 </Message>
2630 <Message Id="StubMissingWixburnSection" Number="339" SourceLineNumbers="no">
2631 <Instance>
2632 Stub executable '{0}' does not contain a .wixburn data section.
2633 <Parameter Type="System.String" Name="filename" />
2634 </Instance>
2635 </Message>
2636 <Message Id="StubWixburnSectionTooSmall" Number="340" SourceLineNumbers="no">
2637 <Instance>
2638 Stub executable '{0}' .wixburn data section is too small to store the Burn container header.
2639 <Parameter Type="System.String" Name="filename" />
2640 </Instance>
2641 </Message>
2642 <Message Id="MissingBundleInformation" Number="341" SourceLineNumbers="no">
2643 <Instance>
2644 The Bundle is missing '{0}' data, and cannot continue.
2645 <Parameter Type="System.String" Name="data" />
2646 </Instance>
2647 </Message>
2648 <Message Id="UnexpectedGroupChild" Number="342" SourceLineNumbers="no">
2649 <Instance>
2650 A group parent ('{0}'/'{1}') had an unexpected child ('{2}'/'{3}').
2651 <Parameter Type="System.String" Name="parentType" />
2652 <Parameter Type="System.String" Name="parentId" />
2653 <Parameter Type="System.String" Name="childType" />
2654 <Parameter Type="System.String" Name="childId" />
2655 </Instance>
2656 </Message>
2657 <Message Id="OrderingReferenceLoopDetected" Number="343">
2658 <Instance>
2659 A circular reference of ordering dependencies was detected. The infinite loop includes: {0}. Ordering dependency references must form a directed acyclic graph.
2660 <Parameter Type="System.String" Name="loopList" />
2661 </Instance>
2662 </Message>
2663 <Message Id="IdentifierNotFound" Number="344" SourceLineNumbers="no">
2664 <Instance>
2665 An expected identifier ('{1}', of type '{0}') was not found.
2666 <Parameter Type="System.String" Name="type" />
2667 <Parameter Type="System.String" Name="identifier" />
2668 </Instance>
2669 </Message>
2670 <Message Id="MergePlatformMismatch" Number="345">
2671 <Instance>
2672 '{0}' is a 64-bit merge module but the product consuming it is 32-bit. 32-bit products can consume only 32-bit merge modules.
2673 <Parameter Type="System.String" Name="mergeModuleFile" />
2674 </Instance>
2675 </Message>
2676 <Message Id="IllegalRelativeLongFilename" Number="346">
2677 <Instance>
2678 The {0}/@{1} attribute's value, '{2}', is not a valid relative long name because it contains illegal characters. Legal relative long names contain no more than 260 characters and must contain at least one non-period character. Any character except for the follow may be used: ? | &gt; &lt; : / * ".
2679 <Parameter Type="System.String" Name="elementName" />
2680 <Parameter Type="System.String" Name="attributeName" />
2681 <Parameter Type="System.String" Name="value" />
2682 </Instance>
2683 </Message>
2684 <Message Id="IllegalAttributeValueWithLegalList" Number="347">
2685 <Instance>
2686 The {0}/@{1} attribute's value, '{2}', is not one of the legal options: {3}.
2687 <Parameter Type="System.String" Name="elementName" />
2688 <Parameter Type="System.String" Name="attributeName" />
2689 <Parameter Type="System.String" Name="value" />
2690 <Parameter Type="System.String" Name="legalValueList" />
2691 </Instance>
2692 </Message>
2693 <Message Id="IllegalAttributeValueWithIllegalList" Number="348">
2694 <Instance>
2695 The {0}/@{1} attribute's value, '{2}', is one of the illegal options: {3}.
2696 <Parameter Type="System.String" Name="elementName" />
2697 <Parameter Type="System.String" Name="attributeName" />
2698 <Parameter Type="System.String" Name="value" />
2699 <Parameter Type="System.String" Name="illegalValueList" />
2700 </Instance>
2701 </Message>
2702
2703 <Message Id="InvalidSummaryInfoCodePage" Number="349">
2704 <Instance>
2705 The code page '{0}' is invalid for summary information. You must specify an ANSI code page.
2706 <Parameter Type="System.Int32" Name="codePage" />
2707 </Instance>
2708 </Message>
2709 <Message Id="ValidationFailedDueToLowMsiEngine" Number="350" SourceLineNumbers="no">
2710 <Instance>
2711 The package being validated requires a higher version of Windows Installer than is installed on this machine. Validation cannot continue.
2712 </Instance>
2713 </Message>
2714 <Message Id="DuplicateSourcesForOutput" Number="351" SourceLineNumbers="no">
2715 <Instance>
2716 Multiple source files ({0}) have resulted in the same output file '{1}'. This is likely because the source files only differ in extension or path. Rename the source files to avoid this problem.
2717 <Parameter Type="System.String" Name="sourceList" />
2718 <Parameter Type="System.String" Name="outputFile" />
2719 </Instance>
2720 </Message>
2721 <Message Id="UnableToReadPackageInformation" Number="352">
2722 <Instance>
2723 Unable to read package '{0}'. {1}
2724 <Parameter Type="System.String" Name="packagePath" />
2725 <Parameter Type="System.String" Name="detailedErrorMessage" />
2726 </Instance>
2727 </Message>
2728 <Message Id="MultipleFilesMatchedWithOutputSpecification" Number="353" SourceLineNumbers="no">
2729 <Instance>
2730 A per-source file output specification has been provided ('{0}'), but multiple source files match the source specification ({1}). Specifying a unique output requires that only a single source file match.
2731 <Parameter Type="System.String" Name="sourceSpecification" />
2732 <Parameter Type="System.String" Name="sourceList" />
2733 </Instance>
2734 </Message>
2735 <Message Id="InvalidBundle" Number="354" SourceLineNumbers="no">
2736 <Instance>
2737 Unable to read bundle executable '{0}'. This is not a valid WiX bundle.
2738 <Parameter Type="System.String" Name="bundleExecutable" />
2739 </Instance>
2740 </Message>
2741 <Message Id="BundleTooNew" Number="355" SourceLineNumbers="no">
2742 <Instance>
2743 Unable to read bundle executable '{0}', because this bundle was created with a newer version of WiX (bundle version '{1}'). You must use a newer version of WiX in order to read this bundle.
2744 <Parameter Type="System.String" Name="bundleExecutable" />
2745 <!-- we use a 64-bit field here because the field is really a 32-bit UInt,
2746 but UInt gives a non-CLS-compliant warning.
2747 So 64-bit makes sure we don't drop the last bit -->
2748 <Parameter Type="System.Int64" Name="bundleVersion" />
2749 </Instance>
2750 </Message>
2751 <Message Id="WrongFileExtensionForNumberOfInputs" Number="356" SourceLineNumbers="no">
2752 <Instance>
2753 The extension '{0}' on the input specified '{1}' does not match the number of inputs required to handle an input with this extension. Check if you are missing an input or have too many.
2754 <Parameter Type="System.String" Name="inputExtension" />
2755 <Parameter Type="System.String" Name="input" />
2756 </Instance>
2757 </Message>
2758 <Message Id="MediaTableCollision" Number="357">
2759 <Instance>
2760 Only one of Media and MediaTemplate tables should be authored.
2761 </Instance>
2762 </Message>
2763 <Message Id="InvalidCabinetTemplate" Number="358">
2764 <Instance>
2765 CabinetTemplate attribute's value '{0}' must contain '{{0}}' and should contain no more than 8 characters followed by an optional extension of no more than 3 characters. Any character except for the follow may be used: \ ? | &gt; &lt; : / * " + , ; = [ ] (space). The Windows Installer team has recommended following the 8.3 format for external cabinet files and any other naming scheme is officially unsupported (which means it is not guaranteed to work on all platforms).
2766 <Parameter Type="System.String" Name="cabinetTemplate" />
2767 </Instance>
2768 </Message>
2769 <Message Id="MaximumUncompressedMediaSizeTooLarge" Number="359">
2770 <Instance>
2771 '{0}' is too large. Reduce the size of maximum uncompressed media size.
2772 <Parameter Type="System.Int32" Name="maximumUncompressedMediaSize" />
2773 </Instance>
2774 </Message>
2775 <Message Id="CatalogVerificationFailed" Number="360" SourceLineNumbers="no">
2776 <Instance>
2777 File '{0}' could not be verified with a catalog file.
2778 <Parameter Type="System.String" Name="fileName" />
2779 </Instance>
2780 </Message>
2781 <Message Id="CatalogFileHashFailed" Number="361" SourceLineNumbers="no">
2782 <Instance>
2783 Could not get hash of file '{0}'. Error: {2}.
2784 <Parameter Type="System.String" Name="fileName" />
2785 <Parameter Type="System.Int32" Name="errorCode" />
2786 </Instance>
2787 </Message>
2788 <Message Id="ReservedNamespaceViolation" Number="362">
2789 <Instance>
2790 The {0}/@{1} attribute's value begins with the reserved prefix '{2}'. Some prefixes are reserved by the Windows Installer and WiX toolset for well-known values. Change your attribute's value to not begin with the same prefix.
2791 <Parameter Type="System.String" Name="element" />
2792 <Parameter Type="System.String" Name="attribute" />
2793 <Parameter Type="System.String" Name="prefix" />
2794 </Instance>
2795 </Message>
2796 <Message Id="PerUserButAllUsersEquals1" Number="363">
2797 <Instance>
2798 The MSI '{0}' is explicitly marked to not elevate so it must be a per-user package but the ALLUSERS Property is set to '1' creating a per-machine package. Remove the Property with Id='ALLUSERS' and use Package/@InstallScope attribute to be explicit instead.
2799 <Parameter Type="System.String" Name="path" />
2800 </Instance>
2801 </Message>
2802 <Message Id="UnsupportedAllUsersValue" Number="364">
2803 <Instance>
2804 The MSI '{0}' set the ALLUSERS Property to '{0}' which is not supported. Remove the Property with Id='ALLUSERS' and use Package/@InstallScope attribute instead.
2805 <Parameter Type="System.String" Name="path" />
2806 <Parameter Type="System.String" Name="value" />
2807 </Instance>
2808 </Message>
2809 <Message Id="DisallowedMsiProperty" Number="365">
2810 <Instance>
2811 The '{0}' MsiProperty is controlled by the bootstrapper and cannot be authored. (Illegal properties are: {1}.) Remove the MsiProperty element.
2812 <Parameter Type="System.String" Name="property" />
2813 <Parameter Type="System.String" Name="illegalValueList" />
2814 </Instance>
2815 </Message>
2816 <Message Id="MissingOrInvalidModuleInstallerVersion" Number="366">
2817 <Instance>
2818 The merge module '{0}' from file '{1}' is either missing or has an invalid installer version. The value read from the installer version in module's summary information was '{2}'. This should be a numeric value representing a valid installer version such as 200 or 301.
2819 <Parameter Type="System.String" Name="moduleId" />
2820 <Parameter Type="System.String" Name="mergeModuleFile" />
2821 <Parameter Type="System.String" Name="productInstallerVersion" />
2822 </Instance>
2823 </Message>
2824 <Message Id="IllegalGeneratedGuidComponentUnversionedKeypath" Number="367">
2825 <Instance>
2826 The Component/@Guid attribute's value '*' is not valid for this component because it does not meet the criteria for having an automatically generated guid. Components with more than one file cannot use an automatically generated guid unless a versioned file is the keypath and the other files are unversioned. This component's keypath is not versioned. Create multiple components to use automatically generated guids.
2827 </Instance>
2828 </Message>
2829 <Message Id="IllegalGeneratedGuidComponentVersionedNonkeypath" Number="368">
2830 <Instance>
2831 The Component/@Guid attribute's value '*' is not valid for this component because it does not meet the criteria for having an automatically generated guid. Components with more than one file cannot use an automatically generated guid unless a versioned file is the keypath and the other files are unversioned. This component has a non-keypath file that is versioned. Create multiple components to use automatically generated guids.
2832 </Instance>
2833 </Message>
2834 <Message Id="DuplicateComponentGuids" Number="369">
2835 <Instance>
2836 Component/@Id='{0}' has a @Guid value '{1}' that duplicates another component in this package. It is recommended to give each component its own unique GUID.
2837 <Parameter Type="System.String" Name="componentId" />
2838 <Parameter Type="System.String" Name="guid" />
2839 </Instance>
2840 </Message>
2841 <Message Id="DuplicateProviderDependencyKey" Number="370" SourceLineNumbers="no">
2842 <Instance>
2843 The provider dependency key '{0}' was already imported from the package with Id '{1}'. Please remove the Provides element with the key '{0}' from the package authoring.
2844 <Parameter Type="System.String" Name="providerKey" />
2845 <Parameter Type="System.String" Name="packageId" />
2846 </Instance>
2847 </Message>
2848 <Message Id="MissingDependencyVersion" Number="371" SourceLineNumbers="no">
2849 <Instance>
2850 The provider dependency version was not authored for the package with Id '{0}'. Please author the Provides/@Version attribute for this package.
2851 <Parameter Type="System.String" Name="packageId" />
2852 </Instance>
2853 </Message>
2854
2855 <Message Id="UnexpectedElementWithAttribute" Number="372">
2856 <Instance>
2857 The {0} element cannot have a child element '{1}' when attribute '{2}' is set.
2858 <Parameter Type="System.String" Name="elementName" />
2859 <Parameter Type="System.String" Name="childElementName" />
2860 <Parameter Type="System.String" Name="attribute" />
2861 </Instance>
2862 </Message>
2863 <Message Id="ExpectedAttributeWithElement" Number="373">
2864 <Instance>
2865 The {0} element must have attribute '{1}' when child element '{2}' is present.
2866 <Parameter Type="System.String" Name="elementName" />
2867 <Parameter Type="System.String" Name="attribute" />
2868 <Parameter Type="System.String" Name="childElementName" />
2869 </Instance>
2870 </Message>
2871 <Message Id="DuplicatedUiLocalization" Number="374">
2872 <Instance>
2873 The localization for control {0} in dialog {1} is duplicated. Only one localization per control is allowed.
2874 <Parameter Type="System.String" Name="controlName" />
2875 <Parameter Type="System.String" Name="dialogName" />
2876 </Instance>
2877 <Instance>
2878 The localization for dialog {0} is duplicated. Only one localization per dialog is allowed.
2879 <Parameter Type="System.String" Name="dialogName" />
2880 </Instance>
2881 </Message>
2882 <Message Id="MaximumCabinetSizeForLargeFileSplittingTooLarge" Number="375">
2883 <Instance>
2884 '{0}' is too large. Reduce the size of maximum cabinet size for large file splitting. The maximum permitted value is '{1}' MB.
2885 <Parameter Type="System.Int32" Name="maximumCabinetSizeForLargeFileSplitting" />
2886 <Parameter Type="System.Int32" Name="maxValueOfMaxCabSizeForLargeFileSplitting" />
2887 </Instance>
2888 </Message>
2889 <Message Id="SplitCabinetCopyRegistrationFailed" Number="376" SourceLineNumbers="no">
2890 <Instance>
2891 Failed to register the copy command for cabinet '{0}' formed by splitting cabinet '{1}'.
2892 <Parameter Type="System.String" Name="newCabName" />
2893 <Parameter Type="System.String" Name="firstCabName" />
2894 </Instance>
2895 </Message>
2896 <Message Id="SplitCabinetNameCollision" Number="377" SourceLineNumbers="no">
2897 <Instance>
2898 The cabinet name '{0}' collides with the new cabinet formed by splitting cabinet '{1}', consider renaming cabinet '{0}'.
2899 <Parameter Type="System.String" Name="newCabName" />
2900 <Parameter Type="System.String" Name="firstCabName" />
2901 </Instance>
2902 </Message>
2903 <Message Id="SplitCabinetInsertionFailed" Number="378" SourceLineNumbers="no">
2904 <Instance>
2905 Could not find the last split cabinet '{2}' in the Media Table. So failed to add new cabinet '{0}' formed by splitting cabinet '{1}' to the installer package.
2906 <Parameter Type="System.String" Name="newCabName" />
2907 <Parameter Type="System.String" Name="firstCabName" />
2908 <Parameter Type="System.String" Name="lastCabinetOfThisSequence" />
2909 </Instance>
2910 </Message>
2911 <Message Id="InvalidPreprocessorFunctionAutoVersion" Number="379">
2912 <Instance>
2913 Invalid AutoVersion template specified.
2914 </Instance>
2915 </Message>
2916 <Message Id="InvalidModuleOrBundleVersion" Number="380">
2917 <Instance>
2918 Invalid {0}/@Version '{1}'. {0} version has a max value of "65535.65535.65535.65535" and must be all numeric.
2919 <Parameter Type="System.String" Name="moduleOrBundle" />
2920 <Parameter Type="System.String" Name="version" />
2921 </Instance>
2922 </Message>
2923 <Message Id="UnsupportedPlatformForElement" Number="381">
2924 <Instance>
2925 The element {1} does not support platform '{0}'. Consider removing the element or using the preprocessor to conditionally include the element based on the platform.
2926 <Parameter Type="System.String" Name="platform" />
2927 <Parameter Type="System.String" Name="elementName" />
2928 </Instance>
2929 </Message>
2930 <Message Id="MissingMedia" Number="382">
2931 <Instance>
2932 There is no media defined for disk id '{0}'. You must author either &lt;Media Id='{0}' ...&gt; or &lt;MediaTemplate ...&gt;.
2933 <Parameter Type="System.Int32" Name="diskId" />
2934 </Instance>
2935 </Message>
2936 <Message Id="RemotePayloadUnsupported" Number="383">
2937 <Instance>
2938 The RemotePayload element can only be used for ExePackage and MsuPackage payloads.
2939 </Instance>
2940 </Message>
2941 <Message Id="IllegalYesNoAlwaysValue" Number="384">
2942 <Instance>
2943 The {0}/@{1} attribute's value, '{2}', is not a legal yes/no/always value. The only legal values are 'always', 'no' or 'yes'.
2944 <Parameter Type="System.String" Name="elementName" />
2945 <Parameter Type="System.String" Name="attributeName" />
2946 <Parameter Type="System.String" Name="value" />
2947 </Instance>
2948 </Message>
2949 <Message Id="TooDeeplyIncluded" Number="385">
2950 <Instance>
2951 Include files cannot be nested more deeply than {0} times. Make sure included files don't accidentally include themselves.
2952 <Parameter Type="System.Int32" Name="depth" />
2953 </Instance>
2954 </Message>
2955 <Message Id="InlineDirectorySyntaxRequiresPath" Number="387">
2956 <Instance>
2957 The {0}/@{1} attribute's value '{2}' only specifies a directory reference. The inline directory syntax requires that at least one directory be specified in addition to the value. For example, use '{3}:\foo\' to add a 'foo' directory.
2958 <Parameter Type="System.String" Name="elementName" />
2959 <Parameter Type="System.String" Name="attributeName" />
2960 <Parameter Type="System.String" Name="value" />
2961 <Parameter Type="System.String" Name="identifier" />
2962 </Instance>
2963 </Message>
2964 <Message Id="InsecureBundleFilename" Number="388" SourceLineNumbers="no">
2965 <Instance>
2966 The file name '{0}' creates an insecure bundle. Windows will load unnecessary compatibility shims into a bundle with that file name. These compatibility shims can be DLL hijacked allowing attackers to compromise your customers' computer. Choose a different bundle file name.
2967 <Parameter Type="System.String" Name="filename" />
2968 </Instance>
2969 </Message>
2970 <Message Id="PayloadMustBeRelativeToCache" Number="389">
2971 <Instance>
2972 The {0}/@{1} attribute's value, '{2}', is not a legal path name: Payload names must be relative to their cache directory and cannot contain '..'.
2973 <Parameter Type="System.String" Name="elementName" />
2974 <Parameter Type="System.String" Name="attributeName" />
2975 <Parameter Type="System.String" Name="attributeValue" />
2976 </Instance>
2977 </Message>
2978 <Message Id="MsiTransactionX86BeforeX64" Number="390">
2979 <Instance>
2980 MSI transactions must install all x64 packages before any x86 package.
2981 </Instance>
2982 </Message>
2983 </Class>
2984
2985 <Class Name="WixWarnings" ContainerName="WixWarningEventArgs" BaseContainerName="MessageEventArgs" Level="Warning">
2986 <Message Id="IdentifierCannotBeModularized" Number="1000">
2987 <Instance>
2988 The {0}/@{1} attribute's value, '{2}', is {3} characters long. It will be too long if modularized. The identifier shouldn't be longer than {4} characters long to allow for modularization (appending a guid for merge modules).
2989 <Parameter Type="System.String" Name="elementName" />
2990 <Parameter Type="System.String" Name="attributeName" />
2991 <Parameter Type="System.String" Name="identifier" />
2992 <Parameter Type="System.Int32" Name="length" />
2993 <Parameter Type="System.Int32" Name="maximumLength" />
2994 </Instance>
2995 </Message>
2996 <Message Id="EmptyAttributeValue" Number="1001">
2997 <Instance>
2998 The {0}/@{1} attribute's value cannot be an empty string. If you want the value to be null or empty, simply remove the entire attribute.
2999 <Parameter Type="System.String" Name="elementName" />
3000 <Parameter Type="System.String" Name="attributeName" />
3001 </Instance>
3002 </Message>
3003 <Message Id="UnableToFindFileFromCabOrImage" Number="1002">
3004 <Instance>
3005 Unable to find existing file {0} to place in src location {1}. Will likely cause a linker break.
3006 <Parameter Type="System.String" Name="existingFileSpec" />
3007 <Parameter Type="System.String" Name="srcFileSpec" />
3008 </Instance>
3009 </Message>
3010 <Message Id="CopyFileFileIdUseless" Number="1003">
3011 <Instance>Since the CopyFile/@FileId attribute was specified but none of the following attributes (DestinationName, DestinationDirectory, DestinationProperty) were specified, this authoring will not do anything.</Instance>
3012 </Message>
3013 <Message Id="NestedInstall" Number="1004">
3014 <Instance>
3015 The {0}.{1} column's value, '{2}', indicates a nested install. Nested installations are not supported by the WiX team. This action will be left out of the decompiled output.
3016 <Parameter Type="System.String" Name="tableName" />
3017 <Parameter Type="System.String" Name="columnName" />
3018 <Parameter Type="System.Object" Name="value" />
3019 </Instance>
3020 </Message>
3021 <Message Id="OrphanedProgId" Number="1005">
3022 <Instance>
3023 ProgId '{0}' is orphaned. It has no associated component, so it will never install. Every ProgId should have either a parent Class element or child Extension element (at any distance).
3024 <Parameter Type="System.String" Name="progId" />
3025 </Instance>
3026 </Message>
3027 <Message Id="PropertyUseless" Number="1006">
3028 <Instance>
3029 Property '{0}' does not contain a Value attribute and is not marked as Admin, Secure, or Hidden. The Property element is being ignored.
3030 <Parameter Type="System.String" Name="id" />
3031 </Instance>
3032 </Message>
3033 <Message Id="RemoveFileNameRequired" Number="1007">
3034 <Instance>The RemoveFile/@Name attribute will soon become required. In order to match the old functionality of not specifying this attribute, please use the new RemoveFolder element instead.</Instance>
3035 </Message>
3036 <Message Id="SuppressAction" Number="1008">
3037 <Instance>
3038 The action '{0}' in the {1} table is being suppressed.
3039 <Parameter Type="System.String" Name="action" />
3040 <Parameter Type="System.String" Name="sequenceName" />
3041 </Instance>
3042 </Message>
3043 <Message Id="SuppressMergedAction" Number="1009" SourceLineNumbers="no">
3044 <Instance>
3045 The merged action '{0}' in the {1} table is being suppressed.
3046 <Parameter Type="System.String" Name="action" />
3047 <Parameter Type="System.String" Name="sequenceName" />
3048 </Instance>
3049 </Message>
3050 <Message Id="TargetDirCorrectedDefaultDir" Number="1010" SourceLineNumbers="no">
3051 <Instance>
3052 The Directory with Id 'TARGETDIR' must have the value 'SourceDir' in its 'DefaultDir' column. This has been automatically corrected for you in the decompiled output.
3053 </Instance>
3054 </Message>
3055 <Message Id="AccessDeniedForDeletion" Number="1011">
3056 <Instance>
3057 Access denied; cannot delete '{0}'.
3058 <Parameter Type="System.String" Name="tempFilesBasePath" />
3059 </Instance>
3060 </Message>
3061 <Message Id="DirectoryInUse" Number="1012">
3062 <Instance>
3063 The directory '{0}' is in use and cannot be deleted.
3064 <Parameter Type="System.String" Name="filePath" />
3065 </Instance>
3066 </Message>
3067 <Message Id="AccessDeniedForSettingAttributes" Number="1013">
3068 <Instance>
3069 Access denied; cannot set attributes on '{0}'.
3070 <Parameter Type="System.String" Name="filePath" />
3071 </Instance>
3072 </Message>
3073 <Message Id="UnknownAction" Number="1024">
3074 <Instance>
3075 The {0} table contains an action '{1}' which is not a known custom action, dialog, or standard action. This action will be left out of the decompiled output.
3076 <Parameter Type="System.String" Name="sequenceTableName" />
3077 <Parameter Type="System.String" Name="actionName" />
3078 </Instance>
3079 </Message>
3080 <Message Id="IdentifierTooLong" Number="1026">
3081 <Instance>
3082 The {0}/@{1} attribute's value, '{2}', is too long for an identifier. Standard identifiers are 72 characters long or less.
3083 <Parameter Type="System.String" Name="elementName" />
3084 <Parameter Type="System.String" Name="attributeName" />
3085 <Parameter Type="System.String" Name="value" />
3086 </Instance>
3087 </Message>
3088 <Message Id="UnknownPermission" Number="1030">
3089 <Instance>
3090 The {0} table contains a row with primary key '{1}' which has an unknown permission at bit {2}.
3091 <Parameter Type="System.String" Name="tableName" />
3092 <Parameter Type="System.String" Name="primaryKey" />
3093 <Parameter Type="System.Int32" Name="bitPosition" />
3094 </Instance>
3095 </Message>
3096 <Message Id="DirectoryRedundantNames" Number="1031">
3097 <Instance>
3098 The {0} element's {1} and {2} values are both '{3}'. This is redundant; the {2} attribute should be removed.
3099 <Parameter Type="System.String" Name="elementName" />
3100 <Parameter Type="System.String" Name="shortNameAttributeName" />
3101 <Parameter Type="System.String" Name="longNameAttributeName" />
3102 <Parameter Type="System.String" Name="attributeValue" />
3103 </Instance>
3104 <Instance>
3105 The {0} element's source and destination names are identical. This is redundant; the {1} and {2} attributes should be removed if present.
3106 <Parameter Type="System.String" Name="elementName" />
3107 <Parameter Type="System.String" Name="sourceNameAttributeName" />
3108 <Parameter Type="System.String" Name="longSourceAttributeName" />
3109 </Instance>
3110 </Message>
3111 <Message Id="UnableToResetAcls" Number="1032" SourceLineNumbers="no">
3112 <Instance>Unable to reset acls on destination files.</Instance>
3113 </Message>
3114 <Message Id="MediaExternalCabinetFilenameIllegal" Number="1033">
3115 <Instance>
3116 The {0}/@{1} attribute's value, '{2}', is not a valid external cabinet name. Legal cabinet names should follow 8.3 format: they should contain no more than 8 characters followed by an optional extension of no more than 3 characters. Any character except for the following may be used: \ ? | &gt; &lt; : / * " + , ; = [ ] (space). The Windows Installer team has recommended following the 8.3 format for external cabinet files and any other naming scheme is officially unsupported (which means it is not guaranteed to work on all platforms).
3117 <Parameter Type="System.String" Name="elementName" />
3118 <Parameter Type="System.String" Name="attributeName" />
3119 <Parameter Type="System.String" Name="value" />
3120 </Instance>
3121 </Message>
3122 <Message Id="DeprecatedPreProcVariable" Number="1034">
3123 <Instance>
3124 The built-in preprocessor variable '{0}' is deprecated. Please correct your authoring to use the new '{1}' preprocessor variable instead.
3125 <Parameter Type="System.String" Name="oldName" />
3126 <Parameter Type="System.String" Name="newName" />
3127 </Instance>
3128 </Message>
3129 <Message Id="FileSearchFileNameIssue" Number="1043">
3130 <Instance>
3131 The {0} element's {1} and {2} attributes were found. Due to a bug with the Windows Installer, only the Name or LongName attribute should be used. Use the Name attribute for 8.3 compliant file names and the LongName attribute for longer ones. When using only the LongName attribute, ICE03 should be ignored for the Signature table's FileName column.
3132 <Parameter Type="System.String" Name="elementName" />
3133 <Parameter Type="System.String" Name="attributeName1" />
3134 <Parameter Type="System.String" Name="attributeName2" />
3135 </Instance>
3136 </Message>
3137 <Message Id="AmbiguousFileOrDirectoryName" Number="1044">
3138 <Instance>
3139 The {0}/@{1} attribute's value '{2}' is an ambiguous short name because it ends with a '~' character followed by a number. Under some circumstances, this name could resolve to more than one file or directory name and lead to unpredictable results (for example 'MICROS~1' may correspond to 'Microsoft Shared' or 'Microsoft Foo' or literally 'Micros~1').
3140 <Parameter Type="System.String" Name="elementName" />
3141 <Parameter Type="System.String" Name="attributeName" />
3142 <Parameter Type="System.String" Name="value" />
3143 </Instance>
3144 </Message>
3145 <Message Id="PossiblyIncorrectTypelibVersion" Number="1048">
3146 <Instance>
3147 The Typelib table entry with Id '{0}' could have an incorrect version of '256.0'. InstallShield has a bug relating to the Typelib Version column: it will incorrectly set the value '65536' in to represent version '1.0'. However, this number actually corresponds to version '256.0'. This bug will not affect the typelib version that is registered during installation, however, it will prevent the Windows Installer from correctly identifying whether a typelib is already installed and lead to unnecessary reinstallations of the typelib.
3148 <Parameter Type="System.String" Name="id" />
3149 </Instance>
3150 </Message>
3151 <Message Id="ImplicitComponentPrimaryFeature" Number="1049" SourceLineNumbers="no">
3152 <Instance>
3153 The component '{0}' does not have an explicit primary feature parent specified. If the source files are linked in a different order, the primary parent feature may change. To prevent accidental changes, the primary feature parent should be set to 'yes' in one of the ComponentRef/@Primary, ComponentGroupRef/@Primary, or FeatureGroupRef/@Primary locations for this component.
3154 <Parameter Type="System.String" Name="componentId" />
3155 </Instance>
3156 </Message>
3157 <Message Id="ActionSequenceCollision" Number="1050">
3158 <Instance>
3159 The {0} table contains actions '{1}' and '{2}' which both have the same sequence number {3}. Please change the sequence number for one of these actions to avoid an ICE warning.
3160 <Parameter Type="System.String" Name="sequenceTableName" />
3161 <Parameter Type="System.String" Name="actionName1" />
3162 <Parameter Type="System.String" Name="actionName2" />
3163 <Parameter Type="System.Int32" Name="sequenceNumber" />
3164 </Instance>
3165 </Message>
3166 <Message Id="ActionSequenceCollision2" Number="1051">
3167 <Instance>The location of the action related to previous warning.</Instance>
3168 </Message>
3169 <Message Id="SuppressAction2" Number="1052">
3170 <Instance>The location of the suppressed action related to previous warning.</Instance>
3171 </Message>
3172 <Message Id="UnexpectedTableInProduct" Number="1053">
3173 <Instance>
3174 An unexpected row in the '{0}' table was found in this product. Products should not contain the '{0}' table.
3175 <Parameter Type="System.String" Name="tableName" />
3176 </Instance>
3177 </Message>
3178 <Message Id="DeprecatedAttribute" Number="1054">
3179 <Instance>
3180 The {0}/@{1} attribute has been deprecated.
3181 <Parameter Type="System.String" Name="elementName" />
3182 <Parameter Type="System.String" Name="attributeName" />
3183 </Instance>
3184 <Instance>
3185 The {0}/@{1} attribute has been deprecated. Please use the {2} attribute instead.
3186 <Parameter Type="System.String" Name="elementName" />
3187 <Parameter Type="System.String" Name="attributeName" />
3188 <Parameter Type="System.String" Name="newAttributeName" />
3189 </Instance>
3190 <Instance>
3191 The {0}/@{1} attribute has been deprecated. Please use the {2} or {3} attribute instead.
3192 <Parameter Type="System.String" Name="elementName" />
3193 <Parameter Type="System.String" Name="attributeName" />
3194 <Parameter Type="System.String" Name="newAttributeName1" />
3195 <Parameter Type="System.String" Name="newAttributeName2" />
3196 </Instance>
3197 </Message>
3198 <Message Id="MergeRescheduledAction" Number="1055">
3199 <Instance>
3200 The {0} table contains an action '{1}' which cannot be merged from the merge module '{2}'. This action is likely colliding with an action in the database that is being created. The colliding action may have been authored in the database or merged in from another merge module. If this is a standard action, it is likely colliding due to a difference in the condition for the action in the database and merge module. If this is a custom action, it should only be declared in the database or one merge module.
3201 <Parameter Type="System.String" Name="tableName" />
3202 <Parameter Type="System.String" Name="actionName" />
3203 <Parameter Type="System.String" Name="mergeModuleFile" />
3204 </Instance>
3205 </Message>
3206 <Message Id="MergeTableFailed" Number="1056">
3207 <Instance>
3208 The {0} table contains a row with primary key(s) '{1}' which cannot be merged from the merge module '{2}'. This is likely due to collision of rows with the same primary key(s) (but other different values in other columns) between the database and the merge module.
3209 <Parameter Type="System.String" Name="tableName" />
3210 <Parameter Type="System.String" Name="primaryKeys" />
3211 <Parameter Type="System.String" Name="mergeModuleFile" />
3212 </Instance>
3213 </Message>
3214 <Message Id="DecompiledStandardActionRelativelyScheduledInModule" Number="1057">
3215 <Instance>
3216 The {0} table contains a standard action '{1}' that does not have a sequence number specified. A value in the Sequence column is required for standard actions in a merge module. Remove the action from the decompiled authoring to have WiX automatically sequence it.
3217 <Parameter Type="System.String" Name="sequenceTableName" />
3218 <Parameter Type="System.String" Name="actionName" />
3219 </Instance>
3220 </Message>
3221 <Message Id="IllegalActionInSequence" Number="1058">
3222 <Instance>
3223 The {0} table contains an action '{1}' which is not allowed in this table. If this is a standard action then it is not valid for this table, if it is a custom action or dialog then this table does not accept actions of that type. This action will be left out of the decompiled output.
3224 <Parameter Type="System.String" Name="sequenceTableName" />
3225 <Parameter Type="System.String" Name="actionName" />
3226 </Instance>
3227 </Message>
3228 <Message Id="ExpectedForeignRow" Number="1059">
3229 <Instance>
3230 The {0} table contains a row with primary key(s) '{1}' whose {2} column contains a value, '{3}', which specifies a foreign key relationship with the {4} table. However, since the expected foreign row specified by this value does not exist, this will result in some information being left out of the decompiled output.
3231 <Parameter Type="System.String" Name="tableName" />
3232 <Parameter Type="System.String" Name="primaryKey" />
3233 <Parameter Type="System.String" Name="columnName" />
3234 <Parameter Type="System.String" Name="columnValue" />
3235 <Parameter Type="System.String" Name="foreignTableName" />
3236 </Instance>
3237 <Instance>
3238 The {0} table contains a row with primary key(s) '{1}' whose {2} and {4} columns contain the values, '{3}' and '{5}', which specify a foreign key relationship with the {6} table. However, since the expected foreign row specified by this value does not exist, this will result in some information being left out of the decompiled output.
3239 <Parameter Type="System.String" Name="tableName" />
3240 <Parameter Type="System.String" Name="primaryKey" />
3241 <Parameter Type="System.String" Name="columnName1" />
3242 <Parameter Type="System.String" Name="columnValue1" />
3243 <Parameter Type="System.String" Name="columnName2" />
3244 <Parameter Type="System.String" Name="columnValue2" />
3245 <Parameter Type="System.String" Name="foreignTableName" />
3246 </Instance>
3247 </Message>
3248 <Message Id="DecompilingAsCustomTable" Number="1060">
3249 <Instance>
3250 The {0} table is being decompiled as a custom table.
3251 <Parameter Type="System.String" Name="tableName" />
3252 </Instance>
3253 </Message>
3254 <Message Id="IllegalPatchCreationTable" Number="1061">
3255 <Instance>
3256 The {0} table is not legal in a patch creation file. The information in this table will be left out of the decompiled output.
3257 <Parameter Type="System.String" Name="tableName" />
3258 </Instance>
3259 </Message>
3260 <Message Id="SkippingMergeModuleTable" Number="1062">
3261 <Instance>
3262 The {0} table can only be represented in WiX for merge modules. The information in this table will be left out of the decompiled output.
3263 <Parameter Type="System.String" Name="tableName" />
3264 </Instance>
3265 </Message>
3266 <Message Id="SkippingPatchCreationTable" Number="1063">
3267 <Instance>
3268 The {0} table can only be represented in WiX for patch creation files. The information in this table will be left out of the decompiled output.
3269 <Parameter Type="System.String" Name="tableName" />
3270 </Instance>
3271 </Message>
3272 <Message Id="UnrepresentableColumnValue" Number="1064">
3273 <Instance>
3274 The {0}.{1} column's value, '{2}', cannot currently be represented in the WiX schema.
3275 <Parameter Type="System.String" Name="tableName" />
3276 <Parameter Type="System.String" Name="columnName" />
3277 <Parameter Type="System.Object" Name="value" />
3278 </Instance>
3279 </Message>
3280 <Message Id="DeprecatedTable" Number="1065" SourceLineNumbers="no">
3281 <Instance>
3282 The {0} table is not supported by the WiX toolset because it has been deprecated by the Windows Installer team. Any information in this table will be left out of the decompiled output.
3283 <Parameter Type="System.String" Name="tableName" />
3284 </Instance>
3285 </Message>
3286 <Message Id="PatchTable" Number="1066">
3287 <Instance>
3288 The {0} table is added to the install package by a transform from a patch package (.msp) and not authored directly into an install package (.msi). The information in this table will be left out of the decompiled output.
3289 <Parameter Type="System.String" Name="tableName" />
3290 </Instance>
3291 </Message>
3292 <Message Id="IllegalColumnValue" Number="1067">
3293 <Instance>
3294 The {0}.{1} column's value, '{2}', is not a recognized legal value. This information will be left out of the decompiled output.
3295 <Parameter Type="System.String" Name="tableName" />
3296 <Parameter Type="System.String" Name="columnName" />
3297 <Parameter Type="System.Object" Name="value" />
3298 </Instance>
3299 </Message>
3300 <Message Id="DeprecatedLongNameAttribute" Number="1069">
3301 <Instance>
3302 The {0}/@{1} attribute has been deprecated. Since WiX now has the ability to generate short file/directory names, the desired name should be specified in the {2} attribute instead. If the name specified in the {2} attribute is a short name, then WiX will not generate a short name. If the name specified in the {2} attribute is a long name and you want to manually specify the short name, please set the short name value in the {3} attribute.
3303 <Parameter Type="System.String" Name="elementName" />
3304 <Parameter Type="System.String" Name="longNameAttributeName" />
3305 <Parameter Type="System.String" Name="nameAttributeName" />
3306 <Parameter Type="System.String" Name="shortNameAttributeName" />
3307 </Instance>
3308 </Message>
3309 <Message Id="GeneratedShortFileNameConflict" Number="1070">
3310 <Instance>
3311 The short file name '{0}' was generated for multiple files that may be installed to the same directory. This could be due to conflicting long file names specified by the File/@Name attribute. If that is the case, please resolve the conflict in those attributes. Otherwise, please manually set the File/@ShortName attribute on the conflicting row to fix the collision. If one of the colliding files was added via a patch, that short file name should be specified manually to avoid disturbing the original short file name.
3312 <Parameter Type="System.String" Name="shortFileName" />
3313 </Instance>
3314 </Message>
3315 <Message Id="GeneratedShortFileNameConflict2" Number="1071">
3316 <Instance>
3317 The location of a conflicting generated short file name related to the previous warning.
3318 </Instance>
3319 </Message>
3320 <Message Id="DangerousTableInMergeModule" Number="1072">
3321 <Instance>
3322 Merge modules should not contain the '{0}' table because all merge conflicts cannot avoided. However, this warning can be suppressed if all of the consumers of the Merge Module agree to not duplicate identifiers in the '{0}' table.
3323 <Parameter Type="System.String" Name="tableName" />
3324 </Instance>
3325 </Message>
3326 <Message Id="DeprecatedLocalizationVariablePrefix" Number="1073">
3327 <Instance>
3328 The localization variable $(loc.{0}) uses a deprecated prefix '$'. Please use the '!' prefix instead. Since the prefix '$' is also used by the preprocessor, it has been deprecated to avoid namespace collisions.
3329 <Parameter Type="System.String" Name="variableId" />
3330 </Instance>
3331 </Message>
3332 <Message Id="PlaceholderValue" Number="1074">
3333 <Instance>
3334 The {0}/@{1} attribute's value, '{2}', is a placeholder value used in example files. Please replace this placeholder with the appropriate value.
3335 <Parameter Type="System.String" Name="elementName" />
3336 <Parameter Type="System.String" Name="attributeName" />
3337 <Parameter Type="System.String" Name="value" />
3338 </Instance>
3339 </Message>
3340 <Message Id="MissingUpgradeCode" Number="1075">
3341 <Instance>The Product/@UpgradeCode attribute was not found; it is strongly recommended to ensure that this product can be upgraded.</Instance>
3342 </Message>
3343 <Message Id="ValidationWarning" Number="1076">
3344 <Instance>
3345 {0}: {1}
3346 <Parameter Type="System.String" Name="ice" />
3347 <Parameter Type="System.String" Name="message" />
3348 </Instance>
3349 </Message>
3350 <Message Id="PropertyValueContainsPropertyReference" Number="1077">
3351 <Instance>
3352 The '{0}' Property contains '[{1}]' in its value which is an illegal reference to another property. If this value is a string literal, not a property reference, please ignore this warning. To set a property with the value of another property, use a CustomAction with Property and Value attributes.
3353 <Parameter Type="System.String" Name="propertyId" />
3354 <Parameter Type="System.String" Name="otherProperty" />
3355 </Instance>
3356 </Message>
3357 <Message Id="DeprecatedUpgradeProperty" Number="1078">
3358 <Instance>Specifying a Property element as a child of an Upgrade element has been deprecated. Please specify this Property element as a child of a different element such as Product or Fragment.</Instance>
3359 </Message>
3360 <Message Id="EmptyCabinet" Number="1079">
3361 <Instance>
3362 The cabinet '{0}' does not contain any files. If this installation contains no files, this warning can likely be safely ignored. Otherwise, please add files to the cabinet or remove it.
3363 <Parameter Type="System.String" Name="cabinetName" />
3364 </Instance>
3365 <Instance>
3366 The cabinet '{0}' does not contain any files. If this patch contains no files, this warning can likely be safely ignored. Otherwise, try passing -p to torch.exe when first building the transforms, or add a ComponentRef to your PatchFamily authoring to pull changed files into the cabinet.
3367 <Parameter Type="System.String" Name="cabinetName" />
3368 <Parameter Type="System.Boolean" Name="isPatch" />
3369 </Instance>
3370 </Message>
3371 <Message Id="DeprecatedRegistryElement" Number="1080">
3372 <Instance>The Registry element has been deprecated. Please use one of the new elements which replaces its functionality: RegistryKey for creating registry keys, RegistryValue for writing registry values, RemoveRegistryKey for removing registry keys, and RemoveRegistryValue for removing registry values.</Instance>
3373 </Message>
3374 <Message Id="IllegalRegistryKeyPath" Number="1081">
3375 <Instance>
3376 Component '{0}' specifies an illegal registry keypath of '{1}'. Since this entry actually represents a registry key, not a registry value, it cannot be the keypath.
3377 <Parameter Type="System.String" Name="componentName" />
3378 <Parameter Type="System.String" Name="registryId" />
3379 </Instance>
3380 </Message>
3381 <Message Id="DeprecatedPatchSequenceTargetAttribute" Number="1082">
3382 <Instance>
3383 The {0}/@{1} attribute has been deprecated in favor of the more strongly-typed ProductCode or TargetImage attributes. Please use the ProductCode attribute for indicating the ProductCode of a patch family directly, or the TargetImage attribute to specify the TargetImage which in turn will retrieve the ProductCode of the patch family.
3384 <Parameter Type="System.String" Name="elementName" />
3385 <Parameter Type="System.String" Name="attributeName" />
3386 </Instance>
3387 </Message>
3388 <Message Id="ProductIdAuthored" Number="1083">
3389 <Instance>
3390 The 'ProductID' property should not be directly authored because it will prevent the ValidateProductID standard action from performing any validation during the installation. This property will be set by the ValidateProductID standard action or control event.
3391 </Instance>
3392 </Message>
3393 <Message Id="ImplicitMergeModulePrimaryFeature" Number="1084" SourceLineNumbers="no">
3394 <Instance>
3395 The merge module '{0}' does not have an explicit primary feature parent specified. If the source files are linked in a different order, the primary parent feature may change. To prevent accidental changes, the primary feature parent should be set to 'yes' in one of the MergeRef/@Primary or FeatureGroupRef/@Primary locations for this component.
3396 <Parameter Type="System.String" Name="componentId" />
3397 </Instance>
3398 </Message>
3399 <Message Id="DeprecatedIgnoreModularizationElement" Number="1085">
3400 <Instance>
3401 The IgnoreModularization element has been deprecated. Use the Binary/@SuppressModularization, CustomAction/@SuppressModularization, or Property/@SuppressModularization attribute instead.
3402 </Instance>
3403 </Message>
3404 <Message Id="PropertyModularizationSuppressed" Number="1086">
3405 <Instance>
3406 The Property/@SuppressModularization attribute has been set to 'yes'. Using this functionality is strongly discouraged; it should only be necessary as a workaround of last resort in rare scenarios.
3407 </Instance>
3408 </Message>
3409 <Message Id="DeprecatedPackageCompressedAttribute" Number="1087">
3410 <Instance>
3411 The Package/@Compressed attribute is deprecated under the Module element because merge modules must always be compressed.
3412 </Instance>
3413 </Message>
3414 <Message Id="DeprecatedModuleGuidAttribute" Number="1088">
3415 <Instance>
3416 The Module/@Guid attribute is deprecated merge modules use their package code as the modularization guid. Use the Package/@Id attribute instead.
3417 </Instance>
3418 </Message>
3419 <Message Id="DeprecatedQuestionMarksGuid" Number="1090">
3420 <Instance>
3421 The {0}/@{1} attribute's value '????????-????-????-????-????????????' has been deprecated. Use '*' instead.
3422 <Parameter Type="System.String" Name="elementName" />
3423 <Parameter Type="System.String" Name="attributeName" />
3424 </Instance>
3425 </Message>
3426 <Message Id="PackageCodeSet" Number="1091">
3427 <Instance>
3428 The Package/@Id attribute has been set. Setting this attribute will allow nonidentical .msi files to have the same package code. This may be a problem because the package code is the primary identifier used by the installer to search for and validate the correct package for a given installation. If a package is changed without changing the package code, the installer may not use the newer package if both are still accessible to the installer. Please remove the Id attribute in order to automatically generate a new package code for each new .msi file.
3429 </Instance>
3430 </Message>
3431 <Message Id="InvalidModuleOrBundleVersion" Number="1093">
3432 <Instance>
3433 Invalid {0}/@Version '{1}'. {0} version has a max value of "65535.65535.65535.65535" and must be all numeric.
3434 <Parameter Type="System.String" Name="moduleOrBundle" />
3435 <Parameter Type="System.String" Name="version" />
3436 </Instance>
3437 </Message>
3438 <Message Id="InvalidRemoveFile" Number="1095">
3439 <Instance>
3440 File '{0}' was removed from component '{1}'. Removing a file from a component will not result in the file being removed by a patch. You should author a RemoveFile element in your component to remove the file from the installation if you want the file to be removed.
3441 <Parameter Type="System.String" Name="file" />
3442 <Parameter Type="System.String" Name="component" />
3443 </Instance>
3444 </Message>
3445 <Message Id="PreprocessorWarning" Number="1096">
3446 <Instance>
3447 {0}
3448 <Parameter Type="System.String" Name="message" />
3449 </Instance>
3450 </Message>
3451 <Message Id="UpdateOfNonKeyPathFile" Number="1097" SourceLineNumbers="no">
3452 <Instance>
3453 File '{0}' in Component '{1}' was changed, but the KeyPath file '{2}' was not. This file will not be patched on the target system if the REINSTALLMODE does not contain 'A'. The KeyPath file should also be changed and included in your patch.
3454 <Parameter Type="System.String" Name="nonKeyPathFileId" />
3455 <Parameter Type="System.String" Name="componentId" />
3456 <Parameter Type="System.String" Name="keyPathFileId" />
3457 </Instance>
3458 </Message>
3459 <Message Id="UnsupportedCommandLineArgument" Number="1098" SourceLineNumbers="no">
3460 <Instance>
3461 '{0}' is not a valid command line argument.
3462 <Parameter Type="System.String" Name="arg" />
3463 </Instance>
3464 </Message>
3465 <Message Id="MajorUpgradePatchNotRecommended" Number="1099" SourceLineNumbers="no">
3466 <Instance>
3467 Changing the ProductCode in a patch is not recommended because the patch cannot be uninstalled nor can it be sequenced along with other patches for the target product. See http://msdn2.microsoft.com/library/aa367571.aspx for more information.
3468 </Instance>
3469 </Message>
3470 <Message Id="RetainRangeMismatch" Number="1100">
3471 <Instance>
3472 Mismatch in RetainRangeCounts for the file '{0}' - ignoring the retain ranges.
3473 <Parameter Type="System.String" Name="fileId"/>
3474 </Instance>
3475 </Message>
3476 <Message Id="DefaultLanguageUsedForVersionedFile" Number="1101">
3477 <Instance>
3478 The DefaultLanguage '{0}' was used for file '{1}' which has no language. Specifying a language that is different from the actual file may result in unexpected versioning behavior during a repair or while patching. Either specify a value for DefaultLanguage or put the language in the version information resource to eliminate this warning.
3479 <Parameter Type="System.String" Name="language"/>
3480 <Parameter Type="System.String" Name="fileId"/>
3481 </Instance>
3482 </Message>
3483 <Message Id="DefaultLanguageUsedForUnversionedFile" Number="1102">
3484 <Instance>
3485 The DefaultLanguage '{0}' was used for file '{1}' which has no language or version. For unversioned files, specifying a value for DefaultLanguage is not neccessary and it will not be used when determining file versions. Remove the DefaultLanguage attribute to eliminate this warning.
3486 <Parameter Type="System.String" Name="language"/>
3487 <Parameter Type="System.String" Name="fileId"/>
3488 </Instance>
3489 </Message>
3490 <Message Id="DefaultVersionUsedForUnversionedFile" Number="1103">
3491 <Instance>
3492 The DefaultVersion '{0}' was used for file '{1}' which has no version. No entry for this file will be placed in the MsiFileHash table. For unversioned files, specifying a version that is different from the actual file may result in unexpected versioning behavior during a repair or while patching. Version the resource to eliminate this warning.
3493 <Parameter Type="System.String" Name="version"/>
3494 <Parameter Type="System.String" Name="fileId"/>
3495 </Instance>
3496 </Message>
3497 <Message Id="InvalidHigherInstallerVersionInModule" Number="1104">
3498 <Instance>
3499 Merge module '{0}' has an installer version of {1} which is greater than the product's installer version of {2}. Merging a module with a higher installer version than the product it is being merged into can result in invalid values in the resulting msi. You must set the Package/@InstallerVersion attribute to {1} or greater to merge this merge module into your product.
3500 <Parameter Type="System.String" Name="moduleId" />
3501 <Parameter Type="System.Int32" Name="moduleInstallerVersion" />
3502 <Parameter Type="System.Int32" Name="productInstallerVersion" />
3503 </Instance>
3504 </Message>
3505 <Message Id="ValidationFailedDueToSystemPolicy" Number="1105" SourceLineNumbers="no">
3506 <Instance>
3507 Validation could not run due to system policy. To eliminate this warning, run the process as admin or suppress ICE validation.
3508 </Instance>
3509 </Message>
3510 <Message Id="ColumnsIncompatibleWithInstallerVersion" Number="1106">
3511 <Instance>
3512 Table '{0}' uses columns that require a version of Windows Installer greater than specified in your package ('{1}').
3513 <Parameter Type="System.String" Name="tableName" />
3514 <Parameter Type="System.Int32" Name="productInstallerVersion" />
3515 </Instance>
3516 </Message>
3517 <Message Id="TableIncompatibleWithInstallerVersion" Number="1107">
3518 <Instance>
3519 Using table '{0}' requires a version of Windows Installer greater than specified in your package ('{1}').
3520 <Parameter Type="System.String" Name="tableName" />
3521 <Parameter Type="System.Int32" Name="productInstallerVersion" />
3522 </Instance>
3523 </Message>
3524 <Message Id="DeprecatedCommandLineSwitch" Number="1108" SourceLineNumbers="no">
3525 <Instance>
3526 The command line switch '{0}' is deprecated.
3527 <Parameter Type="System.String" Name="oldSwitch" />
3528 </Instance>
3529 <Instance>
3530 The command line switch '{0}' is deprecated. Please use '{1}' instead.
3531 <Parameter Type="System.String" Name="oldSwitch" />
3532 <Parameter Type="System.String" Name="newSwitch" />
3533 </Instance>
3534 </Message>
3535 <Message Id="UnexpectedEntrySection" Number="1109">
3536 <Instance>
3537 Found mismatched entry point &lt;{0}&gt;. Expected &lt;{1}&gt; for specified output package type {2}.
3538 <Parameter Type="System.String" Name="sectionType" />
3539 <Parameter Type="System.String" Name="expectedType" />
3540 <Parameter Type="System.String" Name="outputExtension" />
3541 </Instance>
3542 </Message>
3543 <Message Id="NewComponentAddedToExistingFeature" Number="1110">
3544 <Instance>
3545 Component '{0}' was added to feature '{1}' in the transform '{2}'. If you cannot guarantee that this feature will always be installed, you should consider adding new components to new top-level features to prevent prompts for source when installing this patch.
3546 <Parameter Type="System.String" Name="component" />
3547 <Parameter Type="System.String" Name="feature" />
3548 <Parameter Type="System.String" Name="transformPath" />
3549 </Instance>
3550 </Message>
3551 <Message Id="DeprecatedAttributeValue" Number="1111">
3552 <Instance>
3553 The value &quot;{0}&quot; for the {1}/@{2} attribute has been deprecated. Please use &quot;{3}&quot; instead.
3554 <Parameter Type="System.String" Name="attributeValue" />
3555 <Parameter Type="System.String" Name="elementName" />
3556 <Parameter Type="System.String" Name="attributeName" />
3557 <Parameter Type="System.String" Name="newAttributeValue" />
3558 </Instance>
3559 </Message>
3560 <Message Id="InsufficientPermissionHarvestTypeLib" Number="1112" SourceLineNumbers="no">
3561 <Instance>
3562 Not enough permissions to harvest type library. On Windows Vista, you must either run Heat elevated, or install Windows Vista SP1 (or higher).
3563 </Instance>
3564 </Message>
3565 <Message Id="UnclearShortcut" Number="1113">
3566 <Instance>
3567 Because it is an advertised shortcut, the target of shortcut '{0}' will be the keypath of component '{2}' rather than parent file '{1}'. To eliminate this warning, you can (1) make the Shortcut element a child of the File element that is the keypath of component '{2}', (2) make file '{1}' the keypath of component '{2}', or (3) remove the @Advertise attribute so the shortcut is a non-advertised shortcut.
3568 <Parameter Type="System.String" Name="shortcutId" />
3569 <Parameter Type="System.String" Name="fileId" />
3570 <Parameter Type="System.String" Name="componentId" />
3571 </Instance>
3572 </Message>
3573 <Message Id="TooManyProgIds" Number="1114">
3574 <Instance>
3575 Class '{0}' tried to use ProgId '{1}' which has already been associated with class '{2}'. This information will be left out of the decompiled output.
3576 <Parameter Type="System.String" Name="clsId" />
3577 <Parameter Type="System.String" Name="progId" />
3578 <Parameter Type="System.String" Name="otherClsId" />
3579 </Instance>
3580 </Message>
3581 <Message Id="BadColumnDataIgnored" Number="1115">
3582 <Instance>
3583 The value '{0}' in table '{1}', column '{2}' is invalid according to the column's validation information. The decompiled output includes a best-effort representation of this value.
3584 <Parameter Type="System.String" Name="value" />
3585 <Parameter Type="System.String" Name="tableName" />
3586 <Parameter Type="System.String" Name="columnName" />
3587 </Instance>
3588 </Message>
3589 <Message Id="NullMsiAssemblyNameValue" Number="1116">
3590 <Instance>
3591 The assembly in component '{0}' has a null or empty {1} assembly name value.
3592 <Parameter Type="System.String" Name="componentName" />
3593 <Parameter Type="System.String" Name="name" />
3594 </Instance>
3595 </Message>
3596 <Message Id="InvalidAttributeCombination" Number="1117">
3597 <Instance>
3598 It is invalid to combine attributes {0} and {1}. The decompiled output will set attribute {2} to {3}.
3599 <Parameter Type="System.String" Name="attrib1" />
3600 <Parameter Type="System.String" Name="attrib2" />
3601 <Parameter Type="System.String" Name="name" />
3602 <Parameter Type="System.String" Name="value" />
3603 </Instance>
3604 </Message>
3605 <Message Id="VariableDeclarationCollision" Number="1118">
3606 <Instance>
3607 The variable '{0}' with value '{1}' was previously declared with value '{2}'.
3608 <Parameter Type="System.String" Name="variableName" />
3609 <Parameter Type="System.String" Name="variableValue" />
3610 <Parameter Type="System.String" Name="variableCollidingValue" />
3611 </Instance>
3612 </Message>
3613 <Message Id="DuplicatePrimaryKey" Number="1119">
3614 <Instance>
3615 The primary key '{0}' is duplicated in table '{1}' and will be ignored. Please remove one of the entries or rename a part of the primary key to avoid the collision.
3616 <Parameter Type="System.String" Name="primaryKey" />
3617 <Parameter Type="System.String" Name="tableName" />
3618 </Instance>
3619 </Message>
3620 <Message Id="RequiresMsi200for64bitPackage" Number="1121">
3621 <Instance>
3622 Package/@InstallerVersion must be 200 or greater for a 64-bit package. The value will be changed to 200. Please specify a value of 200 or greater in order to eliminate this warning.
3623 </Instance>
3624 </Message>
3625 <Message Id="ExternalCabsAreNotSigned" Number="1122" SourceLineNumbers="no">
3626 <Instance>
3627 The installer database '{0}' has external cabs, but at least one of them is not signed. Please ensure that all external cabs are signed, if you mean to sign them. If you don't mean to sign them, there is no need to run the insignia tool as part of your build.
3628 <Parameter Type="System.String" Name="databaseFile" />
3629 </Instance>
3630 </Message>
3631 <Message Id="FailedToDeleteTempDir" Number="1123" SourceLineNumbers="no">
3632 <Instance>
3633 Failed to delete temporary directory: {0}
3634 <Parameter Type="System.String" Name="directory" />
3635 </Instance>
3636 </Message>
3637 <Message Id="StandardDirectoryConflictInMergeModule" Number="1124">
3638 <Instance>
3639 The Directory '{0}' starts with the same Id as the standard folder in Windows Installer '{1}'. A directory Id that begins with the same Id as a standard folder that is in an MSM may encounter a conflict when merging the MSM into an MSI. This may result in the contents of this merge module being installed to an unexpected location. To eliminate this warning, change your directory Id to not start with the same Id as any standard folders.
3640 <Parameter Type="System.String" Name="directory" />
3641 <Parameter Type="System.String" Name="standardDirectory" />
3642 </Instance>
3643 </Message>
3644 <Message Id="PreprocessorUnknownPragma" Number="1125">
3645 <Instance>
3646 The pragma '{0}' is unknown. Please ensure you have referenced the extension that defines this pragma.
3647 <Parameter Type="System.String" Name="pragmaName" />
3648 </Instance>
3649 </Message>
3650 <Message Id="DeprecatedComponentGroupId" Number="1126">
3651 <Instance>
3652 The {0}/@Id attribute contains invalid characters for an identifier. Being able to use invalid identifier characters for a {0} identifier has been deprecated.
3653 <Parameter Type="System.String" Name="elementName" />
3654 </Instance>
3655 </Message>
3656 <Message Id="UxPayloadsOnlySupportEmbedding" Number="1127">
3657 <Instance>
3658 A UX Payload ('{0}') was marked for something other than embedded packaging, possibly because it included a @DownloadUrl attribute. At present, UX Payloads must be embedded in the Bundle, so the requested packaging is being ignored.
3659 <Parameter Type="System.String" Name="sourceFile" />
3660 </Instance>
3661 </Message>
3662 <Message Id="DiscardedRollbackBoundary" Number="1129">
3663 <Instance>
3664 The RollbackBoundary '{0}' was discarded because it was not followed by a package. Without a package the rollback boundary doesn't do anything. Verify that the RollbackBoundary element is not followed by another RollbackBoundary and that the element is not at the end of the chain.
3665 <Parameter Type="System.String" Name="rollbackBoundaryId" />
3666 </Instance>
3667 </Message>
3668 <Message Id="DeprecatedElement" Number="1130">
3669 <Instance>
3670 The {0} element has been deprecated.
3671 <Parameter Type="System.String" Name="elementName" />
3672 </Instance>
3673 <Instance>
3674 The {0} element has been deprecated. Please use the {1} element instead.
3675 <Parameter Type="System.String" Name="elementName" />
3676 <Parameter Type="System.String" Name="newElementName" />
3677 </Instance>
3678 <Instance>
3679 The {0} element has been deprecated. Please use the {1} or {2} element instead.
3680 <Parameter Type="System.String" Name="elementName" />
3681 <Parameter Type="System.String" Name="newElementName1" />
3682 <Parameter Type="System.String" Name="newElementName2" />
3683 </Instance>
3684 </Message>
3685 <Message Id="CannotUpdateCabCache" Number="1131">
3686 <Instance>
3687 Cannot update the timestamp of cached cabinet: '{0}'. If the timestamp is not updated, the build may rebuild more than is necessary. To fix the issue, ensure that the cabinet file is writable, error: {1}
3688 <Parameter Type="System.String" Name="cabinetPath" />
3689 <Parameter Type="System.String" Name="detail" />
3690 </Instance>
3691 </Message>
3692 <Message Id="DownloadUrlNotSupportedForEmbeddedPayloads" Number="1132">
3693 <Instance>
3694 The Payload '{0}' is embedded but included a @DownloadUrl attribute. Embedded Payloads cannot be downloaded so the download URL is being ignored.
3695 <Parameter Type="System.String" Name="payloadId" />
3696 </Instance>
3697 </Message>
3698 <Message Id="DiscouragedAllUsersValue" Number="1133">
3699 <Instance>
3700 Bundles require a package to be either per-machine or per-user. The MSI '{0}' ALLUSERS Property is set to '2' which may change from per-user to per-machine at install time. The Bundle will assume the package is per-{1} and will not work correctly if that changes. If possible, remove the Property with Id='ALLUSERS' and use Package/@InstallScope attribute instead.
3701 <Parameter Type="System.String" Name="path" />
3702 <Parameter Type="System.String" Name="machineOrUser" />
3703 </Instance>
3704 </Message>
3705 <Message Id="ImplicitlyPerUser" Number="1134">
3706 <Instance>
3707 The MSI '{0}' does not explicitly indicate that it is a per-user package even though the ALLUSERS Property is blank. This suggests a per-user package so the Bundle will assume the package is per-user. If possible, use the Package/@InstallScope attribute to be explicit instead.
3708 <Parameter Type="System.String" Name="path" />
3709 </Instance>
3710 </Message>
3711 <Message Id="PerUserButForcingPerMachine" Number="1135">
3712 <Instance>
3713 The MSI '{0}' is a per-user package being forced to per-machine. Verify that the MsiPackage/@ForcePerMachine attribute is expected and that the per-user package works correctly when forced to install per-machine.
3714 <Parameter Type="System.String" Name="path" />
3715 </Instance>
3716 </Message>
3717 <Message Id="AttributeShouldContain" Number="1136">
3718 <Instance>
3719 The {0}/@{1} attribute value '{2}' should contain '{3}' when the {0}/@{4} attribute is set to '{5}'.
3720 <Parameter Type="System.String" Name="elementName" />
3721 <Parameter Type="System.String" Name="attributeName" />
3722 <Parameter Type="System.String" Name="attributeValue" />
3723 <Parameter Type="System.String" Name="expectedContains" />
3724 <Parameter Type="System.String" Name="otherAttributeName" />
3725 <Parameter Type="System.String" Name="otherAttributeValue" />
3726 </Instance>
3727 </Message>
3728 <Message Id="DuplicateComponentGuidsMustHaveMutuallyExclusiveConditions" Number="1137">
3729 <Instance>
3730 Component/@Id='{0}' has a @Guid value '{1}' that duplicates another component in this package. This is not officially supported by Windows Installer but works as long as all components have mutually-exclusive conditions. It is recommended to give each component its own unique GUID.
3731 <Parameter Type="System.String" Name="componentId" />
3732 <Parameter Type="System.String" Name="guid" />
3733 </Instance>
3734 </Message>
3735 <Message Id="DeprecatedRegistryKeyActionAttribute" Number="1138">
3736 <Instance>
3737 The RegistryKey/@Action attribute has been deprecated. In most cases, you can simply omit @Action. If you need to force Windows Installer to create an empty key or recursively delete the key, use the ForceCreateOnInstall or ForceDeleteOnUninstall attributes instead.
3738 </Instance>
3739 </Message>
3740 <Message Id="NotABinaryWixlib" Number="1139" SourceLineNumbers="no">
3741 <Instance>
3742 '{0}' is not a binary Wixlib and has no embedded files.
3743 <Parameter Type="System.String" Name="wixlib" />
3744 </Instance>
3745 </Message>
3746 <Message Id="NoPerMachineDependencies" Number="1140">
3747 <Instance>
3748 Bundle dependencies will not be registered on per-machine package '{0}' for a per-user bundle. Either make sure that all packages are installed per-machine, or author any per-machine dependencies as permanent packages.
3749 <Parameter Type="System.String" Name="packageId" />
3750 </Instance>
3751 </Message>
3752 <Message Id="DownloadUrlNotSupportedForAttachedContainers" Number="1141">
3753 <Instance>
3754 The Container '{0}' is attached but included a @DownloadUrl attribute. Attached Containers cannot be downloaded so the download URL is being ignored.
3755 <Parameter Type="System.String" Name="containerId" />
3756 </Instance>
3757 </Message>
3758 <Message Id="ReservedAttribute" Number="1142">
3759 <Instance>
3760 The {0}/@{1} attribute is reserved for future use and has no effect in this version of the WiX toolset.
3761 <Parameter Type="System.String" Name="elementName" />
3762 <Parameter Type="System.String" Name="attributeName" />
3763 </Instance>
3764 </Message>
3765 <Message Id="RequiresMsi500forArmPackage" Number="1143">
3766 <Instance>
3767 Package/@InstallerVersion must be 500 or greater for an Arm package. The value will be changed to 500. Please specify a value of 500 or greater in order to eliminate this warning.
3768 </Instance>
3769 </Message>
3770 <Message Id="RemotePayloadsMustNotAlsoBeCompressed" Number="1144">
3771 <Instance>
3772 The {0}/@Compressed attribute must have value 'no' when a RemotePayload child element is present. RemotePayload indicates that a package will always be downloaded and cannot be compressed into a bundle. To eliminate this warning, explicitly set the {0}/@Compressed attribute to 'no'.
3773 <Parameter Type="System.String" Name="elementName" />
3774 </Instance>
3775 </Message>
3776 <Message Id="AllChangesIncludedInPatch" Number="1145">
3777 <Instance>
3778 All changes between the baseline and upgraded packages will be included in the patch except for any change to the ProductCode. The 'All' element is supported primarily for testing purposes and negates the benefits of patch families.
3779 </Instance>
3780 </Message>
3781 <Message Id="RelatedAttributeConditionallyIgnored" Number="1146">
3782 <Instance>
3783 Ignoring attribute {0} because attribute {1} is set to {2}.
3784 <Parameter Type="System.String" Name="recessiveAttribute" />
3785 <Parameter Type="System.String" Name="dominantAttribute" />
3786 <Parameter Type="System.String" Name="dominantValue" />
3787 </Instance>
3788 </Message>
3789 <Message Id="BackslashTerminateInlineDirectorySyntax" Number="1147">
3790 <Instance>
3791 Backslash terminate the {0}/@{1} attribute's inline directory value '{2}'. A backslash ensures a directory name will not be mistaken for a directory reference.
3792 <Parameter Type="System.String" Name="elementName" />
3793 <Parameter Type="System.String" Name="attributeName" />
3794 <Parameter Type="System.String" Name="value" />
3795 </Instance>
3796 </Message>
3797 <Message Id="VersionTruncated" Number="1148">
3798 <Instance>
3799 Product version {0} in package '{1}' is not valid per the MSI SDK and cannot be represented in a bundle. It has been truncated to {2}.
3800 <Parameter Type="System.String" Name="originalVersion" />
3801 <Parameter Type="System.String" Name="package" />
3802 <Parameter Type="System.String" Name="truncatedVersion" />
3803 </Instance>
3804 </Message>
3805 <Message Id="ServiceConfigFamilyNotSupported" Number="1149">
3806 <Instance>
3807 {0} functionality is documented in the Windows Installer SDK to "not [work] as expected." Consider replacing {0} with the WixUtilExtension ServiceConfig element.
3808 <Parameter Type="System.String" Name="elementName" />
3809 </Instance>
3810 </Message>
3811 </Class>
3812
3813 <Class Name="WixVerboses" ContainerName="WixVerboseEventArgs" BaseContainerName="MessageEventArgs" Level="Verbose">
3814 <Message Id="ImportBinaryStream" Number="9000" SourceLineNumbers="no">
3815 <Instance>
3816 Importing binary stream from '{0}'.
3817 <Parameter Type="System.String" Name="streamSource" />
3818 </Instance>
3819 </Message>
3820 <Message Id="ImportIconStream" Number="9001" SourceLineNumbers="no">
3821 <Instance>
3822 Importing icon stream from '{0}'.
3823 <Parameter Type="System.String" Name="streamSource" />
3824 </Instance>
3825 </Message>
3826 <Message Id="CopyFile" Number="9002" SourceLineNumbers="no">
3827 <Instance>
3828 Copying file '{0}' to '{1}'.
3829 <Parameter Type="System.String" Name="sourceFile" />
3830 <Parameter Type="System.String" Name="destinationFile" />
3831 </Instance>
3832 </Message>
3833 <Message Id="MoveFile" Number="9003" SourceLineNumbers="no">
3834 <Instance>
3835 Moving file '{0}' to '{1}'.
3836 <Parameter Type="System.String" Name="sourceFile" />
3837 <Parameter Type="System.String" Name="destinationFile" />
3838 </Instance>
3839 </Message>
3840 <Message Id="CreateDirectory" Number="9004" SourceLineNumbers="no">
3841 <Instance>
3842 The directory '{0}' does not exist, creating it now.
3843 <Parameter Type="System.String" Name="directory" />
3844 </Instance>
3845 </Message>
3846 <Message Id="RemoveDestinationFile" Number="9005" SourceLineNumbers="no">
3847 <Instance>
3848 The destination file '{0}' already exists, attempting to remove it.
3849 <Parameter Type="System.String" Name="destinationFile" />
3850 </Instance>
3851 </Message>
3852 <Message Id="CabFile" Number="9006" SourceLineNumbers="no">
3853 <Instance>
3854 Cabbing file {0} from '{1}'.
3855 <Parameter Type="System.String" Name="fileId" />
3856 <Parameter Type="System.String" Name="filePath" />
3857 </Instance>
3858 </Message>
3859 <Message Id="UpdatingFileInformation" Number="9007" SourceLineNumbers="no">
3860 <Instance>Updating file information.</Instance>
3861 </Message>
3862 <Message Id="GeneratingDatabase" Number="9008" SourceLineNumbers="no">
3863 <Instance>Generating database.</Instance>
3864 </Message>
3865 <Message Id="MergingModules" Number="9009" SourceLineNumbers="no">
3866 <Instance>Merging modules.</Instance>
3867 </Message>
3868 <Message Id="CreatingCabinetFiles" Number="9010" SourceLineNumbers="no">
3869 <Instance>Creating cabinet files.</Instance>
3870 </Message>
3871 <Message Id="ImportingStreams" Number="9011" SourceLineNumbers="no">
3872 <Instance>Importing streams.</Instance>
3873 </Message>
3874 <Message Id="LayingOutMedia" Number="9012" SourceLineNumbers="no">
3875 <Instance>Laying out media.</Instance>
3876 </Message>
3877 <Message Id="DecompilingTable" Number="9013" SourceLineNumbers="no">
3878 <Instance>
3879 Decompiling the {0} table.
3880 <Parameter Type="System.String" Name="tableName" />
3881 </Instance>
3882 </Message>
3883 <Message Id="ValidationInfo" Number="9014" SourceLineNumbers="no">
3884 <Instance>
3885 {0}: {1}
3886 <Parameter Type="System.String" Name="ice" />
3887 <Parameter Type="System.String" Name="message" />
3888 </Instance>
3889 </Message>
3890 <Message Id="CreateCabinet" Number="9015" SourceLineNumbers="no">
3891 <Instance>
3892 Creating cabinet '{0}'.
3893 <Parameter Type="System.String" Name="cabinet" />
3894 </Instance>
3895 </Message>
3896 <Message Id="ValidatingDatabase" Number="9016" SourceLineNumbers="no">
3897 <Instance>Validating database.</Instance>
3898 </Message>
3899 <Message Id="OpeningMergeModule" Number="9017" SourceLineNumbers="no">
3900 <Instance>
3901 Opening merge module '{0}' with language '{1}'.
3902 <Parameter Type="System.String" Name="modulePath" />
3903 <Parameter Type="System.Int16" Name="language" />
3904 </Instance>
3905 </Message>
3906 <Message Id="MergingMergeModule" Number="9018" SourceLineNumbers="no">
3907 <Instance>
3908 Merging merge module '{0}'.
3909 <Parameter Type="System.String" Name="modulePath" />
3910 </Instance>
3911 </Message>
3912 <Message Id="ConnectingMergeModule" Number="9019" SourceLineNumbers="no">
3913 <Instance>
3914 Connecting merge module '{0}' to feature '{1}'.
3915 <Parameter Type="System.String" Name="modulePath" />
3916 <Parameter Type="System.String" Name="feature" />
3917 </Instance>
3918 </Message>
3919 <Message Id="ResequencingMergeModuleFiles" Number="9020" SourceLineNumbers="no">
3920 <Instance>Resequencing files from all merge modules.</Instance>
3921 </Message>
3922 <Message Id="BinderTempDirLocatedAt" Number="9021" SourceLineNumbers="no">
3923 <Instance>
3924 Binder temporary directory located at '{0}'.
3925 <Parameter Type="System.String" Name="directory" />
3926 </Instance>
3927 </Message>
3928 <Message Id="ValidatorTempDirLocatedAt" Number="9022" SourceLineNumbers="no">
3929 <Instance>
3930 Validator temporary directory located at '{0}'.
3931 <Parameter Type="System.String" Name="directory" />
3932 </Instance>
3933 </Message>
3934 <Message Id="GeneratingBundle" Number="9023" SourceLineNumbers="no">
3935 <Instance>
3936 Generating Burn bundle '{0}' from stub '{1}'.
3937 <Parameter Type="System.String" Name="bundleFile" />
3938 <Parameter Type="System.String" Name="stubFile" />
3939 </Instance>
3940 </Message>
3941 <Message Id="ResolvingManifest" Number="9024" SourceLineNumbers="no">
3942 <Instance>
3943 Generating resolved manifest '{0}'.
3944 <Parameter Type="System.String" Name="manifestFile" />
3945 </Instance>
3946 </Message>
3947 <Message Id="LoadingPayload" Number="9025" SourceLineNumbers="no">
3948 <Instance>
3949 Loading payload '{0}' into container.
3950 <Parameter Type="System.String" Name="payload" />
3951 </Instance>
3952 </Message>
3953 <Message Id="BundleGuid" Number="9026" SourceLineNumbers="no">
3954 <Instance>
3955 Assigning bundle GUID '{0}'.
3956 <Parameter Type="System.String" Name="bundleGuid" />
3957 </Instance>
3958 </Message>
3959 <Message Id="CopyingExternalPayload" Number="9027" SourceLineNumbers="no">
3960 <Instance>
3961 Copying external payload from '{0}' to '{1}'.
3962 <Parameter Type="System.String" Name="payload" />
3963 <Parameter Type="System.String" Name="outputDirectory" />
3964 </Instance>
3965 </Message>
3966 <Message Id="EmbeddingContainer" Number="9028" SourceLineNumbers="no">
3967 <Instance>
3968 Embedding container '{0}' ({1} bytes) with '{2}' compression.
3969 <Parameter Type="System.String" Name="container" />
3970 <Parameter Type="System.Int64" Name="size" />
3971 <Parameter Type="System.String" Name="compression" />
3972 </Instance>
3973 </Message>
3974 <Message Id="SwitchingToPerUserPackage" Number="9029">
3975 <Instance>
3976 Bundle switching from per-machine to per-user due to addition of per-user package '{0}'.
3977 <Parameter Type="System.String" Name="path" />
3978 </Instance>
3979 </Message>
3980 <Message Id="SetCabbingThreadCount" Number="9030" SourceLineNumbers="no">
3981 <Instance>
3982 There will be '{0}' threads used to produce CAB files.
3983 <Parameter Type="System.String" Name="threads" />
3984 </Instance>
3985 </Message>
3986 <Message Id="ValidationSerialized" Number="9031" SourceLineNumbers="no">
3987 <Instance>
3988 Multiple packages cannot reliably be validated simultaneously. This validation will resume when the other package being validated has completed.
3989 </Instance>
3990 </Message>
3991 <Message Id="ReusingCabCache" Number="9032">
3992 <Instance>
3993 Reusing cabinet '{0}' from cabinet cache path: '{1}'.
3994 <Parameter Type="System.String" Name="cabinetName" />
3995 <Parameter Type="System.String" Name="source" />
3996 </Instance>
3997 </Message>
3998 <Message Id="CabinetsSplitInParallel" Number="9033" SourceLineNumbers="no">
3999 <Instance>
4000 Multiple Cabinets with Large Files are splitting simultaneously. This current cabinet is waiting on a shared resource and splitting will resume when the other splitting has completed.
4001 </Instance>
4002 </Message>
4003 <Message Id="ValidatedDatabase" Number="9034" SourceLineNumbers="no">
4004 <Instance>
4005 Validation complete: {0:N0}ms elapsed.
4006 <Parameter Type="System.Int64" Name="size" />
4007 </Instance>
4008 </Message>
4009 </Class>
4010</Messages>
diff --git a/src/WixToolset.Core/Decompiler.cs b/src/WixToolset.Core/Decompiler.cs
new file mode 100644
index 00000000..249b5788
--- /dev/null
+++ b/src/WixToolset.Core/Decompiler.cs
@@ -0,0 +1,9357 @@
1// Copyright (c) .NET Foundation and 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.Diagnostics.CodeAnalysis;
11 using System.Globalization;
12 using System.IO;
13 using System.Text;
14 using System.Text.RegularExpressions;
15 using WixToolset.Data;
16 using WixToolset.Data.Rows;
17 using WixToolset.Extensibility;
18 using WixToolset.Msi;
19 using WixToolset.Core.Native;
20 using Wix = WixToolset.Data.Serialize;
21
22 /// <summary>
23 /// Decompiles an msi database into WiX source.
24 /// </summary>
25 public class Decompiler
26 {
27 private static readonly Regex NullSplitter = new Regex(@"\[~]");
28
29 private int codepage;
30 private bool compressed;
31 private bool shortNames;
32 private DecompilerCore core;
33 private string exportFilePath;
34 private List<IDecompilerExtension> extensions;
35 private Dictionary<string, IDecompilerExtension> extensionsByTableName;
36 private string modularizationGuid;
37 private OutputType outputType;
38 private Hashtable patchTargetFiles;
39 private Hashtable sequenceElements;
40 private bool showPedanticMessages;
41 private WixActionRowCollection standardActions;
42 private bool suppressCustomTables;
43 private bool suppressDroppingEmptyTables;
44 private bool suppressRelativeActionSequencing;
45 private bool suppressUI;
46 private TableDefinitionCollection tableDefinitions;
47 // private TempFileCollection tempFiles;
48 private bool treatProductAsModule;
49
50 /// <summary>
51 /// Creates a new decompiler object with a default set of table definitions.
52 /// </summary>
53 public Decompiler()
54 {
55 this.standardActions = WindowsInstallerStandard.GetStandardActions();
56
57 this.extensions = new List<IDecompilerExtension>();
58 this.extensionsByTableName = new Dictionary<string,IDecompilerExtension>();
59 this.patchTargetFiles = new Hashtable();
60 this.sequenceElements = new Hashtable();
61 this.tableDefinitions = new TableDefinitionCollection();
62 this.exportFilePath = "SourceDir";
63 }
64
65 /// <summary>
66 /// Gets or sets the base source file path.
67 /// </summary>
68 /// <value>Base source file path.</value>
69 public string ExportFilePath
70 {
71 get { return this.exportFilePath; }
72 set { this.exportFilePath = value; }
73 }
74
75 /// <summary>
76 /// Gets or sets the option to show pedantic messages.
77 /// </summary>
78 /// <value>The option to show pedantic messages.</value>
79 public bool ShowPedanticMessages
80 {
81 get { return this.showPedanticMessages; }
82 set { this.showPedanticMessages = value; }
83 }
84
85 /// <summary>
86 /// Gets or sets the option to suppress custom tables.
87 /// </summary>
88 /// <value>The option to suppress dropping empty tables.</value>
89 public bool SuppressCustomTables
90 {
91 get { return this.suppressCustomTables; }
92 set { this.suppressCustomTables = value; }
93 }
94
95 /// <summary>
96 /// Gets or sets the option to suppress dropping empty tables.
97 /// </summary>
98 /// <value>The option to suppress dropping empty tables.</value>
99 public bool SuppressDroppingEmptyTables
100 {
101 get { return this.suppressDroppingEmptyTables; }
102 set { this.suppressDroppingEmptyTables = value; }
103 }
104
105 /// <summary>
106 /// Gets or sets the option to suppress decompiling with relative action sequencing (uses sequence numbers).
107 /// </summary>
108 /// <value>The option to suppress decompiling with relative action sequencing (uses sequence numbers).</value>
109 public bool SuppressRelativeActionSequencing
110 {
111 get { return this.suppressRelativeActionSequencing; }
112 set { this.suppressRelativeActionSequencing = value; }
113 }
114
115 /// <summary>
116 /// Gets or sets the option to suppress decompiling UI-related tables.
117 /// </summary>
118 /// <value>The option to suppress decompiling UI-related tables.</value>
119 public bool SuppressUI
120 {
121 get { return this.suppressUI; }
122 set { this.suppressUI = value; }
123 }
124
125 /// <summary>
126 /// Gets or sets the temporary path for the Decompiler. If left null, the decompiler
127 /// will use %TEMP% environment variable.
128 /// </summary>
129 /// <value>Path to temp files.</value>
130 public string TempFilesLocation
131 {
132 get
133 {
134 // return null == this.tempFiles ? String.Empty : this.tempFiles.BasePath;
135 return Path.GetTempPath();
136 }
137
138 // set
139 // {
140 // if (null == value)
141 // {
142 // this.tempFiles = new TempFileCollection();
143 // }
144 // else
145 // {
146 // this.tempFiles = new TempFileCollection(value);
147 // }
148 // }
149 }
150
151 /// <summary>
152 /// Gets or sets whether the decompiler should use module logic on a product output.
153 /// </summary>
154 /// <value>The option to treat a product like a module</value>
155 public bool TreatProductAsModule
156 {
157 get { return this.treatProductAsModule; }
158 set { this.treatProductAsModule = value; }
159 }
160
161 /// <summary>
162 /// Decompile the database file.
163 /// </summary>
164 /// <param name="output">The output to decompile.</param>
165 /// <returns>The serialized WiX source code.</returns>
166 [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "System.InvalidOperationException.#ctor(System.String)")]
167 public Wix.Wix Decompile(Output output)
168 {
169 if (null == output)
170 {
171 throw new ArgumentNullException("output");
172 }
173
174 this.codepage = output.Codepage;
175 this.outputType = output.Type;
176
177 // collect the table definitions from the output
178 this.tableDefinitions.Clear();
179 foreach (Table table in output.Tables)
180 {
181 this.tableDefinitions.Add(table.Definition);
182 }
183
184 // add any missing standard and wix-specific table definitions
185 foreach (TableDefinition tableDefinition in WindowsInstallerStandard.GetTableDefinitions())
186 {
187 if (!this.tableDefinitions.Contains(tableDefinition.Name))
188 {
189 this.tableDefinitions.Add(tableDefinition);
190 }
191 }
192
193 // add any missing extension table definitions
194 foreach (IDecompilerExtension extension in this.extensions)
195 {
196 if (null != extension.TableDefinitions)
197 {
198 foreach (TableDefinition tableDefinition in extension.TableDefinitions)
199 {
200 if (!this.tableDefinitions.Contains(tableDefinition.Name))
201 {
202 this.tableDefinitions.Add(tableDefinition);
203 }
204 }
205 }
206 }
207
208 // if we don't have the temporary files object yet, get one
209#if REDO_IN_NETCORE
210 if (null == this.tempFiles)
211 {
212 this.TempFilesLocation = null;
213 }
214#endif
215 Directory.CreateDirectory(this.TempFilesLocation); // ensure the base path is there
216
217 bool encounteredError = false;
218 Wix.IParentElement rootElement;
219 Wix.Wix wixElement = new Wix.Wix();
220
221 switch (this.outputType)
222 {
223 case OutputType.Module:
224 rootElement = new Wix.Module();
225 break;
226 case OutputType.PatchCreation:
227 rootElement = new Wix.PatchCreation();
228 break;
229 case OutputType.Product:
230 rootElement = new Wix.Product();
231 break;
232 default:
233 throw new InvalidOperationException(WixStrings.EXP_UnknownOutputType);
234 }
235 wixElement.AddChild((Wix.ISchemaElement)rootElement);
236
237 // try to decompile the database file
238 try
239 {
240 this.core = new DecompilerCore(rootElement);
241 this.core.ShowPedanticMessages = this.showPedanticMessages;
242
243 // stop processing if an error previously occurred
244 if (this.core.EncounteredError)
245 {
246 return null;
247 }
248
249 // initialize the decompiler and its extensions
250 foreach (IDecompilerExtension extension in this.extensions)
251 {
252 extension.Core = this.core;
253 extension.Initialize(output.Tables);
254 }
255 this.InitializeDecompile(output.Tables);
256
257 // stop processing if an error previously occurred
258 if (this.core.EncounteredError)
259 {
260 return null;
261 }
262
263 // decompile the tables
264 this.DecompileTables(output);
265
266 // finalize the decompiler and its extensions
267 this.FinalizeDecompile(output.Tables);
268 foreach (IDecompilerExtension extension in this.extensions)
269 {
270 extension.Finish(output.Tables);
271 }
272 }
273 finally
274 {
275 encounteredError = this.core.EncounteredError;
276
277 this.core = null;
278 foreach (IDecompilerExtension extension in this.extensions)
279 {
280 extension.Core = null;
281 }
282 }
283
284 // return the root element only if decompilation completed successfully
285 return (encounteredError ? null : wixElement);
286 }
287
288 /// <summary>
289 /// Adds an extension.
290 /// </summary>
291 /// <param name="extension">The extension to add.</param>
292 public void AddExtension(IDecompilerExtension extension)
293 {
294 this.extensions.Add(extension);
295
296 if (null != extension.TableDefinitions)
297 {
298 foreach (TableDefinition tableDefinition in extension.TableDefinitions)
299 {
300 if (!this.extensionsByTableName.ContainsKey(tableDefinition.Name))
301 {
302 this.extensionsByTableName.Add(tableDefinition.Name, extension);
303 }
304 else
305 {
306 Messaging.Instance.OnMessage(WixErrors.DuplicateExtensionTable(extension.GetType().ToString(), tableDefinition.Name));
307 }
308 }
309 }
310 }
311
312 /// <summary>
313 /// Cleans up the temp files used by the Decompiler.
314 /// </summary>
315 /// <returns>True if all files were deleted, false otherwise.</returns>
316 /// <remarks>
317 /// This should be called after every call to Decompile to ensure there
318 /// are no conflicts between each decompiled database.
319 /// </remarks>
320 public bool DeleteTempFiles()
321 {
322#if REDO_IN_NETCORE
323 if (null == this.tempFiles)
324 {
325 return true; // no work to do
326 }
327 else
328 {
329 bool deleted = Common.DeleteTempFiles(this.tempFiles.BasePath, this.core);
330
331 if (deleted)
332 {
333 this.tempFiles = null; // temp files have been deleted, no need to remember this now
334 }
335
336 return deleted;
337 }
338#endif
339 return true;
340 }
341
342 /// <summary>
343 /// Set the common control attributes in a control element.
344 /// </summary>
345 /// <param name="attributes">The control attributes.</param>
346 /// <param name="control">The control element.</param>
347 private static void SetControlAttributes(int attributes, Wix.Control control)
348 {
349 if (0 == (attributes & MsiInterop.MsidbControlAttributesEnabled))
350 {
351 control.Disabled = Wix.YesNoType.yes;
352 }
353
354 if (MsiInterop.MsidbControlAttributesIndirect == (attributes & MsiInterop.MsidbControlAttributesIndirect))
355 {
356 control.Indirect = Wix.YesNoType.yes;
357 }
358
359 if (MsiInterop.MsidbControlAttributesInteger == (attributes & MsiInterop.MsidbControlAttributesInteger))
360 {
361 control.Integer = Wix.YesNoType.yes;
362 }
363
364 if (MsiInterop.MsidbControlAttributesLeftScroll == (attributes & MsiInterop.MsidbControlAttributesLeftScroll))
365 {
366 control.LeftScroll = Wix.YesNoType.yes;
367 }
368
369 if (MsiInterop.MsidbControlAttributesRightAligned == (attributes & MsiInterop.MsidbControlAttributesRightAligned))
370 {
371 control.RightAligned = Wix.YesNoType.yes;
372 }
373
374 if (MsiInterop.MsidbControlAttributesRTLRO == (attributes & MsiInterop.MsidbControlAttributesRTLRO))
375 {
376 control.RightToLeft = Wix.YesNoType.yes;
377 }
378
379 if (MsiInterop.MsidbControlAttributesSunken == (attributes & MsiInterop.MsidbControlAttributesSunken))
380 {
381 control.Sunken = Wix.YesNoType.yes;
382 }
383
384 if (0 == (attributes & MsiInterop.MsidbControlAttributesVisible))
385 {
386 control.Hidden = Wix.YesNoType.yes;
387 }
388 }
389
390 /// <summary>
391 /// Creates an action element.
392 /// </summary>
393 /// <param name="actionRow">The action row from which the element should be created.</param>
394 private void CreateActionElement(WixActionRow actionRow)
395 {
396 Wix.ISchemaElement actionElement = null;
397
398 if (null != this.core.GetIndexedElement("CustomAction", actionRow.Action)) // custom action
399 {
400 Wix.Custom custom = new Wix.Custom();
401
402 custom.Action = actionRow.Action;
403
404 if (null != actionRow.Condition)
405 {
406 custom.Content = actionRow.Condition;
407 }
408
409 switch (actionRow.Sequence)
410 {
411 case (-4):
412 custom.OnExit = Wix.ExitType.suspend;
413 break;
414 case (-3):
415 custom.OnExit = Wix.ExitType.error;
416 break;
417 case (-2):
418 custom.OnExit = Wix.ExitType.cancel;
419 break;
420 case (-1):
421 custom.OnExit = Wix.ExitType.success;
422 break;
423 default:
424 if (null != actionRow.Before)
425 {
426 custom.Before = actionRow.Before;
427 }
428 else if (null != actionRow.After)
429 {
430 custom.After = actionRow.After;
431 }
432 else if (0 < actionRow.Sequence)
433 {
434 custom.Sequence = actionRow.Sequence;
435 }
436 break;
437 }
438
439 actionElement = custom;
440 }
441 else if (null != this.core.GetIndexedElement("Dialog", actionRow.Action)) // dialog
442 {
443 Wix.Show show = new Wix.Show();
444
445 show.Dialog = actionRow.Action;
446
447 if (null != actionRow.Condition)
448 {
449 show.Content = actionRow.Condition;
450 }
451
452 switch (actionRow.Sequence)
453 {
454 case (-4):
455 show.OnExit = Wix.ExitType.suspend;
456 break;
457 case (-3):
458 show.OnExit = Wix.ExitType.error;
459 break;
460 case (-2):
461 show.OnExit = Wix.ExitType.cancel;
462 break;
463 case (-1):
464 show.OnExit = Wix.ExitType.success;
465 break;
466 default:
467 if (null != actionRow.Before)
468 {
469 show.Before = actionRow.Before;
470 }
471 else if (null != actionRow.After)
472 {
473 show.After = actionRow.After;
474 }
475 else if (0 < actionRow.Sequence)
476 {
477 show.Sequence = actionRow.Sequence;
478 }
479 break;
480 }
481
482 actionElement = show;
483 }
484 else // possibly a standard action without suggested sequence information
485 {
486 actionElement = this.CreateStandardActionElement(actionRow);
487 }
488
489 // add the action element to the appropriate sequence element
490 if (null != actionElement)
491 {
492 string sequenceTable = actionRow.SequenceTable.ToString();
493 Wix.IParentElement sequenceElement = (Wix.IParentElement)this.sequenceElements[sequenceTable];
494
495 if (null == sequenceElement)
496 {
497 switch (actionRow.SequenceTable)
498 {
499 case SequenceTable.AdminExecuteSequence:
500 sequenceElement = new Wix.AdminExecuteSequence();
501 break;
502 case SequenceTable.AdminUISequence:
503 sequenceElement = new Wix.AdminUISequence();
504 break;
505 case SequenceTable.AdvtExecuteSequence:
506 sequenceElement = new Wix.AdvertiseExecuteSequence();
507 break;
508 case SequenceTable.InstallExecuteSequence:
509 sequenceElement = new Wix.InstallExecuteSequence();
510 break;
511 case SequenceTable.InstallUISequence:
512 sequenceElement = new Wix.InstallUISequence();
513 break;
514 default:
515 throw new InvalidOperationException(WixStrings.EXP_UnknowSequenceTable);
516 }
517
518 this.core.RootElement.AddChild((Wix.ISchemaElement)sequenceElement);
519 this.sequenceElements.Add(sequenceTable, sequenceElement);
520 }
521
522 try
523 {
524 sequenceElement.AddChild(actionElement);
525 }
526 catch (System.ArgumentException) // action/dialog is not valid for this sequence
527 {
528 this.core.OnMessage(WixWarnings.IllegalActionInSequence(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action));
529 }
530 }
531 }
532
533 /// <summary>
534 /// Creates a standard action element.
535 /// </summary>
536 /// <param name="actionRow">The action row from which the element should be created.</param>
537 /// <returns>The created element.</returns>
538 private Wix.ISchemaElement CreateStandardActionElement(WixActionRow actionRow)
539 {
540 Wix.ActionSequenceType actionElement = null;
541
542 switch (actionRow.Action)
543 {
544 case "AllocateRegistrySpace":
545 actionElement = new Wix.AllocateRegistrySpace();
546 break;
547 case "AppSearch":
548 WixActionRow appSearchActionRow = this.standardActions[actionRow.SequenceTable, actionRow.Action];
549
550 if (null != actionRow.Before || null != actionRow.After || (null != appSearchActionRow && actionRow.Sequence != appSearchActionRow.Sequence))
551 {
552 Wix.AppSearch appSearch = new Wix.AppSearch();
553
554 if (null != actionRow.Condition)
555 {
556 appSearch.Content = actionRow.Condition;
557 }
558
559 if (null != actionRow.Before)
560 {
561 appSearch.Before = actionRow.Before;
562 }
563 else if (null != actionRow.After)
564 {
565 appSearch.After = actionRow.After;
566 }
567 else if (0 < actionRow.Sequence)
568 {
569 appSearch.Sequence = actionRow.Sequence;
570 }
571
572 return appSearch;
573 }
574 break;
575 case "BindImage":
576 actionElement = new Wix.BindImage();
577 break;
578 case "CCPSearch":
579 Wix.CCPSearch ccpSearch = new Wix.CCPSearch();
580 Decompiler.SequenceRelativeAction(actionRow, ccpSearch);
581 return ccpSearch;
582 case "CostFinalize":
583 actionElement = new Wix.CostFinalize();
584 break;
585 case "CostInitialize":
586 actionElement = new Wix.CostInitialize();
587 break;
588 case "CreateFolders":
589 actionElement = new Wix.CreateFolders();
590 break;
591 case "CreateShortcuts":
592 actionElement = new Wix.CreateShortcuts();
593 break;
594 case "DeleteServices":
595 actionElement = new Wix.DeleteServices();
596 break;
597 case "DisableRollback":
598 Wix.DisableRollback disableRollback = new Wix.DisableRollback();
599 Decompiler.SequenceRelativeAction(actionRow, disableRollback);
600 return disableRollback;
601 case "DuplicateFiles":
602 actionElement = new Wix.DuplicateFiles();
603 break;
604 case "ExecuteAction":
605 actionElement = new Wix.ExecuteAction();
606 break;
607 case "FileCost":
608 actionElement = new Wix.FileCost();
609 break;
610 case "FindRelatedProducts":
611 Wix.FindRelatedProducts findRelatedProducts = new Wix.FindRelatedProducts();
612 Decompiler.SequenceRelativeAction(actionRow, findRelatedProducts);
613 return findRelatedProducts;
614 case "ForceReboot":
615 Wix.ForceReboot forceReboot = new Wix.ForceReboot();
616 Decompiler.SequenceRelativeAction(actionRow, forceReboot);
617 return forceReboot;
618 case "InstallAdminPackage":
619 actionElement = new Wix.InstallAdminPackage();
620 break;
621 case "InstallExecute":
622 Wix.InstallExecute installExecute = new Wix.InstallExecute();
623 Decompiler.SequenceRelativeAction(actionRow, installExecute);
624 return installExecute;
625 case "InstallExecuteAgain":
626 Wix.InstallExecuteAgain installExecuteAgain = new Wix.InstallExecuteAgain();
627 Decompiler.SequenceRelativeAction(actionRow, installExecuteAgain);
628 return installExecuteAgain;
629 case "InstallFiles":
630 actionElement = new Wix.InstallFiles();
631 break;
632 case "InstallFinalize":
633 actionElement = new Wix.InstallFinalize();
634 break;
635 case "InstallInitialize":
636 actionElement = new Wix.InstallInitialize();
637 break;
638 case "InstallODBC":
639 actionElement = new Wix.InstallODBC();
640 break;
641 case "InstallServices":
642 actionElement = new Wix.InstallServices();
643 break;
644 case "InstallValidate":
645 actionElement = new Wix.InstallValidate();
646 break;
647 case "IsolateComponents":
648 actionElement = new Wix.IsolateComponents();
649 break;
650 case "LaunchConditions":
651 Wix.LaunchConditions launchConditions = new Wix.LaunchConditions();
652 Decompiler.SequenceRelativeAction(actionRow, launchConditions);
653 return launchConditions;
654 case "MigrateFeatureStates":
655 actionElement = new Wix.MigrateFeatureStates();
656 break;
657 case "MoveFiles":
658 actionElement = new Wix.MoveFiles();
659 break;
660 case "MsiPublishAssemblies":
661 actionElement = new Wix.MsiPublishAssemblies();
662 break;
663 case "MsiUnpublishAssemblies":
664 actionElement = new Wix.MsiUnpublishAssemblies();
665 break;
666 case "PatchFiles":
667 actionElement = new Wix.PatchFiles();
668 break;
669 case "ProcessComponents":
670 actionElement = new Wix.ProcessComponents();
671 break;
672 case "PublishComponents":
673 actionElement = new Wix.PublishComponents();
674 break;
675 case "PublishFeatures":
676 actionElement = new Wix.PublishFeatures();
677 break;
678 case "PublishProduct":
679 actionElement = new Wix.PublishProduct();
680 break;
681 case "RegisterClassInfo":
682 actionElement = new Wix.RegisterClassInfo();
683 break;
684 case "RegisterComPlus":
685 actionElement = new Wix.RegisterComPlus();
686 break;
687 case "RegisterExtensionInfo":
688 actionElement = new Wix.RegisterExtensionInfo();
689 break;
690 case "RegisterFonts":
691 actionElement = new Wix.RegisterFonts();
692 break;
693 case "RegisterMIMEInfo":
694 actionElement = new Wix.RegisterMIMEInfo();
695 break;
696 case "RegisterProduct":
697 actionElement = new Wix.RegisterProduct();
698 break;
699 case "RegisterProgIdInfo":
700 actionElement = new Wix.RegisterProgIdInfo();
701 break;
702 case "RegisterTypeLibraries":
703 actionElement = new Wix.RegisterTypeLibraries();
704 break;
705 case "RegisterUser":
706 actionElement = new Wix.RegisterUser();
707 break;
708 case "RemoveDuplicateFiles":
709 actionElement = new Wix.RemoveDuplicateFiles();
710 break;
711 case "RemoveEnvironmentStrings":
712 actionElement = new Wix.RemoveEnvironmentStrings();
713 break;
714 case "RemoveExistingProducts":
715 Wix.RemoveExistingProducts removeExistingProducts = new Wix.RemoveExistingProducts();
716 Decompiler.SequenceRelativeAction(actionRow, removeExistingProducts);
717 return removeExistingProducts;
718 case "RemoveFiles":
719 actionElement = new Wix.RemoveFiles();
720 break;
721 case "RemoveFolders":
722 actionElement = new Wix.RemoveFolders();
723 break;
724 case "RemoveIniValues":
725 actionElement = new Wix.RemoveIniValues();
726 break;
727 case "RemoveODBC":
728 actionElement = new Wix.RemoveODBC();
729 break;
730 case "RemoveRegistryValues":
731 actionElement = new Wix.RemoveRegistryValues();
732 break;
733 case "RemoveShortcuts":
734 actionElement = new Wix.RemoveShortcuts();
735 break;
736 case "ResolveSource":
737 Wix.ResolveSource resolveSource = new Wix.ResolveSource();
738 Decompiler.SequenceRelativeAction(actionRow, resolveSource);
739 return resolveSource;
740 case "RMCCPSearch":
741 Wix.RMCCPSearch rmccpSearch = new Wix.RMCCPSearch();
742 Decompiler.SequenceRelativeAction(actionRow, rmccpSearch);
743 return rmccpSearch;
744 case "ScheduleReboot":
745 Wix.ScheduleReboot scheduleReboot = new Wix.ScheduleReboot();
746 Decompiler.SequenceRelativeAction(actionRow, scheduleReboot);
747 return scheduleReboot;
748 case "SelfRegModules":
749 actionElement = new Wix.SelfRegModules();
750 break;
751 case "SelfUnregModules":
752 actionElement = new Wix.SelfUnregModules();
753 break;
754 case "SetODBCFolders":
755 actionElement = new Wix.SetODBCFolders();
756 break;
757 case "StartServices":
758 actionElement = new Wix.StartServices();
759 break;
760 case "StopServices":
761 actionElement = new Wix.StopServices();
762 break;
763 case "UnpublishComponents":
764 actionElement = new Wix.UnpublishComponents();
765 break;
766 case "UnpublishFeatures":
767 actionElement = new Wix.UnpublishFeatures();
768 break;
769 case "UnregisterClassInfo":
770 actionElement = new Wix.UnregisterClassInfo();
771 break;
772 case "UnregisterComPlus":
773 actionElement = new Wix.UnregisterComPlus();
774 break;
775 case "UnregisterExtensionInfo":
776 actionElement = new Wix.UnregisterExtensionInfo();
777 break;
778 case "UnregisterFonts":
779 actionElement = new Wix.UnregisterFonts();
780 break;
781 case "UnregisterMIMEInfo":
782 actionElement = new Wix.UnregisterMIMEInfo();
783 break;
784 case "UnregisterProgIdInfo":
785 actionElement = new Wix.UnregisterProgIdInfo();
786 break;
787 case "UnregisterTypeLibraries":
788 actionElement = new Wix.UnregisterTypeLibraries();
789 break;
790 case "ValidateProductID":
791 actionElement = new Wix.ValidateProductID();
792 break;
793 case "WriteEnvironmentStrings":
794 actionElement = new Wix.WriteEnvironmentStrings();
795 break;
796 case "WriteIniValues":
797 actionElement = new Wix.WriteIniValues();
798 break;
799 case "WriteRegistryValues":
800 actionElement = new Wix.WriteRegistryValues();
801 break;
802 default:
803 this.core.OnMessage(WixWarnings.UnknownAction(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action));
804 return null;
805 }
806
807 if (actionElement != null)
808 {
809 this.SequenceStandardAction(actionRow, actionElement);
810 }
811
812 return actionElement;
813 }
814
815 /// <summary>
816 /// Applies the condition and sequence to a standard action element based on the action row data.
817 /// </summary>
818 /// <param name="actionRow">Action row data from the database.</param>
819 /// <param name="actionElement">Element to be sequenced.</param>
820 private void SequenceStandardAction(WixActionRow actionRow, Wix.ActionSequenceType actionElement)
821 {
822 if (null != actionRow.Condition)
823 {
824 actionElement.Content = actionRow.Condition;
825 }
826
827 if ((null != actionRow.Before || null != actionRow.After) && 0 == actionRow.Sequence)
828 {
829 this.core.OnMessage(WixWarnings.DecompiledStandardActionRelativelyScheduledInModule(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action));
830 }
831 else if (0 < actionRow.Sequence)
832 {
833 actionElement.Sequence = actionRow.Sequence;
834 }
835 }
836
837 /// <summary>
838 /// Applies the condition and relative sequence to an action element based on the action row data.
839 /// </summary>
840 /// <param name="actionRow">Action row data from the database.</param>
841 /// <param name="actionElement">Element to be sequenced.</param>
842 private static void SequenceRelativeAction(WixActionRow actionRow, Wix.ActionModuleSequenceType actionElement)
843 {
844 if (null != actionRow.Condition)
845 {
846 actionElement.Content = actionRow.Condition;
847 }
848
849 if (null != actionRow.Before)
850 {
851 actionElement.Before = actionRow.Before;
852 }
853 else if (null != actionRow.After)
854 {
855 actionElement.After = actionRow.After;
856 }
857 else if (0 < actionRow.Sequence)
858 {
859 actionElement.Sequence = actionRow.Sequence;
860 }
861 }
862
863 /// <summary>
864 /// Ensure that a particular property exists in the decompiled output.
865 /// </summary>
866 /// <param name="id">The identifier of the property.</param>
867 /// <returns>The property element.</returns>
868 private Wix.Property EnsureProperty(string id)
869 {
870 Wix.Property property = (Wix.Property)this.core.GetIndexedElement("Property", id);
871
872 if (null == property)
873 {
874 property = new Wix.Property();
875 property.Id = id;
876
877 // create a dummy row for indexing
878 Row row = new Row(null, this.tableDefinitions["Property"]);
879 row[0] = id;
880
881 this.core.RootElement.AddChild(property);
882 this.core.IndexElement(row, property);
883 }
884
885 return property;
886 }
887
888 /// <summary>
889 /// Finalize decompilation.
890 /// </summary>
891 /// <param name="tables">The collection of all tables.</param>
892 private void FinalizeDecompile(TableIndexedCollection tables)
893 {
894 if (OutputType.PatchCreation == this.outputType)
895 {
896 this.FinalizeFamilyFileRangesTable(tables);
897 }
898 else
899 {
900 this.FinalizeCheckBoxTable(tables);
901 this.FinalizeComponentTable(tables);
902 this.FinalizeDialogTable(tables);
903 this.FinalizeDuplicateMoveFileTables(tables);
904 this.FinalizeFeatureComponentsTable(tables);
905 this.FinalizeFileTable(tables);
906 this.FinalizeMIMETable(tables);
907 this.FinalizeMsiLockPermissionsExTable(tables);
908 this.FinalizeLockPermissionsTable(tables);
909 this.FinalizeProgIdTable(tables);
910 this.FinalizePropertyTable(tables);
911 this.FinalizeRemoveFileTable(tables);
912 this.FinalizeSearchTables(tables);
913 this.FinalizeUpgradeTable(tables);
914 this.FinalizeSequenceTables(tables);
915 this.FinalizeVerbTable(tables);
916 }
917 }
918
919 /// <summary>
920 /// Finalize the CheckBox table.
921 /// </summary>
922 /// <param name="tables">The collection of all tables.</param>
923 /// <remarks>
924 /// Enumerates through all the Control rows, looking for controls of type "CheckBox" with
925 /// a value in the Property column. This is then possibly matched up with a CheckBox row
926 /// to retrieve a CheckBoxValue. There is no foreign key from the Control to CheckBox table.
927 /// </remarks>
928 private void FinalizeCheckBoxTable(TableIndexedCollection tables)
929 {
930 // if the user has requested to suppress the UI elements, we have nothing to do
931 if (this.suppressUI)
932 {
933 return;
934 }
935
936 Table checkBoxTable = tables["CheckBox"];
937 Table controlTable = tables["Control"];
938
939 Hashtable checkBoxes = new Hashtable();
940 Hashtable checkBoxProperties = new Hashtable();
941
942 // index the CheckBox table
943 if (null != checkBoxTable)
944 {
945 foreach (Row row in checkBoxTable.Rows)
946 {
947 checkBoxes.Add(row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), row);
948 checkBoxProperties.Add(row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), false);
949 }
950 }
951
952 // enumerate through the Control table, adding CheckBox values where appropriate
953 if (null != controlTable)
954 {
955 foreach (Row row in controlTable.Rows)
956 {
957 Wix.Control control = (Wix.Control)this.core.GetIndexedElement(row);
958
959 if ("CheckBox" == Convert.ToString(row[2]) && null != row[8])
960 {
961 Row checkBoxRow = (Row)checkBoxes[row[8]];
962
963 if (null == checkBoxRow)
964 {
965 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, "Control", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Property", Convert.ToString(row[8]), "CheckBox"));
966 }
967 else
968 {
969 // if we've seen this property already, create a reference to it
970 if (Convert.ToBoolean(checkBoxProperties[row[8]]))
971 {
972 control.CheckBoxPropertyRef = Convert.ToString(row[8]);
973 }
974 else
975 {
976 control.Property = Convert.ToString(row[8]);
977 checkBoxProperties[row[8]] = true;
978 }
979
980 if (null != checkBoxRow[1])
981 {
982 control.CheckBoxValue = Convert.ToString(checkBoxRow[1]);
983 }
984 }
985 }
986 }
987 }
988 }
989
990 /// <summary>
991 /// Finalize the Component table.
992 /// </summary>
993 /// <param name="tables">The collection of all tables.</param>
994 /// <remarks>
995 /// Set the keypaths for each component.
996 /// </remarks>
997 private void FinalizeComponentTable(TableIndexedCollection tables)
998 {
999 Table componentTable = tables["Component"];
1000 Table fileTable = tables["File"];
1001 Table odbcDataSourceTable = tables["ODBCDataSource"];
1002 Table registryTable = tables["Registry"];
1003
1004 // set the component keypaths
1005 if (null != componentTable)
1006 {
1007 foreach (Row row in componentTable.Rows)
1008 {
1009 int attributes = Convert.ToInt32(row[3]);
1010
1011 if (null == row[5])
1012 {
1013 Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[0]));
1014
1015 component.KeyPath = Wix.YesNoType.yes;
1016 }
1017 else if (MsiInterop.MsidbComponentAttributesRegistryKeyPath == (attributes & MsiInterop.MsidbComponentAttributesRegistryKeyPath))
1018 {
1019 object registryObject = this.core.GetIndexedElement("Registry", Convert.ToString(row[5]));
1020
1021 if (null != registryObject)
1022 {
1023 Wix.RegistryValue registryValue = registryObject as Wix.RegistryValue;
1024
1025 if (null != registryValue)
1026 {
1027 registryValue.KeyPath = Wix.YesNoType.yes;
1028 }
1029 else
1030 {
1031 this.core.OnMessage(WixWarnings.IllegalRegistryKeyPath(row.SourceLineNumbers, "Component", Convert.ToString(row[5])));
1032 }
1033 }
1034 else
1035 {
1036 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, "Component", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "KeyPath", Convert.ToString(row[5]), "Registry"));
1037 }
1038 }
1039 else if (MsiInterop.MsidbComponentAttributesODBCDataSource == (attributes & MsiInterop.MsidbComponentAttributesODBCDataSource))
1040 {
1041 Wix.ODBCDataSource odbcDataSource = (Wix.ODBCDataSource)this.core.GetIndexedElement("ODBCDataSource", Convert.ToString(row[5]));
1042
1043 if (null != odbcDataSource)
1044 {
1045 odbcDataSource.KeyPath = Wix.YesNoType.yes;
1046 }
1047 else
1048 {
1049 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, "Component", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "KeyPath", Convert.ToString(row[5]), "ODBCDataSource"));
1050 }
1051 }
1052 else
1053 {
1054 Wix.File file = (Wix.File)this.core.GetIndexedElement("File", Convert.ToString(row[5]));
1055
1056 if (null != file)
1057 {
1058 file.KeyPath = Wix.YesNoType.yes;
1059 }
1060 else
1061 {
1062 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, "Component", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "KeyPath", Convert.ToString(row[5]), "File"));
1063 }
1064 }
1065 }
1066 }
1067
1068 // add the File children elements
1069 if (null != fileTable)
1070 {
1071 foreach (FileRow fileRow in fileTable.Rows)
1072 {
1073 Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", fileRow.Component);
1074 Wix.File file = (Wix.File)this.core.GetIndexedElement(fileRow);
1075
1076 if (null != component)
1077 {
1078 component.AddChild(file);
1079 }
1080 else
1081 {
1082 this.core.OnMessage(WixWarnings.ExpectedForeignRow(fileRow.SourceLineNumbers, "File", fileRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", fileRow.Component, "Component"));
1083 }
1084 }
1085 }
1086
1087 // add the ODBCDataSource children elements
1088 if (null != odbcDataSourceTable)
1089 {
1090 foreach (Row row in odbcDataSourceTable.Rows)
1091 {
1092 Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1]));
1093 Wix.ODBCDataSource odbcDataSource = (Wix.ODBCDataSource)this.core.GetIndexedElement(row);
1094
1095 if (null != component)
1096 {
1097 component.AddChild(odbcDataSource);
1098 }
1099 else
1100 {
1101 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, "ODBCDataSource", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component"));
1102 }
1103 }
1104 }
1105
1106 // add the Registry children elements
1107 if (null != registryTable)
1108 {
1109 foreach (Row row in registryTable.Rows)
1110 {
1111 Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[5]));
1112 Wix.ISchemaElement registryElement = (Wix.ISchemaElement)this.core.GetIndexedElement(row);
1113
1114 if (null != component)
1115 {
1116 component.AddChild(registryElement);
1117 }
1118 else
1119 {
1120 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, "Registry", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[5]), "Component"));
1121 }
1122 }
1123 }
1124 }
1125
1126 /// <summary>
1127 /// Finalize the Dialog table.
1128 /// </summary>
1129 /// <param name="tables">The collection of all tables.</param>
1130 /// <remarks>
1131 /// Sets the first, default, and cancel control for each dialog and adds all child control
1132 /// elements to the dialog.
1133 /// </remarks>
1134 private void FinalizeDialogTable(TableIndexedCollection tables)
1135 {
1136 // if the user has requested to suppress the UI elements, we have nothing to do
1137 if (this.suppressUI)
1138 {
1139 return;
1140 }
1141
1142 Table controlTable = tables["Control"];
1143 Table dialogTable = tables["Dialog"];
1144
1145 Hashtable addedControls = new Hashtable();
1146 Hashtable controlRows = new Hashtable();
1147
1148 // index the rows in the control rows (because we need the Control_Next value)
1149 if (null != controlTable)
1150 {
1151 foreach (Row row in controlTable.Rows)
1152 {
1153 controlRows.Add(row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), row);
1154 }
1155 }
1156
1157 if (null != dialogTable)
1158 {
1159 foreach (Row row in dialogTable.Rows)
1160 {
1161 Wix.Dialog dialog = (Wix.Dialog)this.core.GetIndexedElement(row);
1162 string dialogId = Convert.ToString(row[0]);
1163
1164 Wix.Control control = (Wix.Control)this.core.GetIndexedElement("Control", dialogId, Convert.ToString(row[7]));
1165 if (null == control)
1166 {
1167 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, "Dialog", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog", dialogId, "Control_First", Convert.ToString(row[7]), "Control"));
1168 }
1169
1170 // add tabbable controls
1171 while (null != control)
1172 {
1173 Row controlRow = (Row)controlRows[String.Concat(dialogId, DecompilerConstants.PrimaryKeyDelimiter, control.Id)];
1174
1175 control.TabSkip = Wix.YesNoType.no;
1176 dialog.AddChild(control);
1177 addedControls.Add(control, null);
1178
1179 if (null != controlRow[10])
1180 {
1181 control = (Wix.Control)this.core.GetIndexedElement("Control", dialogId, Convert.ToString(controlRow[10]));
1182 if (null != control)
1183 {
1184 // looped back to the first control in the dialog
1185 if (addedControls.Contains(control))
1186 {
1187 control = null;
1188 }
1189 }
1190 else
1191 {
1192 this.core.OnMessage(WixWarnings.ExpectedForeignRow(controlRow.SourceLineNumbers, "Control", controlRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", dialogId, "Control_Next", Convert.ToString(controlRow[10]), "Control"));
1193 }
1194 }
1195 else
1196 {
1197 control = null;
1198 }
1199 }
1200
1201 // set default control
1202 if (null != row[8])
1203 {
1204 Wix.Control defaultControl = (Wix.Control)this.core.GetIndexedElement("Control", dialogId, Convert.ToString(row[8]));
1205
1206 if (null != defaultControl)
1207 {
1208 defaultControl.Default = Wix.YesNoType.yes;
1209 }
1210 else
1211 {
1212 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, "Dialog", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog", dialogId, "Control_Default", Convert.ToString(row[8]), "Control"));
1213 }
1214 }
1215
1216 // set cancel control
1217 if (null != row[9])
1218 {
1219 Wix.Control cancelControl = (Wix.Control)this.core.GetIndexedElement("Control", dialogId, Convert.ToString(row[9]));
1220
1221 if (null != cancelControl)
1222 {
1223 cancelControl.Cancel = Wix.YesNoType.yes;
1224 }
1225 else
1226 {
1227 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, "Dialog", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog", dialogId, "Control_Cancel", Convert.ToString(row[9]), "Control"));
1228 }
1229 }
1230 }
1231 }
1232
1233 // add the non-tabbable controls to the dialog
1234 if (null != controlTable)
1235 {
1236 foreach (Row row in controlTable.Rows)
1237 {
1238 Wix.Control control = (Wix.Control)this.core.GetIndexedElement(row);
1239 Wix.Dialog dialog = (Wix.Dialog)this.core.GetIndexedElement("Dialog", Convert.ToString(row[0]));
1240
1241 if (null == dialog)
1242 {
1243 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, "Control", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", Convert.ToString(row[0]), "Dialog"));
1244 continue;
1245 }
1246
1247 if (!addedControls.Contains(control))
1248 {
1249 control.TabSkip = Wix.YesNoType.yes;
1250 dialog.AddChild(control);
1251 }
1252 }
1253 }
1254 }
1255
1256 /// <summary>
1257 /// Finalize the DuplicateFile and MoveFile tables.
1258 /// </summary>
1259 /// <param name="tables">The collection of all tables.</param>
1260 /// <remarks>
1261 /// Sets the source/destination property/directory for each DuplicateFile or
1262 /// MoveFile row.
1263 /// </remarks>
1264 private void FinalizeDuplicateMoveFileTables(TableIndexedCollection tables)
1265 {
1266 Table duplicateFileTable = tables["DuplicateFile"];
1267 Table moveFileTable = tables["MoveFile"];
1268
1269 if (null != duplicateFileTable)
1270 {
1271 foreach (Row row in duplicateFileTable.Rows)
1272 {
1273 Wix.CopyFile copyFile = (Wix.CopyFile)this.core.GetIndexedElement(row);
1274
1275 if (null != row[4])
1276 {
1277 if (null != this.core.GetIndexedElement("Directory", Convert.ToString(row[4])))
1278 {
1279 copyFile.DestinationDirectory = Convert.ToString(row[4]);
1280 }
1281 else
1282 {
1283 copyFile.DestinationProperty = Convert.ToString(row[4]);
1284 }
1285 }
1286 }
1287 }
1288
1289 if (null != moveFileTable)
1290 {
1291 foreach (Row row in moveFileTable.Rows)
1292 {
1293 Wix.CopyFile copyFile = (Wix.CopyFile)this.core.GetIndexedElement(row);
1294
1295 if (null != row[4])
1296 {
1297 if (null != this.core.GetIndexedElement("Directory", Convert.ToString(row[4])))
1298 {
1299 copyFile.SourceDirectory = Convert.ToString(row[4]);
1300 }
1301 else
1302 {
1303 copyFile.SourceProperty = Convert.ToString(row[4]);
1304 }
1305 }
1306
1307 if (null != this.core.GetIndexedElement("Directory", Convert.ToString(row[5])))
1308 {
1309 copyFile.DestinationDirectory = Convert.ToString(row[5]);
1310 }
1311 else
1312 {
1313 copyFile.DestinationProperty = Convert.ToString(row[5]);
1314 }
1315 }
1316 }
1317 }
1318
1319 /// <summary>
1320 /// Finalize the FamilyFileRanges table.
1321 /// </summary>
1322 /// <param name="tables">The collection of all tables.</param>
1323 private void FinalizeFamilyFileRangesTable(TableIndexedCollection tables)
1324 {
1325 Table externalFilesTable = tables["ExternalFiles"];
1326 Table familyFileRangesTable = tables["FamilyFileRanges"];
1327 Table targetFiles_OptionalDataTable = tables["TargetFiles_OptionalData"];
1328
1329 Hashtable usedProtectRanges = new Hashtable();
1330
1331 if (null != familyFileRangesTable)
1332 {
1333 foreach (Row row in familyFileRangesTable.Rows)
1334 {
1335 Wix.ProtectRange protectRange = new Wix.ProtectRange();
1336
1337 if (null != row[2] && null != row[3])
1338 {
1339 string[] retainOffsets = (Convert.ToString(row[2])).Split(',');
1340 string[] retainLengths = (Convert.ToString(row[3])).Split(',');
1341
1342 if (retainOffsets.Length == retainLengths.Length)
1343 {
1344 for (int i = 0; i < retainOffsets.Length; i++)
1345 {
1346 if (retainOffsets[i].StartsWith("0x", StringComparison.Ordinal))
1347 {
1348 protectRange.Offset = Convert.ToInt32(retainOffsets[i].Substring(2), 16);
1349 }
1350 else
1351 {
1352 protectRange.Offset = Convert.ToInt32(retainOffsets[i], CultureInfo.InvariantCulture);
1353 }
1354
1355 if (retainLengths[i].StartsWith("0x", StringComparison.Ordinal))
1356 {
1357 protectRange.Length = Convert.ToInt32(retainLengths[i].Substring(2), 16);
1358 }
1359 else
1360 {
1361 protectRange.Length = Convert.ToInt32(retainLengths[i], CultureInfo.InvariantCulture);
1362 }
1363 }
1364 }
1365 else
1366 {
1367 // TODO: warn
1368 }
1369 }
1370 else if (null != row[2] || null != row[3])
1371 {
1372 // TODO: warn about mismatch between columns
1373 }
1374
1375 this.core.IndexElement(row, protectRange);
1376 }
1377 }
1378
1379 if (null != externalFilesTable)
1380 {
1381 foreach (Row row in externalFilesTable.Rows)
1382 {
1383 Wix.ExternalFile externalFile = (Wix.ExternalFile)this.core.GetIndexedElement(row);
1384
1385 Wix.ProtectRange protectRange = (Wix.ProtectRange)this.core.GetIndexedElement("FamilyFileRanges", Convert.ToString(row[0]), Convert.ToString(row[1]));
1386 if (null != protectRange)
1387 {
1388 externalFile.AddChild(protectRange);
1389 usedProtectRanges[protectRange] = null;
1390 }
1391 }
1392 }
1393
1394 if (null != targetFiles_OptionalDataTable)
1395 {
1396 Table targetImagesTable = tables["TargetImages"];
1397 Table upgradedImagesTable = tables["UpgradedImages"];
1398
1399 Hashtable targetImageRows = new Hashtable();
1400 Hashtable upgradedImagesRows = new Hashtable();
1401
1402 // index the TargetImages table
1403 if (null != targetImagesTable)
1404 {
1405 foreach (Row row in targetImagesTable.Rows)
1406 {
1407 targetImageRows.Add(row[0], row);
1408 }
1409 }
1410
1411 // index the UpgradedImages table
1412 if (null != upgradedImagesTable)
1413 {
1414 foreach (Row row in upgradedImagesTable.Rows)
1415 {
1416 upgradedImagesRows.Add(row[0], row);
1417 }
1418 }
1419
1420 foreach (Row row in targetFiles_OptionalDataTable.Rows)
1421 {
1422 Wix.TargetFile targetFile = (Wix.TargetFile)this.patchTargetFiles[row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)];
1423
1424 Row targetImageRow = (Row)targetImageRows[row[0]];
1425 if (null == targetImageRow)
1426 {
1427 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, targetFiles_OptionalDataTable.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Target", Convert.ToString(row[0]), "TargetImages"));
1428 continue;
1429 }
1430
1431 Row upgradedImagesRow = (Row)upgradedImagesRows[targetImageRow[3]];
1432 if (null == upgradedImagesRow)
1433 {
1434 this.core.OnMessage(WixWarnings.ExpectedForeignRow(targetImageRow.SourceLineNumbers, targetImageRow.Table.Name, targetImageRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Upgraded", Convert.ToString(row[3]), "UpgradedImages"));
1435 continue;
1436 }
1437
1438 Wix.ProtectRange protectRange = (Wix.ProtectRange)this.core.GetIndexedElement("FamilyFileRanges", Convert.ToString(upgradedImagesRow[4]), Convert.ToString(row[1]));
1439 if (null != protectRange)
1440 {
1441 targetFile.AddChild(protectRange);
1442 usedProtectRanges[protectRange] = null;
1443 }
1444 }
1445 }
1446
1447 if (null != familyFileRangesTable)
1448 {
1449 foreach (Row row in familyFileRangesTable.Rows)
1450 {
1451 Wix.ProtectRange protectRange = (Wix.ProtectRange)this.core.GetIndexedElement(row);
1452
1453 if (!usedProtectRanges.Contains(protectRange))
1454 {
1455 Wix.ProtectFile protectFile = new Wix.ProtectFile();
1456
1457 protectFile.File = Convert.ToString(row[1]);
1458
1459 protectFile.AddChild(protectRange);
1460
1461 Wix.Family family = (Wix.Family)this.core.GetIndexedElement("ImageFamilies", Convert.ToString(row[0]));
1462 if (null != family)
1463 {
1464 family.AddChild(protectFile);
1465 }
1466 else
1467 {
1468 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, familyFileRangesTable.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Family", Convert.ToString(row[0]), "ImageFamilies"));
1469 }
1470 }
1471 }
1472 }
1473 }
1474
1475 /// <summary>
1476 /// Finalize the FeatureComponents table.
1477 /// </summary>
1478 /// <param name="tables">The collection of all tables.</param>
1479 /// <remarks>
1480 /// Since tables specifying references to the FeatureComponents table have references to
1481 /// the Feature and Component table separately, but not the FeatureComponents table specifically,
1482 /// the FeatureComponents table and primary features must be decompiled during finalization.
1483 /// </remarks>
1484 private void FinalizeFeatureComponentsTable(TableIndexedCollection tables)
1485 {
1486 Table classTable = tables["Class"];
1487 Table extensionTable = tables["Extension"];
1488 Table msiAssemblyTable = tables["MsiAssembly"];
1489 Table publishComponentTable = tables["PublishComponent"];
1490 Table shortcutTable = tables["Shortcut"];
1491 Table typeLibTable = tables["TypeLib"];
1492
1493 if (null != classTable)
1494 {
1495 foreach (Row row in classTable.Rows)
1496 {
1497 this.SetPrimaryFeature(row, 11, 2);
1498 }
1499 }
1500
1501 if (null != extensionTable)
1502 {
1503 foreach (Row row in extensionTable.Rows)
1504 {
1505 this.SetPrimaryFeature(row, 4, 1);
1506 }
1507 }
1508
1509 if (null != msiAssemblyTable)
1510 {
1511 foreach (Row row in msiAssemblyTable.Rows)
1512 {
1513 this.SetPrimaryFeature(row, 1, 0);
1514 }
1515 }
1516
1517 if (null != publishComponentTable)
1518 {
1519 foreach (Row row in publishComponentTable.Rows)
1520 {
1521 this.SetPrimaryFeature(row, 4, 2);
1522 }
1523 }
1524
1525 if (null != shortcutTable)
1526 {
1527 foreach (Row row in shortcutTable.Rows)
1528 {
1529 string target = Convert.ToString(row[4]);
1530
1531 if (!target.StartsWith("[", StringComparison.Ordinal) && !target.EndsWith("]", StringComparison.Ordinal))
1532 {
1533 this.SetPrimaryFeature(row, 4, 3);
1534 }
1535 }
1536 }
1537
1538 if (null != typeLibTable)
1539 {
1540 foreach (Row row in typeLibTable.Rows)
1541 {
1542 this.SetPrimaryFeature(row, 6, 2);
1543 }
1544 }
1545 }
1546
1547 /// <summary>
1548 /// Finalize the File table.
1549 /// </summary>
1550 /// <param name="tables">The collection of all tables.</param>
1551 /// <remarks>
1552 /// Sets the source, diskId, and assembly information for each file.
1553 /// </remarks>
1554 private void FinalizeFileTable(TableIndexedCollection tables)
1555 {
1556 Table fileTable = tables["File"];
1557 Table mediaTable = tables["Media"];
1558 Table msiAssemblyTable = tables["MsiAssembly"];
1559 Table typeLibTable = tables["TypeLib"];
1560
1561 // index the media table by media id
1562 RowDictionary<MediaRow> mediaRows;
1563 if (null != mediaTable)
1564 {
1565 mediaRows = new RowDictionary<MediaRow>(mediaTable);
1566 }
1567
1568 // set the disk identifiers and sources for files
1569 if (null != fileTable)
1570 {
1571 foreach (FileRow fileRow in fileTable.Rows)
1572 {
1573 Wix.File file = (Wix.File)this.core.GetIndexedElement("File", fileRow.File);
1574
1575 // Don't bother processing files that are orphaned (and won't show up in the output anyway)
1576 if (null != file.ParentElement)
1577 {
1578 // set the diskid
1579 if (null != mediaTable)
1580 {
1581 foreach (MediaRow mediaRow in mediaTable.Rows)
1582 {
1583 if (fileRow.Sequence <= mediaRow.LastSequence)
1584 {
1585 file.DiskId = Convert.ToString(mediaRow.DiskId);
1586 break;
1587 }
1588 }
1589 }
1590
1591 // set the source (done here because it requires information from the Directory table)
1592 if (OutputType.Module == this.outputType)
1593 {
1594 file.Source = String.Concat(this.exportFilePath, Path.DirectorySeparatorChar, "File", Path.DirectorySeparatorChar, file.Id, '.', this.modularizationGuid.Substring(1, 36).Replace('-', '_'));
1595 }
1596 else if (Wix.YesNoDefaultType.yes == file.Compressed || (Wix.YesNoDefaultType.no != file.Compressed && this.compressed))
1597 {
1598 file.Source = String.Concat(this.exportFilePath, Path.DirectorySeparatorChar, "File", Path.DirectorySeparatorChar, file.Id);
1599 }
1600 else // uncompressed
1601 {
1602 string fileName = (null != file.ShortName ? file.ShortName : file.Name);
1603
1604 if (!this.shortNames && null != file.Name)
1605 {
1606 fileName = file.Name;
1607 }
1608
1609 if (this.compressed) // uncompressed at the root of the source image
1610 {
1611 file.Source = String.Concat("SourceDir", Path.DirectorySeparatorChar, fileName);
1612 }
1613 else
1614 {
1615 string sourcePath = this.GetSourcePath(file);
1616
1617 file.Source = Path.Combine(sourcePath, fileName);
1618 }
1619 }
1620 }
1621 }
1622 }
1623
1624 // set the file assemblies and manifests
1625 if (null != msiAssemblyTable)
1626 {
1627 foreach (Row row in msiAssemblyTable.Rows)
1628 {
1629 Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[0]));
1630
1631 if (null == component)
1632 {
1633 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, "MsiAssembly", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[0]), "Component"));
1634 }
1635 else
1636 {
1637 foreach (Wix.ISchemaElement element in component.Children)
1638 {
1639 Wix.File file = element as Wix.File;
1640
1641 if (null != file && Wix.YesNoType.yes == file.KeyPath)
1642 {
1643 if (null != row[2])
1644 {
1645 file.AssemblyManifest = Convert.ToString(row[2]);
1646 }
1647
1648 if (null != row[3])
1649 {
1650 file.AssemblyApplication = Convert.ToString(row[3]);
1651 }
1652
1653 if (null == row[4] || 0 == Convert.ToInt32(row[4]))
1654 {
1655 file.Assembly = Wix.File.AssemblyType.net;
1656 }
1657 else
1658 {
1659 file.Assembly = Wix.File.AssemblyType.win32;
1660 }
1661 }
1662 }
1663 }
1664 }
1665 }
1666
1667 // nest the TypeLib elements
1668 if (null != typeLibTable)
1669 {
1670 foreach (Row row in typeLibTable.Rows)
1671 {
1672 Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[2]));
1673 Wix.TypeLib typeLib = (Wix.TypeLib)this.core.GetIndexedElement(row);
1674
1675 foreach (Wix.ISchemaElement element in component.Children)
1676 {
1677 Wix.File file = element as Wix.File;
1678
1679 if (null != file && Wix.YesNoType.yes == file.KeyPath)
1680 {
1681 file.AddChild(typeLib);
1682 }
1683 }
1684 }
1685 }
1686 }
1687
1688 /// <summary>
1689 /// Finalize the MIME table.
1690 /// </summary>
1691 /// <param name="tables">The collection of all tables.</param>
1692 /// <remarks>
1693 /// There is a foreign key shared between the MIME and Extension
1694 /// tables so either one would be valid to be decompiled first, so
1695 /// the only safe way to nest the MIME elements is to do it during finalize.
1696 /// </remarks>
1697 private void FinalizeMIMETable(TableIndexedCollection tables)
1698 {
1699 Table extensionTable = tables["Extension"];
1700 Table mimeTable = tables["MIME"];
1701
1702 Hashtable comExtensions = new Hashtable();
1703
1704 if (null != extensionTable)
1705 {
1706 foreach (Row row in extensionTable.Rows)
1707 {
1708 Wix.Extension extension = (Wix.Extension)this.core.GetIndexedElement(row);
1709
1710 // index the extension
1711 if (!comExtensions.Contains(row[0]))
1712 {
1713 comExtensions.Add(row[0], new ArrayList());
1714 }
1715 ((ArrayList)comExtensions[row[0]]).Add(extension);
1716
1717 // set the default MIME element for this extension
1718 if (null != row[3])
1719 {
1720 Wix.MIME mime = (Wix.MIME)this.core.GetIndexedElement("MIME", Convert.ToString(row[3]));
1721
1722 if (null != mime)
1723 {
1724 mime.Default = Wix.YesNoType.yes;
1725 }
1726 else
1727 {
1728 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, "Extension", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "MIME_", Convert.ToString(row[3]), "MIME"));
1729 }
1730 }
1731 }
1732 }
1733
1734 if (null != mimeTable)
1735 {
1736 foreach (Row row in mimeTable.Rows)
1737 {
1738 Wix.MIME mime = (Wix.MIME)this.core.GetIndexedElement(row);
1739
1740 if (comExtensions.Contains(row[1]))
1741 {
1742 ArrayList extensionElements = (ArrayList)comExtensions[row[1]];
1743
1744 foreach (Wix.Extension extension in extensionElements)
1745 {
1746 extension.AddChild(mime);
1747 }
1748 }
1749 else
1750 {
1751 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, "MIME", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Extension_", Convert.ToString(row[1]), "Extension"));
1752 }
1753 }
1754 }
1755 }
1756
1757 /// <summary>
1758 /// Finalize the ProgId table.
1759 /// </summary>
1760 /// <param name="tables">The collection of all tables.</param>
1761 /// <remarks>
1762 /// Enumerates through all the Class rows, looking for child ProgIds (these are the
1763 /// default ProgIds for a given Class). Then go through the ProgId table and add any
1764 /// remaining ProgIds for each Class. This happens during finalize because there is
1765 /// a circular dependency between the Class and ProgId tables.
1766 /// </remarks>
1767 private void FinalizeProgIdTable(TableIndexedCollection tables)
1768 {
1769 Table classTable = tables["Class"];
1770 Table progIdTable = tables["ProgId"];
1771 Table extensionTable = tables["Extension"];
1772 Table componentTable = tables["Component"];
1773
1774 Hashtable addedProgIds = new Hashtable();
1775 Hashtable classes = new Hashtable();
1776 Hashtable components = new Hashtable();
1777
1778 // add the default ProgIds for each class (and index the class table)
1779 if (null != classTable)
1780 {
1781 foreach (Row row in classTable.Rows)
1782 {
1783 Wix.Class wixClass = (Wix.Class)this.core.GetIndexedElement(row);
1784
1785 if (null != row[3])
1786 {
1787 Wix.ProgId progId = (Wix.ProgId)this.core.GetIndexedElement("ProgId", Convert.ToString(row[3]));
1788
1789 if (null != progId)
1790 {
1791 if (addedProgIds.Contains(progId))
1792 {
1793 this.core.OnMessage(WixWarnings.TooManyProgIds(row.SourceLineNumbers, Convert.ToString(row[0]), Convert.ToString(row[3]), Convert.ToString(addedProgIds[progId])));
1794 }
1795 else
1796 {
1797 wixClass.AddChild(progId);
1798 addedProgIds.Add(progId, wixClass.Id);
1799 }
1800 }
1801 else
1802 {
1803 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, "Class", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "ProgId_Default", Convert.ToString(row[3]), "ProgId"));
1804 }
1805 }
1806
1807 // index the Class elements for nesting of ProgId elements (which don't use the full Class primary key)
1808 if (!classes.Contains(wixClass.Id))
1809 {
1810 classes.Add(wixClass.Id, new ArrayList());
1811 }
1812 ((ArrayList)classes[wixClass.Id]).Add(wixClass);
1813 }
1814 }
1815
1816 // add the remaining non-default ProgId entries for each class
1817 if (null != progIdTable)
1818 {
1819 foreach (Row row in progIdTable.Rows)
1820 {
1821 Wix.ProgId progId = (Wix.ProgId)this.core.GetIndexedElement(row);
1822
1823 if (!addedProgIds.Contains(progId) && null != row[2] && null == progId.ParentElement)
1824 {
1825 ArrayList classElements = (ArrayList)classes[row[2]];
1826
1827 if (null != classElements)
1828 {
1829 foreach (Wix.Class wixClass in classElements)
1830 {
1831 wixClass.AddChild(progId);
1832 addedProgIds.Add(progId, wixClass.Id);
1833 }
1834 }
1835 else
1836 {
1837 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, "ProgId", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Class_", Convert.ToString(row[2]), "Class"));
1838 }
1839 }
1840 }
1841 }
1842
1843 if (null != componentTable)
1844 {
1845 foreach (Row row in componentTable.Rows)
1846 {
1847 Wix.Component wixComponent = (Wix.Component)this.core.GetIndexedElement(row);
1848
1849 // index the Class elements for nesting of ProgId elements (which don't use the full Class primary key)
1850 if (!components.Contains(wixComponent.Id))
1851 {
1852 components.Add(wixComponent.Id, new ArrayList());
1853 }
1854 ((ArrayList)components[wixComponent.Id]).Add(wixComponent);
1855 }
1856 }
1857
1858 // Check for any progIds that are not hooked up to a class and hook them up to the component specified by the extension
1859 if (null != extensionTable)
1860 {
1861 foreach (Row row in extensionTable.Rows)
1862 {
1863 // ignore the extension if it isn't associated with a progId
1864 if (null == row[2])
1865 {
1866 continue;
1867 }
1868
1869 Wix.ProgId progId = (Wix.ProgId)this.core.GetIndexedElement("ProgId", Convert.ToString(row[2]));
1870
1871 // Haven't added the progId yet and it doesn't have a parent progId
1872 if (!addedProgIds.Contains(progId) && null == progId.ParentElement)
1873 {
1874 ArrayList componentElements = (ArrayList)components[row[1]];
1875
1876 if (null != componentElements)
1877 {
1878 foreach (Wix.Component wixComponent in componentElements)
1879 {
1880 wixComponent.AddChild(progId);
1881 }
1882 }
1883 else
1884 {
1885 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, "Extension", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component"));
1886 }
1887 }
1888 }
1889 }
1890 }
1891
1892 /// <summary>
1893 /// Finalize the Property table.
1894 /// </summary>
1895 /// <param name="tables">The collection of all tables.</param>
1896 /// <remarks>
1897 /// Removes properties that are generated from other entries.
1898 /// </remarks>
1899 private void FinalizePropertyTable(TableIndexedCollection tables)
1900 {
1901 Table propertyTable = tables["Property"];
1902 Table customActionTable = tables["CustomAction"];
1903
1904 if (null != propertyTable && null != customActionTable)
1905 {
1906 foreach (Row row in customActionTable.Rows)
1907 {
1908 int bits = Convert.ToInt32(row[1]);
1909 if (MsiInterop.MsidbCustomActionTypeHideTarget == (bits & MsiInterop.MsidbCustomActionTypeHideTarget) &&
1910 MsiInterop.MsidbCustomActionTypeInScript == (bits & MsiInterop.MsidbCustomActionTypeInScript))
1911 {
1912 Wix.Property property = (Wix.Property)this.core.GetIndexedElement("Property", Convert.ToString(row[0]));
1913
1914 // If no other fields on the property are set we must have created it during link
1915 if (null != property && null == property.Value && Wix.YesNoType.yes != property.Secure && Wix.YesNoType.yes != property.SuppressModularization)
1916 {
1917 this.core.RootElement.RemoveChild(property);
1918 }
1919 }
1920 }
1921 }
1922 }
1923
1924 /// <summary>
1925 /// Finalize the RemoveFile table.
1926 /// </summary>
1927 /// <param name="tables">The collection of all tables.</param>
1928 /// <remarks>
1929 /// Sets the directory/property for each RemoveFile row.
1930 /// </remarks>
1931 private void FinalizeRemoveFileTable(TableIndexedCollection tables)
1932 {
1933 Table removeFileTable = tables["RemoveFile"];
1934
1935 if (null != removeFileTable)
1936 {
1937 foreach (Row row in removeFileTable.Rows)
1938 {
1939 bool isDirectory = false;
1940 string property = Convert.ToString(row[3]);
1941
1942 // determine if the property is actually authored as a directory
1943 if (null != this.core.GetIndexedElement("Directory", property))
1944 {
1945 isDirectory = true;
1946 }
1947
1948 Wix.ISchemaElement element = this.core.GetIndexedElement(row);
1949
1950 Wix.RemoveFile removeFile = element as Wix.RemoveFile;
1951 if (null != removeFile)
1952 {
1953 if (isDirectory)
1954 {
1955 removeFile.Directory = property;
1956 }
1957 else
1958 {
1959 removeFile.Property = property;
1960 }
1961 }
1962 else
1963 {
1964 Wix.RemoveFolder removeFolder = (Wix.RemoveFolder)element;
1965
1966 if (isDirectory)
1967 {
1968 removeFolder.Directory = property;
1969 }
1970 else
1971 {
1972 removeFolder.Property = property;
1973 }
1974 }
1975 }
1976 }
1977 }
1978
1979 /// <summary>
1980 /// Finalize the LockPermissions table.
1981 /// </summary>
1982 /// <param name="tables">The collection of all tables.</param>
1983 /// <remarks>
1984 /// Nests the Permission elements below their parent elements. There are no declared foreign
1985 /// keys for the parents of the LockPermissions table.
1986 /// </remarks>
1987 private void FinalizeLockPermissionsTable(TableIndexedCollection tables)
1988 {
1989 Table createFolderTable = tables["CreateFolder"];
1990 Table lockPermissionsTable = tables["LockPermissions"];
1991
1992 Hashtable createFolders = new Hashtable();
1993
1994 // index the CreateFolder table because the foreign key to this table from the
1995 // LockPermissions table is only part of the primary key of this table
1996 if (null != createFolderTable)
1997 {
1998 foreach (Row row in createFolderTable.Rows)
1999 {
2000 Wix.CreateFolder createFolder = (Wix.CreateFolder)this.core.GetIndexedElement(row);
2001 string directoryId = Convert.ToString(row[0]);
2002
2003 if (!createFolders.Contains(directoryId))
2004 {
2005 createFolders.Add(directoryId, new ArrayList());
2006 }
2007 ((ArrayList)createFolders[directoryId]).Add(createFolder);
2008 }
2009 }
2010
2011 if (null != lockPermissionsTable)
2012 {
2013 foreach (Row row in lockPermissionsTable.Rows)
2014 {
2015 string id = Convert.ToString(row[0]);
2016 string table = Convert.ToString(row[1]);
2017
2018 Wix.Permission permission = (Wix.Permission)this.core.GetIndexedElement(row);
2019
2020 if ("CreateFolder" == table)
2021 {
2022 ArrayList createFolderElements = (ArrayList)createFolders[id];
2023
2024 if (null != createFolderElements)
2025 {
2026 foreach (Wix.CreateFolder createFolder in createFolderElements)
2027 {
2028 createFolder.AddChild(permission);
2029 }
2030 }
2031 else
2032 {
2033 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, "LockPermissions", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "LockObject", id, table));
2034 }
2035 }
2036 else
2037 {
2038 Wix.IParentElement parentElement = (Wix.IParentElement)this.core.GetIndexedElement(table, id);
2039
2040 if (null != parentElement)
2041 {
2042 parentElement.AddChild(permission);
2043 }
2044 else
2045 {
2046 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, "LockPermissions", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "LockObject", id, table));
2047 }
2048 }
2049 }
2050 }
2051 }
2052
2053 /// <summary>
2054 /// Finalize the MsiLockPermissionsEx table.
2055 /// </summary>
2056 /// <param name="tables">The collection of all tables.</param>
2057 /// <remarks>
2058 /// Nests the PermissionEx elements below their parent elements. There are no declared foreign
2059 /// keys for the parents of the MsiLockPermissionsEx table.
2060 /// </remarks>
2061 private void FinalizeMsiLockPermissionsExTable(TableIndexedCollection tables)
2062 {
2063 Table createFolderTable = tables["CreateFolder"];
2064 Table msiLockPermissionsExTable = tables["MsiLockPermissionsEx"];
2065
2066 Hashtable createFolders = new Hashtable();
2067
2068 // index the CreateFolder table because the foreign key to this table from the
2069 // MsiLockPermissionsEx table is only part of the primary key of this table
2070 if (null != createFolderTable)
2071 {
2072 foreach (Row row in createFolderTable.Rows)
2073 {
2074 Wix.CreateFolder createFolder = (Wix.CreateFolder)this.core.GetIndexedElement(row);
2075 string directoryId = Convert.ToString(row[0]);
2076
2077 if (!createFolders.Contains(directoryId))
2078 {
2079 createFolders.Add(directoryId, new ArrayList());
2080 }
2081 ((ArrayList)createFolders[directoryId]).Add(createFolder);
2082 }
2083 }
2084
2085 if (null != msiLockPermissionsExTable)
2086 {
2087 foreach (Row row in msiLockPermissionsExTable.Rows)
2088 {
2089 string id = Convert.ToString(row[1]);
2090 string table = Convert.ToString(row[2]);
2091
2092 Wix.PermissionEx permissionEx = (Wix.PermissionEx)this.core.GetIndexedElement(row);
2093
2094 if ("CreateFolder" == table)
2095 {
2096 ArrayList createFolderElements = (ArrayList)createFolders[id];
2097
2098 if (null != createFolderElements)
2099 {
2100 foreach (Wix.CreateFolder createFolder in createFolderElements)
2101 {
2102 createFolder.AddChild(permissionEx);
2103 }
2104 }
2105 else
2106 {
2107 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, "MsiLockPermissionsEx", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "LockObject", id, table));
2108 }
2109 }
2110 else
2111 {
2112 Wix.IParentElement parentElement = (Wix.IParentElement)this.core.GetIndexedElement(table, id);
2113
2114 if (null != parentElement)
2115 {
2116 parentElement.AddChild(permissionEx);
2117 }
2118 else
2119 {
2120 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, "MsiLockPermissionsEx", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "LockObject", id, table));
2121 }
2122 }
2123 }
2124 }
2125 }
2126
2127 /// <summary>
2128 /// Finalize the search tables.
2129 /// </summary>
2130 /// <param name="tables">The collection of all tables.</param>
2131 /// <remarks>Does all the complex linking required for the search tables.</remarks>
2132 private void FinalizeSearchTables(TableIndexedCollection tables)
2133 {
2134 Table appSearchTable = tables["AppSearch"];
2135 Table ccpSearchTable = tables["CCPSearch"];
2136 Table drLocatorTable = tables["DrLocator"];
2137
2138 Hashtable appSearches = new Hashtable();
2139 Hashtable ccpSearches = new Hashtable();
2140 Hashtable drLocators = new Hashtable();
2141 Hashtable locators = new Hashtable();
2142 Hashtable usedSearchElements = new Hashtable();
2143 ArrayList unusedSearchElements = new ArrayList();
2144
2145 Wix.ComplianceCheck complianceCheck = null;
2146
2147 // index the AppSearch table by signatures
2148 if (null != appSearchTable)
2149 {
2150 foreach (Row row in appSearchTable.Rows)
2151 {
2152 string property = Convert.ToString(row[0]);
2153 string signature = Convert.ToString(row[1]);
2154
2155 if (!appSearches.Contains(signature))
2156 {
2157 appSearches.Add(signature, new StringCollection());
2158 }
2159
2160 ((StringCollection)appSearches[signature]).Add(property);
2161 }
2162 }
2163
2164 // index the CCPSearch table by signatures
2165 if (null != ccpSearchTable)
2166 {
2167 foreach (Row row in ccpSearchTable.Rows)
2168 {
2169 string signature = Convert.ToString(row[0]);
2170
2171 if (!ccpSearches.Contains(signature))
2172 {
2173 ccpSearches.Add(signature, new StringCollection());
2174 }
2175
2176 ((StringCollection)ccpSearches[signature]).Add(null);
2177
2178 if (null == complianceCheck && !appSearches.Contains(signature))
2179 {
2180 complianceCheck = new Wix.ComplianceCheck();
2181 this.core.RootElement.AddChild(complianceCheck);
2182 }
2183 }
2184 }
2185
2186 // index the directory searches by their search elements (to get back the original row)
2187 if (null != drLocatorTable)
2188 {
2189 foreach (Row row in drLocatorTable.Rows)
2190 {
2191 drLocators.Add(this.core.GetIndexedElement(row), row);
2192 }
2193 }
2194
2195 // index the locator tables by their signatures
2196 string[] locatorTableNames = new string[] { "CompLocator", "RegLocator", "IniLocator", "DrLocator", "Signature" };
2197 foreach (string locatorTableName in locatorTableNames)
2198 {
2199 Table locatorTable = tables[locatorTableName];
2200
2201 if (null != locatorTable)
2202 {
2203 foreach (Row row in locatorTable.Rows)
2204 {
2205 string signature = Convert.ToString(row[0]);
2206
2207 if (!locators.Contains(signature))
2208 {
2209 locators.Add(signature, new ArrayList());
2210 }
2211
2212 ((ArrayList)locators[signature]).Add(row);
2213 }
2214 }
2215 }
2216
2217 // move the DrLocator rows with a parent of CCP_DRIVE first to ensure they get FileSearch children (not FileSearchRef)
2218 foreach (ArrayList locatorRows in locators.Values)
2219 {
2220 int firstDrLocator = -1;
2221
2222 for (int i = 0; i < locatorRows.Count; i++)
2223 {
2224 Row locatorRow = (Row)locatorRows[i];
2225
2226 if ("DrLocator" == locatorRow.TableDefinition.Name)
2227 {
2228 if (-1 == firstDrLocator)
2229 {
2230 firstDrLocator = i;
2231 }
2232
2233 if ("CCP_DRIVE" == Convert.ToString(locatorRow[1]))
2234 {
2235 locatorRows.RemoveAt(i);
2236 locatorRows.Insert(firstDrLocator, locatorRow);
2237 break;
2238 }
2239 }
2240 }
2241 }
2242
2243 foreach (string signature in locators.Keys)
2244 {
2245 ArrayList locatorRows = (ArrayList)locators[signature];
2246 ArrayList signatureSearchElements = new ArrayList();
2247
2248 foreach (Row locatorRow in locatorRows)
2249 {
2250 bool used = true;
2251 Wix.ISchemaElement searchElement = this.core.GetIndexedElement(locatorRow);
2252
2253 if ("Signature" == locatorRow.TableDefinition.Name && 0 < signatureSearchElements.Count)
2254 {
2255 foreach (Wix.IParentElement searchParentElement in signatureSearchElements)
2256 {
2257 if (!usedSearchElements.Contains(searchElement))
2258 {
2259 searchParentElement.AddChild(searchElement);
2260 usedSearchElements[searchElement] = null;
2261 }
2262 else
2263 {
2264 Wix.FileSearchRef fileSearchRef = new Wix.FileSearchRef();
2265
2266 fileSearchRef.Id = signature;
2267
2268 searchParentElement.AddChild(fileSearchRef);
2269 }
2270 }
2271 }
2272 else if ("DrLocator" == locatorRow.TableDefinition.Name && null != locatorRow[1])
2273 {
2274 string parentSignature = Convert.ToString(locatorRow[1]);
2275
2276 if ("CCP_DRIVE" == parentSignature)
2277 {
2278 if (appSearches.Contains(signature))
2279 {
2280 StringCollection appSearchPropertyIds = (StringCollection)appSearches[signature];
2281
2282 foreach (string propertyId in appSearchPropertyIds)
2283 {
2284 Wix.Property property = this.EnsureProperty(propertyId);
2285 Wix.ComplianceDrive complianceDrive = null;
2286
2287 if (ccpSearches.Contains(signature))
2288 {
2289 property.ComplianceCheck = Wix.YesNoType.yes;
2290 }
2291
2292 foreach (Wix.ISchemaElement element in property.Children)
2293 {
2294 complianceDrive = element as Wix.ComplianceDrive;
2295 if (null != complianceDrive)
2296 {
2297 break;
2298 }
2299 }
2300
2301 if (null == complianceDrive)
2302 {
2303 complianceDrive = new Wix.ComplianceDrive();
2304 property.AddChild(complianceDrive);
2305 }
2306
2307 if (!usedSearchElements.Contains(searchElement))
2308 {
2309 complianceDrive.AddChild(searchElement);
2310 usedSearchElements[searchElement] = null;
2311 }
2312 else
2313 {
2314 Wix.DirectorySearchRef directorySearchRef = new Wix.DirectorySearchRef();
2315
2316 directorySearchRef.Id = signature;
2317
2318 if (null != locatorRow[1])
2319 {
2320 directorySearchRef.Parent = Convert.ToString(locatorRow[1]);
2321 }
2322
2323 if (null != locatorRow[2])
2324 {
2325 directorySearchRef.Path = Convert.ToString(locatorRow[2]);
2326 }
2327
2328 complianceDrive.AddChild(directorySearchRef);
2329 signatureSearchElements.Add(directorySearchRef);
2330 }
2331 }
2332 }
2333 else if (ccpSearches.Contains(signature))
2334 {
2335 Wix.ComplianceDrive complianceDrive = null;
2336
2337 foreach (Wix.ISchemaElement element in complianceCheck.Children)
2338 {
2339 complianceDrive = element as Wix.ComplianceDrive;
2340 if (null != complianceDrive)
2341 {
2342 break;
2343 }
2344 }
2345
2346 if (null == complianceDrive)
2347 {
2348 complianceDrive = new Wix.ComplianceDrive();
2349 complianceCheck.AddChild(complianceDrive);
2350 }
2351
2352 if (!usedSearchElements.Contains(searchElement))
2353 {
2354 complianceDrive.AddChild(searchElement);
2355 usedSearchElements[searchElement] = null;
2356 }
2357 else
2358 {
2359 Wix.DirectorySearchRef directorySearchRef = new Wix.DirectorySearchRef();
2360
2361 directorySearchRef.Id = signature;
2362
2363 if (null != locatorRow[1])
2364 {
2365 directorySearchRef.Parent = Convert.ToString(locatorRow[1]);
2366 }
2367
2368 if (null != locatorRow[2])
2369 {
2370 directorySearchRef.Path = Convert.ToString(locatorRow[2]);
2371 }
2372
2373 complianceDrive.AddChild(directorySearchRef);
2374 signatureSearchElements.Add(directorySearchRef);
2375 }
2376 }
2377 }
2378 else
2379 {
2380 bool usedDrLocator = false;
2381 ArrayList parentLocatorRows = (ArrayList)locators[parentSignature];
2382
2383 if (null != parentLocatorRows)
2384 {
2385 foreach (Row parentLocatorRow in parentLocatorRows)
2386 {
2387 if ("DrLocator" == parentLocatorRow.TableDefinition.Name)
2388 {
2389 Wix.IParentElement parentSearchElement = (Wix.IParentElement)this.core.GetIndexedElement(parentLocatorRow);
2390
2391 if (parentSearchElement.Children.GetEnumerator().MoveNext())
2392 {
2393 Row parentDrLocatorRow = (Row)drLocators[parentSearchElement];
2394 Wix.DirectorySearchRef directorySeachRef = new Wix.DirectorySearchRef();
2395
2396 directorySeachRef.Id = parentSignature;
2397
2398 if (null != parentDrLocatorRow[1])
2399 {
2400 directorySeachRef.Parent = Convert.ToString(parentDrLocatorRow[1]);
2401 }
2402
2403 if (null != parentDrLocatorRow[2])
2404 {
2405 directorySeachRef.Path = Convert.ToString(parentDrLocatorRow[2]);
2406 }
2407
2408 parentSearchElement = directorySeachRef;
2409 unusedSearchElements.Add(directorySeachRef);
2410 }
2411
2412 if (!usedSearchElements.Contains(searchElement))
2413 {
2414 parentSearchElement.AddChild(searchElement);
2415 usedSearchElements[searchElement] = null;
2416 usedDrLocator = true;
2417 }
2418 else
2419 {
2420 Wix.DirectorySearchRef directorySearchRef = new Wix.DirectorySearchRef();
2421
2422 directorySearchRef.Id = signature;
2423
2424 directorySearchRef.Parent = parentSignature;
2425
2426 if (null != locatorRow[2])
2427 {
2428 directorySearchRef.Path = Convert.ToString(locatorRow[2]);
2429 }
2430
2431 parentSearchElement.AddChild(searchElement);
2432 usedDrLocator = true;
2433 }
2434 }
2435 }
2436
2437 // keep track of unused DrLocator rows
2438 if (!usedDrLocator)
2439 {
2440 unusedSearchElements.Add(searchElement);
2441 }
2442 }
2443 else
2444 {
2445 // TODO: warn
2446 }
2447 }
2448 }
2449 else if (appSearches.Contains(signature))
2450 {
2451 StringCollection appSearchPropertyIds = (StringCollection)appSearches[signature];
2452
2453 foreach (string propertyId in appSearchPropertyIds)
2454 {
2455 Wix.Property property = this.EnsureProperty(propertyId);
2456
2457 if (ccpSearches.Contains(signature))
2458 {
2459 property.ComplianceCheck = Wix.YesNoType.yes;
2460 }
2461
2462 if (!usedSearchElements.Contains(searchElement))
2463 {
2464 property.AddChild(searchElement);
2465 usedSearchElements[searchElement] = null;
2466 }
2467 else if ("RegLocator" == locatorRow.TableDefinition.Name)
2468 {
2469 Wix.RegistrySearchRef registrySearchRef = new Wix.RegistrySearchRef();
2470
2471 registrySearchRef.Id = signature;
2472
2473 property.AddChild(registrySearchRef);
2474 signatureSearchElements.Add(registrySearchRef);
2475 }
2476 else
2477 {
2478 // TODO: warn about unavailable Ref element
2479 }
2480 }
2481 }
2482 else if (ccpSearches.Contains(signature))
2483 {
2484 if (!usedSearchElements.Contains(searchElement))
2485 {
2486 complianceCheck.AddChild(searchElement);
2487 usedSearchElements[searchElement] = null;
2488 }
2489 else if ("RegLocator" == locatorRow.TableDefinition.Name)
2490 {
2491 Wix.RegistrySearchRef registrySearchRef = new Wix.RegistrySearchRef();
2492
2493 registrySearchRef.Id = signature;
2494
2495 complianceCheck.AddChild(registrySearchRef);
2496 signatureSearchElements.Add(registrySearchRef);
2497 }
2498 else
2499 {
2500 // TODO: warn about unavailable Ref element
2501 }
2502 }
2503 else
2504 {
2505 if ("DrLocator" == locatorRow.TableDefinition.Name)
2506 {
2507 unusedSearchElements.Add(searchElement);
2508 }
2509 else
2510 {
2511 // TODO: warn
2512 used = false;
2513 }
2514 }
2515
2516 // keep track of the search elements for this signature so that nested searches go in the proper parents
2517 if (used)
2518 {
2519 signatureSearchElements.Add(searchElement);
2520 }
2521 }
2522 }
2523
2524 foreach (Wix.IParentElement unusedSearchElement in unusedSearchElements)
2525 {
2526 bool used = false;
2527
2528 foreach (Wix.ISchemaElement schemaElement in unusedSearchElement.Children)
2529 {
2530 Wix.DirectorySearch directorySearch = schemaElement as Wix.DirectorySearch;
2531 if (null != directorySearch)
2532 {
2533 StringCollection appSearchProperties = (StringCollection)appSearches[directorySearch.Id];
2534
2535 Wix.ISchemaElement unusedSearchSchemaElement = unusedSearchElement as Wix.ISchemaElement;
2536 if (null != appSearchProperties)
2537 {
2538 Wix.Property property = this.EnsureProperty(appSearchProperties[0]);
2539
2540 property.AddChild(unusedSearchSchemaElement);
2541 used = true;
2542 break;
2543 }
2544 else if (ccpSearches.Contains(directorySearch.Id))
2545 {
2546 complianceCheck.AddChild(unusedSearchSchemaElement);
2547 used = true;
2548 break;
2549 }
2550 else
2551 {
2552 // TODO: warn
2553 }
2554 }
2555 }
2556
2557 if (!used)
2558 {
2559 // TODO: warn
2560 }
2561 }
2562 }
2563
2564 /// <summary>
2565 /// Finalize the sequence tables.
2566 /// </summary>
2567 /// <param name="tables">The collection of all tables.</param>
2568 /// <remarks>
2569 /// Creates the sequence elements. Occurs during finalization because its
2570 /// not known if sequences refer to custom actions or dialogs during decompilation.
2571 /// </remarks>
2572 private void FinalizeSequenceTables(TableIndexedCollection tables)
2573 {
2574 // finalize the normal sequence tables
2575 if (OutputType.Product == this.outputType && !this.treatProductAsModule)
2576 {
2577 foreach (SequenceTable sequenceTable in Enum.GetValues(typeof(SequenceTable)))
2578 {
2579 // if suppressing UI elements, skip UI-related sequence tables
2580 if (this.suppressUI && ("AdminUISequence" == sequenceTable.ToString() || "InstallUISequence" == sequenceTable.ToString()))
2581 {
2582 continue;
2583 }
2584
2585 Table actionsTable = new Table(null, this.tableDefinitions["WixAction"]);
2586 Table table = tables[sequenceTable.ToString()];
2587
2588 if (null != table)
2589 {
2590 ArrayList actionRows = new ArrayList();
2591 bool needAbsoluteScheduling = this.suppressRelativeActionSequencing;
2592 WixActionRowCollection nonSequencedActionRows = new WixActionRowCollection();
2593 WixActionRowCollection suppressedRelativeActionRows = new WixActionRowCollection();
2594
2595 // create a sorted array of actions in this table
2596 foreach (Row row in table.Rows)
2597 {
2598 WixActionRow actionRow = (WixActionRow)actionsTable.CreateRow(null);
2599
2600 actionRow.Action = Convert.ToString(row[0]);
2601
2602 if (null != row[1])
2603 {
2604 actionRow.Condition = Convert.ToString(row[1]);
2605 }
2606
2607 actionRow.Sequence = Convert.ToInt32(row[2]);
2608
2609 actionRow.SequenceTable = sequenceTable;
2610
2611 actionRows.Add(actionRow);
2612 }
2613 actionRows.Sort();
2614
2615 for (int i = 0; i < actionRows.Count && !needAbsoluteScheduling; i++)
2616 {
2617 WixActionRow actionRow = (WixActionRow)actionRows[i];
2618 WixActionRow standardActionRow = this.standardActions[actionRow.SequenceTable, actionRow.Action];
2619
2620 // create actions for custom actions, dialogs, AppSearch when its moved, and standard actions with non-standard conditions
2621 if ("AppSearch" == actionRow.Action || null == standardActionRow || actionRow.Condition != standardActionRow.Condition)
2622 {
2623 WixActionRow previousActionRow = null;
2624 WixActionRow nextActionRow = null;
2625
2626 // find the previous action row if there is one
2627 if (0 <= i - 1)
2628 {
2629 previousActionRow = (WixActionRow)actionRows[i - 1];
2630 }
2631
2632 // find the next action row if there is one
2633 if (actionRows.Count > i + 1)
2634 {
2635 nextActionRow = (WixActionRow)actionRows[i + 1];
2636 }
2637
2638 // the logic for setting the before or after attribute for an action:
2639 // 1. If more than one action shares the same sequence number, everything must be absolutely sequenced.
2640 // 2. If the next action is a standard action and is 1 sequence number higher, this action occurs before it.
2641 // 3. If the previous action is a standard action and is 1 sequence number lower, this action occurs after it.
2642 // 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.
2643 // 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.
2644 // 6. If this action is AppSearch and has all standard information, ignore it.
2645 // 7. If this action is standard and has a non-standard condition, create the action without any scheduling information.
2646 // 8. Everything must be absolutely sequenced.
2647 if ((null != previousActionRow && actionRow.Sequence == previousActionRow.Sequence) || (null != nextActionRow && actionRow.Sequence == nextActionRow.Sequence))
2648 {
2649 needAbsoluteScheduling = true;
2650 }
2651 else if (null != nextActionRow && null != this.standardActions[sequenceTable, nextActionRow.Action] && actionRow.Sequence + 1 == nextActionRow.Sequence)
2652 {
2653 actionRow.Before = nextActionRow.Action;
2654 }
2655 else if (null != previousActionRow && null != this.standardActions[sequenceTable, previousActionRow.Action] && actionRow.Sequence - 1 == previousActionRow.Sequence)
2656 {
2657 actionRow.After = previousActionRow.Action;
2658 }
2659 else if (null == standardActionRow && null != previousActionRow && actionRow.Sequence - 1 == previousActionRow.Sequence && previousActionRow.Before != actionRow.Action)
2660 {
2661 actionRow.After = previousActionRow.Action;
2662 }
2663 else if (null == standardActionRow && null != previousActionRow && actionRow.Sequence != previousActionRow.Sequence && null != nextActionRow && actionRow.Sequence + 1 == nextActionRow.Sequence)
2664 {
2665 actionRow.Before = nextActionRow.Action;
2666 }
2667 else if ("AppSearch" == actionRow.Action && null != standardActionRow && actionRow.Sequence == standardActionRow.Sequence && actionRow.Condition == standardActionRow.Condition)
2668 {
2669 // ignore an AppSearch row which has the WiX standard sequence and a standard condition
2670 }
2671 else if (null != standardActionRow && actionRow.Condition != standardActionRow.Condition) // standard actions get their standard sequence numbers
2672 {
2673 nonSequencedActionRows.Add(actionRow);
2674 }
2675 else if (0 < actionRow.Sequence)
2676 {
2677 needAbsoluteScheduling = true;
2678 }
2679 }
2680 else
2681 {
2682 suppressedRelativeActionRows.Add(actionRow);
2683 }
2684 }
2685
2686 // create the actions now that we know if they must be absolutely or relatively scheduled
2687 foreach (WixActionRow actionRow in actionRows)
2688 {
2689 if (needAbsoluteScheduling)
2690 {
2691 // remove any before/after information to ensure this is absolutely sequenced
2692 actionRow.Before = null;
2693 actionRow.After = null;
2694 }
2695 else if (nonSequencedActionRows.Contains(actionRow.SequenceTable, actionRow.Action))
2696 {
2697 // clear the sequence attribute to ensure this action is scheduled without a sequence number (or before/after)
2698 actionRow.Sequence = 0;
2699 }
2700 else if (suppressedRelativeActionRows.Contains(actionRow.SequenceTable, actionRow.Action))
2701 {
2702 // skip the suppressed relatively scheduled action rows
2703 continue;
2704 }
2705
2706 // create the action element
2707 this.CreateActionElement(actionRow);
2708 }
2709 }
2710 }
2711 }
2712 else if (OutputType.Module == this.outputType || this.treatProductAsModule) // finalize the Module sequence tables
2713 {
2714 foreach (SequenceTable sequenceTable in Enum.GetValues(typeof(SequenceTable)))
2715 {
2716 // if suppressing UI elements, skip UI-related sequence tables
2717 if (this.suppressUI && ("AdminUISequence" == sequenceTable.ToString() || "InstallUISequence" == sequenceTable.ToString()))
2718 {
2719 continue;
2720 }
2721
2722 Table actionsTable = new Table(null, this.tableDefinitions["WixAction"]);
2723 Table table = tables[String.Concat("Module", sequenceTable.ToString())];
2724
2725 if (null != table)
2726 {
2727 foreach (Row row in table.Rows)
2728 {
2729 WixActionRow actionRow = (WixActionRow)actionsTable.CreateRow(null);
2730
2731 actionRow.Action = Convert.ToString(row[0]);
2732
2733 if (null != row[1])
2734 {
2735 actionRow.Sequence = Convert.ToInt32(row[1]);
2736 }
2737
2738 if (null != row[2] && null != row[3])
2739 {
2740 switch (Convert.ToInt32(row[3]))
2741 {
2742 case 0:
2743 actionRow.Before = Convert.ToString(row[2]);
2744 break;
2745 case 1:
2746 actionRow.After = Convert.ToString(row[2]);
2747 break;
2748 default:
2749 this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[3].Column.Name, row[3]));
2750 break;
2751 }
2752 }
2753
2754 if (null != row[4])
2755 {
2756 actionRow.Condition = Convert.ToString(row[4]);
2757 }
2758
2759 actionRow.SequenceTable = sequenceTable;
2760
2761 // create action elements for non-standard actions
2762 if (null == this.standardActions[actionRow.SequenceTable, actionRow.Action] || null != actionRow.After || null != actionRow.Before)
2763 {
2764 this.CreateActionElement(actionRow);
2765 }
2766 }
2767 }
2768 }
2769 }
2770 }
2771
2772 /// <summary>
2773 /// Finalize the Upgrade table.
2774 /// </summary>
2775 /// <param name="tables">The collection of all tables.</param>
2776 /// <remarks>
2777 /// Decompile the rows from the Upgrade and LaunchCondition tables
2778 /// created by the MajorUpgrade element.
2779 /// </remarks>
2780 private void FinalizeUpgradeTable(TableIndexedCollection tables)
2781 {
2782 Table launchConditionTable = tables["LaunchCondition"];
2783 Table upgradeTable = tables["Upgrade"];
2784 string downgradeErrorMessage = null;
2785 string disallowUpgradeErrorMessage = null;
2786 Wix.MajorUpgrade majorUpgrade = new Wix.MajorUpgrade();
2787
2788 // find the DowngradePreventedCondition launch condition message
2789 if (null != launchConditionTable && 0 < launchConditionTable.Rows.Count)
2790 {
2791 foreach (Row launchRow in launchConditionTable.Rows)
2792 {
2793 if (Compiler.DowngradePreventedCondition == Convert.ToString(launchRow[0]))
2794 {
2795 downgradeErrorMessage = Convert.ToString(launchRow[1]);
2796 }
2797 else if (Compiler.UpgradePreventedCondition == Convert.ToString(launchRow[0]))
2798 {
2799 disallowUpgradeErrorMessage = Convert.ToString(launchRow[1]);
2800 }
2801 }
2802 }
2803
2804 if (null != upgradeTable && 0 < upgradeTable.Rows.Count)
2805 {
2806 bool hasMajorUpgrade = false;
2807
2808 foreach (Row row in upgradeTable.Rows)
2809 {
2810 UpgradeRow upgradeRow = (UpgradeRow)row;
2811
2812 if (Compiler.UpgradeDetectedProperty == upgradeRow.ActionProperty)
2813 {
2814 hasMajorUpgrade = true;
2815 int attr = upgradeRow.Attributes;
2816 string removeFeatures = upgradeRow.Remove;
2817
2818 if (MsiInterop.MsidbUpgradeAttributesVersionMaxInclusive == (attr & MsiInterop.MsidbUpgradeAttributesVersionMaxInclusive))
2819 {
2820 majorUpgrade.AllowSameVersionUpgrades = Wix.YesNoType.yes;
2821 }
2822
2823 if (MsiInterop.MsidbUpgradeAttributesMigrateFeatures != (attr & MsiInterop.MsidbUpgradeAttributesMigrateFeatures))
2824 {
2825 majorUpgrade.MigrateFeatures = Wix.YesNoType.no;
2826 }
2827
2828 if (MsiInterop.MsidbUpgradeAttributesIgnoreRemoveFailure == (attr & MsiInterop.MsidbUpgradeAttributesIgnoreRemoveFailure))
2829 {
2830 majorUpgrade.IgnoreRemoveFailure = Wix.YesNoType.yes;
2831 }
2832
2833 if (!String.IsNullOrEmpty(removeFeatures))
2834 {
2835 majorUpgrade.RemoveFeatures = removeFeatures;
2836 }
2837 }
2838 else if (Compiler.DowngradeDetectedProperty == upgradeRow.ActionProperty)
2839 {
2840 hasMajorUpgrade = true;
2841 majorUpgrade.DowngradeErrorMessage = downgradeErrorMessage;
2842 }
2843 }
2844
2845 if (hasMajorUpgrade)
2846 {
2847 if (String.IsNullOrEmpty(downgradeErrorMessage))
2848 {
2849 majorUpgrade.AllowDowngrades = Wix.YesNoType.yes;
2850 }
2851
2852 if (!String.IsNullOrEmpty(disallowUpgradeErrorMessage))
2853 {
2854 majorUpgrade.Disallow = Wix.YesNoType.yes;
2855 majorUpgrade.DisallowUpgradeErrorMessage = disallowUpgradeErrorMessage;
2856 }
2857
2858 majorUpgrade.Schedule = DetermineMajorUpgradeScheduling(tables);
2859 this.core.RootElement.AddChild(majorUpgrade);
2860 }
2861 }
2862 }
2863
2864 /// <summary>
2865 /// Finalize the Verb table.
2866 /// </summary>
2867 /// <param name="tables">The collection of all tables.</param>
2868 /// <remarks>
2869 /// The Extension table is a foreign table for the Verb table, but the
2870 /// foreign key is only part of the primary key of the Extension table,
2871 /// so it needs special logic to be nested properly.
2872 /// </remarks>
2873 private void FinalizeVerbTable(TableIndexedCollection tables)
2874 {
2875 Table extensionTable = tables["Extension"];
2876 Table verbTable = tables["Verb"];
2877
2878 Hashtable extensionElements = new Hashtable();
2879
2880 if (null != extensionTable)
2881 {
2882 foreach (Row row in extensionTable.Rows)
2883 {
2884 Wix.Extension extension = (Wix.Extension)this.core.GetIndexedElement(row);
2885
2886 if (!extensionElements.Contains(row[0]))
2887 {
2888 extensionElements.Add(row[0], new ArrayList());
2889 }
2890
2891 ((ArrayList)extensionElements[row[0]]).Add(extension);
2892 }
2893 }
2894
2895 if (null != verbTable)
2896 {
2897 foreach (Row row in verbTable.Rows)
2898 {
2899 Wix.Verb verb = (Wix.Verb)this.core.GetIndexedElement(row);
2900
2901 ArrayList extensionsArray = (ArrayList)extensionElements[row[0]];
2902 if (null != extensionsArray)
2903 {
2904 foreach (Wix.Extension extension in extensionsArray)
2905 {
2906 extension.AddChild(verb);
2907 }
2908 }
2909 else
2910 {
2911 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, verbTable.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Extension_", Convert.ToString(row[0]), "Extension"));
2912 }
2913 }
2914 }
2915 }
2916
2917 /// <summary>
2918 /// Get the path to a file in the source image.
2919 /// </summary>
2920 /// <param name="file">The file.</param>
2921 /// <returns>The path to the file in the source image.</returns>
2922 private string GetSourcePath(Wix.File file)
2923 {
2924 StringBuilder sourcePath = new StringBuilder();
2925
2926 Wix.Component component = (Wix.Component)file.ParentElement;
2927
2928 for (Wix.Directory directory = (Wix.Directory)component.ParentElement; null != directory; directory = directory.ParentElement as Wix.Directory)
2929 {
2930 string name;
2931
2932 if (!this.shortNames && null != directory.SourceName)
2933 {
2934 name = directory.SourceName;
2935 }
2936 else if (null != directory.ShortSourceName)
2937 {
2938 name = directory.ShortSourceName;
2939 }
2940 else if (!this.shortNames || null == directory.ShortName)
2941 {
2942 name = directory.Name;
2943 }
2944 else
2945 {
2946 name = directory.ShortName;
2947 }
2948
2949 if (0 == sourcePath.Length)
2950 {
2951 sourcePath.Append(name);
2952 }
2953 else
2954 {
2955 sourcePath.Insert(0, Path.DirectorySeparatorChar);
2956 sourcePath.Insert(0, name);
2957 }
2958 }
2959
2960 return sourcePath.ToString();
2961 }
2962
2963 /// <summary>
2964 /// Resolve the dependencies for a table (this is a helper method for GetSortedTableNames).
2965 /// </summary>
2966 /// <param name="tableName">The name of the table to resolve.</param>
2967 /// <param name="unsortedTableNames">The unsorted table names.</param>
2968 /// <param name="sortedTableNames">The sorted table names.</param>
2969 private void ResolveTableDependencies(string tableName, SortedList unsortedTableNames, StringCollection sortedTableNames)
2970 {
2971 unsortedTableNames.Remove(tableName);
2972
2973 foreach (ColumnDefinition columnDefinition in this.tableDefinitions[tableName].Columns)
2974 {
2975 // no dependency to resolve because this column doesn't reference another table
2976 if (null == columnDefinition.KeyTable)
2977 {
2978 continue;
2979 }
2980
2981 foreach (string keyTable in columnDefinition.KeyTable.Split(';'))
2982 {
2983 if (tableName == keyTable)
2984 {
2985 continue; // self-referencing dependency
2986 }
2987 else if (sortedTableNames.Contains(keyTable))
2988 {
2989 continue; // dependent table has already been sorted
2990 }
2991 else if (!this.tableDefinitions.Contains(keyTable))
2992 {
2993 this.core.OnMessage(WixErrors.MissingTableDefinition(keyTable));
2994 }
2995 else if (unsortedTableNames.Contains(keyTable))
2996 {
2997 this.ResolveTableDependencies(keyTable, unsortedTableNames, sortedTableNames);
2998 }
2999 else
3000 {
3001 // found a circular dependency, so ignore it (this assumes that the tables will
3002 // use a finalize method to nest their elements since the ordering will not be
3003 // deterministic
3004 }
3005 }
3006 }
3007
3008 sortedTableNames.Add(tableName);
3009 }
3010
3011 /// <summary>
3012 /// Get the names of the tables to process in the order they should be processed, according to their dependencies.
3013 /// </summary>
3014 /// <returns>A StringCollection containing the ordered table names.</returns>
3015 private StringCollection GetSortedTableNames()
3016 {
3017 StringCollection sortedTableNames = new StringCollection();
3018 SortedList unsortedTableNames = new SortedList();
3019
3020 // index the table names
3021 foreach (TableDefinition tableDefinition in this.tableDefinitions)
3022 {
3023 unsortedTableNames.Add(tableDefinition.Name, tableDefinition.Name);
3024 }
3025
3026 // resolve the dependencies for each table
3027 while (0 < unsortedTableNames.Count)
3028 {
3029 this.ResolveTableDependencies(Convert.ToString(unsortedTableNames.GetByIndex(0)), unsortedTableNames, sortedTableNames);
3030 }
3031
3032 return sortedTableNames;
3033 }
3034
3035 /// <summary>
3036 /// Initialize decompilation.
3037 /// </summary>
3038 /// <param name="tables">The collection of all tables.</param>
3039 private void InitializeDecompile(TableIndexedCollection tables)
3040 {
3041 // reset all the state information
3042 this.compressed = false;
3043 this.patchTargetFiles.Clear();
3044 this.sequenceElements.Clear();
3045 this.shortNames = false;
3046
3047 // set the codepage if its not neutral (0)
3048 if (0 != this.codepage)
3049 {
3050 switch (this.outputType)
3051 {
3052 case OutputType.Module:
3053 ((Wix.Module)this.core.RootElement).Codepage = this.codepage.ToString(CultureInfo.InvariantCulture);
3054 break;
3055 case OutputType.PatchCreation:
3056 ((Wix.PatchCreation)this.core.RootElement).Codepage = this.codepage.ToString(CultureInfo.InvariantCulture);
3057 break;
3058 case OutputType.Product:
3059 ((Wix.Product)this.core.RootElement).Codepage = this.codepage.ToString(CultureInfo.InvariantCulture);
3060 break;
3061 }
3062 }
3063
3064 // index the rows from the extension libraries
3065 Dictionary<string, HashSet<string>> indexedExtensionTables = new Dictionary<string, HashSet<string>>();
3066 foreach (IDecompilerExtension extension in this.extensions)
3067 {
3068 // Get the optional library from the extension with the rows to be removed.
3069 Library library = extension.GetLibraryToRemove(this.tableDefinitions);
3070 if (null != library)
3071 {
3072 foreach (Section section in library.Sections)
3073 {
3074 foreach (Table table in section.Tables)
3075 {
3076 foreach (Row row in table.Rows)
3077 {
3078 string primaryKey;
3079 string tableName;
3080
3081 // the Actions table needs to be handled specially
3082 if ("WixAction" == table.Name)
3083 {
3084 primaryKey = Convert.ToString(row[1]);
3085
3086 if (OutputType.Module == this.outputType)
3087 {
3088 tableName = String.Concat("Module", Convert.ToString(row[0]));
3089 }
3090 else
3091 {
3092 tableName = Convert.ToString(row[0]);
3093 }
3094 }
3095 else
3096 {
3097 primaryKey = row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter);
3098 tableName = table.Name;
3099 }
3100
3101 if (null != primaryKey)
3102 {
3103 HashSet<string> indexedExtensionRows;
3104 if (!indexedExtensionTables.TryGetValue(tableName, out indexedExtensionRows))
3105 {
3106 indexedExtensionRows = new HashSet<string>();
3107 indexedExtensionTables.Add(tableName, indexedExtensionRows);
3108 }
3109
3110 indexedExtensionRows.Add(primaryKey);
3111 }
3112 }
3113 }
3114 }
3115 }
3116 }
3117
3118 // remove the rows from the extension libraries (to allow full round-tripping)
3119 foreach (var kvp in indexedExtensionTables)
3120 {
3121 string tableName = kvp.Key;
3122 HashSet<string> indexedExtensionRows = kvp.Value;
3123
3124 Table table = tables[tableName];
3125 if (null != table)
3126 {
3127 RowDictionary<Row> originalRows = new RowDictionary<Row>(table);
3128
3129 // remove the original rows so that they can be added back if they should remain
3130 table.Rows.Clear();
3131
3132 foreach (Row row in originalRows.Values)
3133 {
3134 if (!indexedExtensionRows.Contains(row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)))
3135 {
3136 table.Rows.Add(row);
3137 }
3138 }
3139 }
3140 }
3141 }
3142
3143 /// <summary>
3144 /// Decompile the tables.
3145 /// </summary>
3146 /// <param name="output">The output being decompiled.</param>
3147 private void DecompileTables(Output output)
3148 {
3149 StringCollection sortedTableNames = this.GetSortedTableNames();
3150
3151 foreach (string tableName in sortedTableNames)
3152 {
3153 Table table = output.Tables[tableName];
3154
3155 // table does not exist in this database or should not be decompiled
3156 if (null == table || !this.DecompilableTable(output, tableName))
3157 {
3158 continue;
3159 }
3160
3161 this.core.OnMessage(WixVerboses.DecompilingTable(table.Name));
3162
3163 // empty tables may be kept with EnsureTable if the user set the proper option
3164 if (0 == table.Rows.Count && this.suppressDroppingEmptyTables)
3165 {
3166 Wix.EnsureTable ensureTable = new Wix.EnsureTable();
3167 ensureTable.Id = table.Name;
3168 this.core.RootElement.AddChild(ensureTable);
3169 }
3170
3171 switch (table.Name)
3172 {
3173 case "_SummaryInformation":
3174 this.Decompile_SummaryInformationTable(table);
3175 break;
3176 case "AdminExecuteSequence":
3177 case "AdminUISequence":
3178 case "AdvtExecuteSequence":
3179 case "InstallExecuteSequence":
3180 case "InstallUISequence":
3181 case "ModuleAdminExecuteSequence":
3182 case "ModuleAdminUISequence":
3183 case "ModuleAdvtExecuteSequence":
3184 case "ModuleInstallExecuteSequence":
3185 case "ModuleInstallUISequence":
3186 // handled in FinalizeSequenceTables
3187 break;
3188 case "ActionText":
3189 this.DecompileActionTextTable(table);
3190 break;
3191 case "AdvtUISequence":
3192 this.core.OnMessage(WixWarnings.DeprecatedTable(table.Name));
3193 break;
3194 case "AppId":
3195 this.DecompileAppIdTable(table);
3196 break;
3197 case "AppSearch":
3198 // handled in FinalizeSearchTables
3199 break;
3200 case "BBControl":
3201 this.DecompileBBControlTable(table);
3202 break;
3203 case "Billboard":
3204 this.DecompileBillboardTable(table);
3205 break;
3206 case "Binary":
3207 this.DecompileBinaryTable(table);
3208 break;
3209 case "BindImage":
3210 this.DecompileBindImageTable(table);
3211 break;
3212 case "CCPSearch":
3213 // handled in FinalizeSearchTables
3214 break;
3215 case "CheckBox":
3216 // handled in FinalizeCheckBoxTable
3217 break;
3218 case "Class":
3219 this.DecompileClassTable(table);
3220 break;
3221 case "ComboBox":
3222 this.DecompileComboBoxTable(table);
3223 break;
3224 case "Control":
3225 this.DecompileControlTable(table);
3226 break;
3227 case "ControlCondition":
3228 this.DecompileControlConditionTable(table);
3229 break;
3230 case "ControlEvent":
3231 this.DecompileControlEventTable(table);
3232 break;
3233 case "CreateFolder":
3234 this.DecompileCreateFolderTable(table);
3235 break;
3236 case "CustomAction":
3237 this.DecompileCustomActionTable(table);
3238 break;
3239 case "CompLocator":
3240 this.DecompileCompLocatorTable(table);
3241 break;
3242 case "Complus":
3243 this.DecompileComplusTable(table);
3244 break;
3245 case "Component":
3246 this.DecompileComponentTable(table);
3247 break;
3248 case "Condition":
3249 this.DecompileConditionTable(table);
3250 break;
3251 case "Dialog":
3252 this.DecompileDialogTable(table);
3253 break;
3254 case "Directory":
3255 this.DecompileDirectoryTable(table);
3256 break;
3257 case "DrLocator":
3258 this.DecompileDrLocatorTable(table);
3259 break;
3260 case "DuplicateFile":
3261 this.DecompileDuplicateFileTable(table);
3262 break;
3263 case "Environment":
3264 this.DecompileEnvironmentTable(table);
3265 break;
3266 case "Error":
3267 this.DecompileErrorTable(table);
3268 break;
3269 case "EventMapping":
3270 this.DecompileEventMappingTable(table);
3271 break;
3272 case "Extension":
3273 this.DecompileExtensionTable(table);
3274 break;
3275 case "ExternalFiles":
3276 this.DecompileExternalFilesTable(table);
3277 break;
3278 case "FamilyFileRanges":
3279 // handled in FinalizeFamilyFileRangesTable
3280 break;
3281 case "Feature":
3282 this.DecompileFeatureTable(table);
3283 break;
3284 case "FeatureComponents":
3285 this.DecompileFeatureComponentsTable(table);
3286 break;
3287 case "File":
3288 this.DecompileFileTable(table);
3289 break;
3290 case "FileSFPCatalog":
3291 this.DecompileFileSFPCatalogTable(table);
3292 break;
3293 case "Font":
3294 this.DecompileFontTable(table);
3295 break;
3296 case "Icon":
3297 this.DecompileIconTable(table);
3298 break;
3299 case "ImageFamilies":
3300 this.DecompileImageFamiliesTable(table);
3301 break;
3302 case "IniFile":
3303 this.DecompileIniFileTable(table);
3304 break;
3305 case "IniLocator":
3306 this.DecompileIniLocatorTable(table);
3307 break;
3308 case "IsolatedComponent":
3309 this.DecompileIsolatedComponentTable(table);
3310 break;
3311 case "LaunchCondition":
3312 this.DecompileLaunchConditionTable(table);
3313 break;
3314 case "ListBox":
3315 this.DecompileListBoxTable(table);
3316 break;
3317 case "ListView":
3318 this.DecompileListViewTable(table);
3319 break;
3320 case "LockPermissions":
3321 this.DecompileLockPermissionsTable(table);
3322 break;
3323 case "Media":
3324 this.DecompileMediaTable(table);
3325 break;
3326 case "MIME":
3327 this.DecompileMIMETable(table);
3328 break;
3329 case "ModuleAdvtUISequence":
3330 this.core.OnMessage(WixWarnings.DeprecatedTable(table.Name));
3331 break;
3332 case "ModuleComponents":
3333 // handled by DecompileComponentTable (since the ModuleComponents table
3334 // rows are created by nesting components under the Module element)
3335 break;
3336 case "ModuleConfiguration":
3337 this.DecompileModuleConfigurationTable(table);
3338 break;
3339 case "ModuleDependency":
3340 this.DecompileModuleDependencyTable(table);
3341 break;
3342 case "ModuleExclusion":
3343 this.DecompileModuleExclusionTable(table);
3344 break;
3345 case "ModuleIgnoreTable":
3346 this.DecompileModuleIgnoreTableTable(table);
3347 break;
3348 case "ModuleSignature":
3349 this.DecompileModuleSignatureTable(table);
3350 break;
3351 case "ModuleSubstitution":
3352 this.DecompileModuleSubstitutionTable(table);
3353 break;
3354 case "MoveFile":
3355 this.DecompileMoveFileTable(table);
3356 break;
3357 case "MsiAssembly":
3358 // handled in FinalizeFileTable
3359 break;
3360 case "MsiDigitalCertificate":
3361 this.DecompileMsiDigitalCertificateTable(table);
3362 break;
3363 case "MsiDigitalSignature":
3364 this.DecompileMsiDigitalSignatureTable(table);
3365 break;
3366 case "MsiEmbeddedChainer":
3367 this.DecompileMsiEmbeddedChainerTable(table);
3368 break;
3369 case "MsiEmbeddedUI":
3370 this.DecompileMsiEmbeddedUITable(table);
3371 break;
3372 case "MsiLockPermissionsEx":
3373 this.DecompileMsiLockPermissionsExTable(table);
3374 break;
3375 case "MsiPackageCertificate":
3376 this.DecompileMsiPackageCertificateTable(table);
3377 break;
3378 case "MsiPatchCertificate":
3379 this.DecompileMsiPatchCertificateTable(table);
3380 break;
3381 case "MsiShortcutProperty":
3382 this.DecompileMsiShortcutPropertyTable(table);
3383 break;
3384 case "ODBCAttribute":
3385 this.DecompileODBCAttributeTable(table);
3386 break;
3387 case "ODBCDataSource":
3388 this.DecompileODBCDataSourceTable(table);
3389 break;
3390 case "ODBCDriver":
3391 this.DecompileODBCDriverTable(table);
3392 break;
3393 case "ODBCSourceAttribute":
3394 this.DecompileODBCSourceAttributeTable(table);
3395 break;
3396 case "ODBCTranslator":
3397 this.DecompileODBCTranslatorTable(table);
3398 break;
3399 case "PatchMetadata":
3400 this.DecompilePatchMetadataTable(table);
3401 break;
3402 case "PatchSequence":
3403 this.DecompilePatchSequenceTable(table);
3404 break;
3405 case "ProgId":
3406 this.DecompileProgIdTable(table);
3407 break;
3408 case "Properties":
3409 this.DecompilePropertiesTable(table);
3410 break;
3411 case "Property":
3412 this.DecompilePropertyTable(table);
3413 break;
3414 case "PublishComponent":
3415 this.DecompilePublishComponentTable(table);
3416 break;
3417 case "RadioButton":
3418 this.DecompileRadioButtonTable(table);
3419 break;
3420 case "Registry":
3421 this.DecompileRegistryTable(table);
3422 break;
3423 case "RegLocator":
3424 this.DecompileRegLocatorTable(table);
3425 break;
3426 case "RemoveFile":
3427 this.DecompileRemoveFileTable(table);
3428 break;
3429 case "RemoveIniFile":
3430 this.DecompileRemoveIniFileTable(table);
3431 break;
3432 case "RemoveRegistry":
3433 this.DecompileRemoveRegistryTable(table);
3434 break;
3435 case "ReserveCost":
3436 this.DecompileReserveCostTable(table);
3437 break;
3438 case "SelfReg":
3439 this.DecompileSelfRegTable(table);
3440 break;
3441 case "ServiceControl":
3442 this.DecompileServiceControlTable(table);
3443 break;
3444 case "ServiceInstall":
3445 this.DecompileServiceInstallTable(table);
3446 break;
3447 case "SFPCatalog":
3448 this.DecompileSFPCatalogTable(table);
3449 break;
3450 case "Shortcut":
3451 this.DecompileShortcutTable(table);
3452 break;
3453 case "Signature":
3454 this.DecompileSignatureTable(table);
3455 break;
3456 case "TargetFiles_OptionalData":
3457 this.DecompileTargetFiles_OptionalDataTable(table);
3458 break;
3459 case "TargetImages":
3460 this.DecompileTargetImagesTable(table);
3461 break;
3462 case "TextStyle":
3463 this.DecompileTextStyleTable(table);
3464 break;
3465 case "TypeLib":
3466 this.DecompileTypeLibTable(table);
3467 break;
3468 case "Upgrade":
3469 this.DecompileUpgradeTable(table);
3470 break;
3471 case "UpgradedFiles_OptionalData":
3472 this.DecompileUpgradedFiles_OptionalDataTable(table);
3473 break;
3474 case "UpgradedFilesToIgnore":
3475 this.DecompileUpgradedFilesToIgnoreTable(table);
3476 break;
3477 case "UpgradedImages":
3478 this.DecompileUpgradedImagesTable(table);
3479 break;
3480 case "UIText":
3481 this.DecompileUITextTable(table);
3482 break;
3483 case "Verb":
3484 this.DecompileVerbTable(table);
3485 break;
3486 default:
3487 DecompilerExtension extension = (DecompilerExtension)this.extensionsByTableName[table.Name];
3488
3489 if (null != extension)
3490 {
3491 extension.DecompileTable(table);
3492 }
3493 else if (!this.suppressCustomTables)
3494 {
3495 this.DecompileCustomTable(table);
3496 }
3497 break;
3498 }
3499 }
3500 }
3501
3502 /// <summary>
3503 /// Determine if a particular table should be decompiled with the current settings.
3504 /// </summary>
3505 /// <param name="output">The output being decompiled.</param>
3506 /// <param name="tableName">The name of a table.</param>
3507 /// <returns>true if the table should be decompiled; false otherwise.</returns>
3508 private bool DecompilableTable(Output output, string tableName)
3509 {
3510 switch (tableName)
3511 {
3512 case "ActionText":
3513 case "BBControl":
3514 case "Billboard":
3515 case "CheckBox":
3516 case "Control":
3517 case "ControlCondition":
3518 case "ControlEvent":
3519 case "Dialog":
3520 case "Error":
3521 case "EventMapping":
3522 case "RadioButton":
3523 case "TextStyle":
3524 case "UIText":
3525 return !this.suppressUI;
3526 case "ModuleAdminExecuteSequence":
3527 case "ModuleAdminUISequence":
3528 case "ModuleAdvtExecuteSequence":
3529 case "ModuleAdvtUISequence":
3530 case "ModuleComponents":
3531 case "ModuleConfiguration":
3532 case "ModuleDependency":
3533 case "ModuleIgnoreTable":
3534 case "ModuleInstallExecuteSequence":
3535 case "ModuleInstallUISequence":
3536 case "ModuleExclusion":
3537 case "ModuleSignature":
3538 case "ModuleSubstitution":
3539 if (OutputType.Module != output.Type)
3540 {
3541 this.core.OnMessage(WixWarnings.SkippingMergeModuleTable(output.SourceLineNumbers, tableName));
3542 return false;
3543 }
3544 else
3545 {
3546 return true;
3547 }
3548 case "ExternalFiles":
3549 case "FamilyFileRanges":
3550 case "ImageFamilies":
3551 case "PatchMetadata":
3552 case "PatchSequence":
3553 case "Properties":
3554 case "TargetFiles_OptionalData":
3555 case "TargetImages":
3556 case "UpgradedFiles_OptionalData":
3557 case "UpgradedFilesToIgnore":
3558 case "UpgradedImages":
3559 if (OutputType.PatchCreation != output.Type)
3560 {
3561 this.core.OnMessage(WixWarnings.SkippingPatchCreationTable(output.SourceLineNumbers, tableName));
3562 return false;
3563 }
3564 else
3565 {
3566 return true;
3567 }
3568 case "MsiPatchHeaders":
3569 case "MsiPatchMetadata":
3570 case "MsiPatchOldAssemblyName":
3571 case "MsiPatchOldAssemblyFile":
3572 case "MsiPatchSequence":
3573 case "Patch":
3574 case "PatchPackage":
3575 this.core.OnMessage(WixWarnings.PatchTable(output.SourceLineNumbers, tableName));
3576 return false;
3577 case "_SummaryInformation":
3578 return true;
3579 case "_Validation":
3580 case "MsiAssemblyName":
3581 case "MsiFileHash":
3582 return false;
3583 default: // all other tables are allowed in any output except for a patch creation package
3584 if (OutputType.PatchCreation == output.Type)
3585 {
3586 this.core.OnMessage(WixWarnings.IllegalPatchCreationTable(output.SourceLineNumbers, tableName));
3587 return false;
3588 }
3589 else
3590 {
3591 return true;
3592 }
3593 }
3594 }
3595
3596 /// <summary>
3597 /// Decompile the _SummaryInformation table.
3598 /// </summary>
3599 /// <param name="table">The table to decompile.</param>
3600 private void Decompile_SummaryInformationTable(Table table)
3601 {
3602 if (OutputType.Module == this.outputType || OutputType.Product == this.outputType)
3603 {
3604 Wix.Package package = new Wix.Package();
3605
3606 foreach (Row row in table.Rows)
3607 {
3608 string value = Convert.ToString(row[1]);
3609
3610 if (null != value && 0 < value.Length)
3611 {
3612 switch (Convert.ToInt32(row[0]))
3613 {
3614 case 1:
3615 if ("1252" != value)
3616 {
3617 package.SummaryCodepage = value;
3618 }
3619 break;
3620 case 3:
3621 package.Description = value;
3622 break;
3623 case 4:
3624 package.Manufacturer = value;
3625 break;
3626 case 5:
3627 if ("Installer" != value)
3628 {
3629 package.Keywords = value;
3630 }
3631 break;
3632 case 6:
3633 package.Comments = value;
3634 break;
3635 case 7:
3636 string[] template = value.Split(';');
3637 if (0 < template.Length && 0 < template[template.Length - 1].Length)
3638 {
3639 package.Languages = template[template.Length - 1];
3640 }
3641
3642 if (1 < template.Length && null != template[0] && 0 < template[0].Length)
3643 {
3644 switch (template[0])
3645 {
3646 case "Intel":
3647 package.Platform = WixToolset.Data.Serialize.Package.PlatformType.x86;
3648 break;
3649 case "Intel64":
3650 package.Platform = WixToolset.Data.Serialize.Package.PlatformType.ia64;
3651 break;
3652 case "x64":
3653 package.Platform = WixToolset.Data.Serialize.Package.PlatformType.x64;
3654 break;
3655 }
3656 }
3657 break;
3658 case 9:
3659 if (OutputType.Module == this.outputType)
3660 {
3661 this.modularizationGuid = value;
3662 package.Id = value;
3663 }
3664 break;
3665 case 14:
3666 package.InstallerVersion = Convert.ToInt32(row[1], CultureInfo.InvariantCulture);
3667 break;
3668 case 15:
3669 int wordCount = Convert.ToInt32(row[1], CultureInfo.InvariantCulture);
3670 if (0x1 == (wordCount & 0x1))
3671 {
3672 this.shortNames = true;
3673 package.ShortNames = Wix.YesNoType.yes;
3674 }
3675
3676 if (0x2 == (wordCount & 0x2))
3677 {
3678 this.compressed = true;
3679
3680 if (OutputType.Product == this.outputType)
3681 {
3682 package.Compressed = Wix.YesNoType.yes;
3683 }
3684 }
3685
3686 if (0x4 == (wordCount & 0x4))
3687 {
3688 package.AdminImage = Wix.YesNoType.yes;
3689 }
3690
3691 if (0x8 == (wordCount & 0x8))
3692 {
3693 package.InstallPrivileges = Wix.Package.InstallPrivilegesType.limited;
3694 }
3695
3696 break;
3697 case 19:
3698 int security = Convert.ToInt32(row[1], CultureInfo.InvariantCulture);
3699 switch (security)
3700 {
3701 case 0:
3702 package.ReadOnly = Wix.YesNoDefaultType.no;
3703 break;
3704 case 4:
3705 package.ReadOnly = Wix.YesNoDefaultType.yes;
3706 break;
3707 }
3708 break;
3709 }
3710 }
3711 }
3712
3713 this.core.RootElement.AddChild(package);
3714 }
3715 else
3716 {
3717 Wix.PatchInformation patchInformation = new Wix.PatchInformation();
3718
3719 foreach (Row row in table.Rows)
3720 {
3721 int propertyId = Convert.ToInt32(row[0]);
3722 string value = Convert.ToString(row[1]);
3723
3724 if (null != row[1] && 0 < value.Length)
3725 {
3726 switch (propertyId)
3727 {
3728 case 1:
3729 if ("1252" != value)
3730 {
3731 patchInformation.SummaryCodepage = value;
3732 }
3733 break;
3734 case 3:
3735 patchInformation.Description = value;
3736 break;
3737 case 4:
3738 patchInformation.Manufacturer = value;
3739 break;
3740 case 5:
3741 if ("Installer,Patching,PCP,Database" != value)
3742 {
3743 patchInformation.Keywords = value;
3744 }
3745 break;
3746 case 6:
3747 patchInformation.Comments = value;
3748 break;
3749 case 7:
3750 string[] template = value.Split(';');
3751 if (0 < template.Length && 0 < template[template.Length - 1].Length)
3752 {
3753 patchInformation.Languages = template[template.Length - 1];
3754 }
3755
3756 if (1 < template.Length && null != template[0] && 0 < template[0].Length)
3757 {
3758 patchInformation.Platforms = template[0];
3759 }
3760 break;
3761 case 15:
3762 int wordCount = Convert.ToInt32(value, CultureInfo.InvariantCulture);
3763 if (0x1 == (wordCount & 0x1))
3764 {
3765 patchInformation.ShortNames = Wix.YesNoType.yes;
3766 }
3767
3768 if (0x2 == (wordCount & 0x2))
3769 {
3770 patchInformation.Compressed = Wix.YesNoType.yes;
3771 }
3772
3773 if (0x4 == (wordCount & 0x4))
3774 {
3775 patchInformation.AdminImage = Wix.YesNoType.yes;
3776 }
3777 break;
3778 case 19:
3779 int security = Convert.ToInt32(value, CultureInfo.InvariantCulture);
3780 switch (security)
3781 {
3782 case 0:
3783 patchInformation.ReadOnly = Wix.YesNoDefaultType.no;
3784 break;
3785 case 4:
3786 patchInformation.ReadOnly = Wix.YesNoDefaultType.yes;
3787 break;
3788 }
3789 break;
3790 }
3791 }
3792 }
3793
3794 this.core.RootElement.AddChild(patchInformation);
3795 }
3796 }
3797
3798 /// <summary>
3799 /// Decompile the ActionText table.
3800 /// </summary>
3801 /// <param name="table">The table to decompile.</param>
3802 private void DecompileActionTextTable(Table table)
3803 {
3804 foreach (Row row in table.Rows)
3805 {
3806 Wix.ProgressText progressText = new Wix.ProgressText();
3807
3808 progressText.Action = Convert.ToString(row[0]);
3809
3810 if (null != row[1])
3811 {
3812 progressText.Content = Convert.ToString(row[1]);
3813 }
3814
3815 if (null != row[2])
3816 {
3817 progressText.Template = Convert.ToString(row[2]);
3818 }
3819
3820 this.core.UIElement.AddChild(progressText);
3821 }
3822 }
3823
3824 /// <summary>
3825 /// Decompile the AppId table.
3826 /// </summary>
3827 /// <param name="table">The table to decompile.</param>
3828 private void DecompileAppIdTable(Table table)
3829 {
3830 foreach (Row row in table.Rows)
3831 {
3832 Wix.AppId appId = new Wix.AppId();
3833
3834 appId.Advertise = Wix.YesNoType.yes;
3835
3836 appId.Id = Convert.ToString(row[0]);
3837
3838 if (null != row[1])
3839 {
3840 appId.RemoteServerName = Convert.ToString(row[1]);
3841 }
3842
3843 if (null != row[2])
3844 {
3845 appId.LocalService = Convert.ToString(row[2]);
3846 }
3847
3848 if (null != row[3])
3849 {
3850 appId.ServiceParameters = Convert.ToString(row[3]);
3851 }
3852
3853 if (null != row[4])
3854 {
3855 appId.DllSurrogate = Convert.ToString(row[4]);
3856 }
3857
3858 if (null != row[5] && Int32.Equals(row[5], 1))
3859 {
3860 appId.ActivateAtStorage = Wix.YesNoType.yes;
3861 }
3862
3863 if (null != row[6] && Int32.Equals(row[6], 1))
3864 {
3865 appId.RunAsInteractiveUser = Wix.YesNoType.yes;
3866 }
3867
3868 this.core.RootElement.AddChild(appId);
3869 this.core.IndexElement(row, appId);
3870 }
3871 }
3872
3873 /// <summary>
3874 /// Decompile the BBControl table.
3875 /// </summary>
3876 /// <param name="table">The table to decompile.</param>
3877 private void DecompileBBControlTable(Table table)
3878 {
3879 foreach (BBControlRow bbControlRow in table.Rows)
3880 {
3881 Wix.Control control = new Wix.Control();
3882
3883 control.Id = bbControlRow.BBControl;
3884
3885 control.Type = bbControlRow.Type;
3886
3887 control.X = bbControlRow.X;
3888
3889 control.Y = bbControlRow.Y;
3890
3891 control.Width = bbControlRow.Width;
3892
3893 control.Height = bbControlRow.Height;
3894
3895 if (null != bbControlRow[7])
3896 {
3897 SetControlAttributes(bbControlRow.Attributes, control);
3898 }
3899
3900 if (null != bbControlRow.Text)
3901 {
3902 control.Text = bbControlRow.Text;
3903 }
3904
3905 Wix.Billboard billboard = (Wix.Billboard)this.core.GetIndexedElement("Billboard", bbControlRow.Billboard);
3906 if (null != billboard)
3907 {
3908 billboard.AddChild(control);
3909 }
3910 else
3911 {
3912 this.core.OnMessage(WixWarnings.ExpectedForeignRow(bbControlRow.SourceLineNumbers, table.Name, bbControlRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Billboard_", bbControlRow.Billboard, "Billboard"));
3913 }
3914 }
3915 }
3916
3917 /// <summary>
3918 /// Decompile the Billboard table.
3919 /// </summary>
3920 /// <param name="table">The table to decompile.</param>
3921 private void DecompileBillboardTable(Table table)
3922 {
3923 Hashtable billboardActions = new Hashtable();
3924 SortedList billboards = new SortedList();
3925
3926 foreach (Row row in table.Rows)
3927 {
3928 Wix.Billboard billboard = new Wix.Billboard();
3929
3930 billboard.Id = Convert.ToString(row[0]);
3931
3932 billboard.Feature = Convert.ToString(row[1]);
3933
3934 this.core.IndexElement(row, billboard);
3935 billboards.Add(String.Format(CultureInfo.InvariantCulture, "{0}|{1:0000000000}", row[0], row[3]), row);
3936 }
3937
3938 foreach (Row row in billboards.Values)
3939 {
3940 Wix.Billboard billboard = (Wix.Billboard)this.core.GetIndexedElement(row);
3941 Wix.BillboardAction billboardAction = (Wix.BillboardAction)billboardActions[row[2]];
3942
3943 if (null == billboardAction)
3944 {
3945 billboardAction = new Wix.BillboardAction();
3946
3947 billboardAction.Id = Convert.ToString(row[2]);
3948
3949 this.core.UIElement.AddChild(billboardAction);
3950 billboardActions.Add(row[2], billboardAction);
3951 }
3952
3953 billboardAction.AddChild(billboard);
3954 }
3955 }
3956
3957 /// <summary>
3958 /// Decompile the Binary table.
3959 /// </summary>
3960 /// <param name="table">The table to decompile.</param>
3961 private void DecompileBinaryTable(Table table)
3962 {
3963 foreach (Row row in table.Rows)
3964 {
3965 Wix.Binary binary = new Wix.Binary();
3966
3967 binary.Id = Convert.ToString(row[0]);
3968
3969 binary.SourceFile = Convert.ToString(row[1]);
3970
3971 this.core.RootElement.AddChild(binary);
3972 }
3973 }
3974
3975 /// <summary>
3976 /// Decompile the BindImage table.
3977 /// </summary>
3978 /// <param name="table">The table to decompile.</param>
3979 private void DecompileBindImageTable(Table table)
3980 {
3981 foreach (Row row in table.Rows)
3982 {
3983 Wix.File file = (Wix.File)this.core.GetIndexedElement("File", Convert.ToString(row[0]));
3984
3985 if (null != file)
3986 {
3987 file.BindPath = Convert.ToString(row[1]);
3988 }
3989 else
3990 {
3991 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "File_", Convert.ToString(row[0]), "File"));
3992 }
3993 }
3994 }
3995
3996 /// <summary>
3997 /// Decompile the Class table.
3998 /// </summary>
3999 /// <param name="table">The table to decompile.</param>
4000 private void DecompileClassTable(Table table)
4001 {
4002 foreach (Row row in table.Rows)
4003 {
4004 Wix.Class wixClass = new Wix.Class();
4005
4006 wixClass.Advertise = Wix.YesNoType.yes;
4007
4008 wixClass.Id = Convert.ToString(row[0]);
4009
4010 switch (Convert.ToString(row[1]))
4011 {
4012 case "LocalServer":
4013 wixClass.Context = Wix.Class.ContextType.LocalServer;
4014 break;
4015 case "LocalServer32":
4016 wixClass.Context = Wix.Class.ContextType.LocalServer32;
4017 break;
4018 case "InprocServer":
4019 wixClass.Context = Wix.Class.ContextType.InprocServer;
4020 break;
4021 case "InprocServer32":
4022 wixClass.Context = Wix.Class.ContextType.InprocServer32;
4023 break;
4024 default:
4025 this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1]));
4026 break;
4027 }
4028
4029 // ProgId children are handled in FinalizeProgIdTable
4030
4031 if (null != row[4])
4032 {
4033 wixClass.Description = Convert.ToString(row[4]);
4034 }
4035
4036 if (null != row[5])
4037 {
4038 wixClass.AppId = Convert.ToString(row[5]);
4039 }
4040
4041 if (null != row[6])
4042 {
4043 string[] fileTypeMaskStrings = (Convert.ToString(row[6])).Split(';');
4044
4045 try
4046 {
4047 foreach (string fileTypeMaskString in fileTypeMaskStrings)
4048 {
4049 string[] fileTypeMaskParts = fileTypeMaskString.Split(',');
4050
4051 if (4 == fileTypeMaskParts.Length)
4052 {
4053 Wix.FileTypeMask fileTypeMask = new Wix.FileTypeMask();
4054
4055 fileTypeMask.Offset = Convert.ToInt32(fileTypeMaskParts[0], CultureInfo.InvariantCulture);
4056
4057 fileTypeMask.Mask = fileTypeMaskParts[2];
4058
4059 fileTypeMask.Value = fileTypeMaskParts[3];
4060
4061 wixClass.AddChild(fileTypeMask);
4062 }
4063 else
4064 {
4065 // TODO: warn
4066 }
4067 }
4068 }
4069 catch (FormatException)
4070 {
4071 this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6]));
4072 }
4073 catch (OverflowException)
4074 {
4075 this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6]));
4076 }
4077 }
4078
4079 if (null != row[7])
4080 {
4081 wixClass.Icon = Convert.ToString(row[7]);
4082 }
4083
4084 if (null != row[8])
4085 {
4086 wixClass.IconIndex = Convert.ToInt32(row[8]);
4087 }
4088
4089 if (null != row[9])
4090 {
4091 wixClass.Handler = Convert.ToString(row[9]);
4092 }
4093
4094 if (null != row[10])
4095 {
4096 wixClass.Argument = Convert.ToString(row[10]);
4097 }
4098
4099 if (null != row[12])
4100 {
4101 if (1 == Convert.ToInt32(row[12]))
4102 {
4103 wixClass.RelativePath = Wix.YesNoType.yes;
4104 }
4105 else
4106 {
4107 this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[12].Column.Name, row[12]));
4108 }
4109 }
4110
4111 Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[2]));
4112 if (null != component)
4113 {
4114 component.AddChild(wixClass);
4115 }
4116 else
4117 {
4118 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[2]), "Component"));
4119 }
4120
4121 this.core.IndexElement(row, wixClass);
4122 }
4123 }
4124
4125 /// <summary>
4126 /// Decompile the ComboBox table.
4127 /// </summary>
4128 /// <param name="table">The table to decompile.</param>
4129 private void DecompileComboBoxTable(Table table)
4130 {
4131 Wix.ComboBox comboBox = null;
4132 SortedList comboBoxRows = new SortedList();
4133
4134 // sort the combo boxes by their property and order
4135 foreach (Row row in table.Rows)
4136 {
4137 comboBoxRows.Add(String.Concat("{0}|{1:0000000000}", row[0], row[1]), row);
4138 }
4139
4140 foreach (Row row in comboBoxRows.Values)
4141 {
4142 if (null == comboBox || Convert.ToString(row[0]) != comboBox.Property)
4143 {
4144 comboBox = new Wix.ComboBox();
4145
4146 comboBox.Property = Convert.ToString(row[0]);
4147
4148 this.core.UIElement.AddChild(comboBox);
4149 }
4150
4151 Wix.ListItem listItem = new Wix.ListItem();
4152
4153 listItem.Value = Convert.ToString(row[2]);
4154
4155 if (null != row[3])
4156 {
4157 listItem.Text = Convert.ToString(row[3]);
4158 }
4159
4160 comboBox.AddChild(listItem);
4161 }
4162 }
4163
4164 /// <summary>
4165 /// Decompile the Control table.
4166 /// </summary>
4167 /// <param name="table">The table to decompile.</param>
4168 private void DecompileControlTable(Table table)
4169 {
4170 foreach (ControlRow controlRow in table.Rows)
4171 {
4172 Wix.Control control = new Wix.Control();
4173
4174 control.Id = controlRow.Control;
4175
4176 control.Type = controlRow.Type;
4177
4178 control.X = controlRow.X;
4179
4180 control.Y = controlRow.Y;
4181
4182 control.Width = controlRow.Width;
4183
4184 control.Height = controlRow.Height;
4185
4186 if (null != controlRow[7])
4187 {
4188 string[] specialAttributes;
4189
4190 // sets various common attributes like Disabled, Indirect, Integer, ...
4191 SetControlAttributes(controlRow.Attributes, control);
4192
4193 switch (control.Type)
4194 {
4195 case "Bitmap":
4196 specialAttributes = MsiInterop.BitmapControlAttributes;
4197 break;
4198 case "CheckBox":
4199 specialAttributes = MsiInterop.CheckboxControlAttributes;
4200 break;
4201 case "ComboBox":
4202 specialAttributes = MsiInterop.ComboboxControlAttributes;
4203 break;
4204 case "DirectoryCombo":
4205 specialAttributes = MsiInterop.VolumeControlAttributes;
4206 break;
4207 case "Edit":
4208 specialAttributes = MsiInterop.EditControlAttributes;
4209 break;
4210 case "Icon":
4211 specialAttributes = MsiInterop.IconControlAttributes;
4212 break;
4213 case "ListBox":
4214 specialAttributes = MsiInterop.ListboxControlAttributes;
4215 break;
4216 case "ListView":
4217 specialAttributes = MsiInterop.ListviewControlAttributes;
4218 break;
4219 case "MaskedEdit":
4220 specialAttributes = MsiInterop.EditControlAttributes;
4221 break;
4222 case "PathEdit":
4223 specialAttributes = MsiInterop.EditControlAttributes;
4224 break;
4225 case "ProgressBar":
4226 specialAttributes = MsiInterop.ProgressControlAttributes;
4227 break;
4228 case "PushButton":
4229 specialAttributes = MsiInterop.ButtonControlAttributes;
4230 break;
4231 case "RadioButtonGroup":
4232 specialAttributes = MsiInterop.RadioControlAttributes;
4233 break;
4234 case "Text":
4235 specialAttributes = MsiInterop.TextControlAttributes;
4236 break;
4237 case "VolumeCostList":
4238 specialAttributes = MsiInterop.VolumeControlAttributes;
4239 break;
4240 case "VolumeSelectCombo":
4241 specialAttributes = MsiInterop.VolumeControlAttributes;
4242 break;
4243 default:
4244 specialAttributes = null;
4245 break;
4246 }
4247
4248 if (null != specialAttributes)
4249 {
4250 bool iconSizeSet = false;
4251
4252 for (int i = 16; 32 > i; i++)
4253 {
4254 if (1 == ((controlRow.Attributes >> i) & 1))
4255 {
4256 string attribute = null;
4257
4258 if (specialAttributes.Length > (i - 16))
4259 {
4260 attribute = specialAttributes[i - 16];
4261 }
4262
4263 // unknown attribute
4264 if (null == attribute)
4265 {
4266 this.core.OnMessage(WixWarnings.IllegalColumnValue(controlRow.SourceLineNumbers, table.Name, controlRow.Fields[7].Column.Name, controlRow.Attributes));
4267 continue;
4268 }
4269
4270 switch (attribute)
4271 {
4272 case "Bitmap":
4273 control.Bitmap = Wix.YesNoType.yes;
4274 break;
4275 case "CDROM":
4276 control.CDROM = Wix.YesNoType.yes;
4277 break;
4278 case "ComboList":
4279 control.ComboList = Wix.YesNoType.yes;
4280 break;
4281 case "ElevationShield":
4282 control.ElevationShield = Wix.YesNoType.yes;
4283 break;
4284 case "Fixed":
4285 control.Fixed = Wix.YesNoType.yes;
4286 break;
4287 case "FixedSize":
4288 control.FixedSize = Wix.YesNoType.yes;
4289 break;
4290 case "Floppy":
4291 control.Floppy = Wix.YesNoType.yes;
4292 break;
4293 case "FormatSize":
4294 control.FormatSize = Wix.YesNoType.yes;
4295 break;
4296 case "HasBorder":
4297 control.HasBorder = Wix.YesNoType.yes;
4298 break;
4299 case "Icon":
4300 control.Icon = Wix.YesNoType.yes;
4301 break;
4302 case "Icon16":
4303 if (iconSizeSet)
4304 {
4305 control.IconSize = Wix.Control.IconSizeType.Item48;
4306 }
4307 else
4308 {
4309 iconSizeSet = true;
4310 control.IconSize = Wix.Control.IconSizeType.Item16;
4311 }
4312 break;
4313 case "Icon32":
4314 if (iconSizeSet)
4315 {
4316 control.IconSize = Wix.Control.IconSizeType.Item48;
4317 }
4318 else
4319 {
4320 iconSizeSet = true;
4321 control.IconSize = Wix.Control.IconSizeType.Item32;
4322 }
4323 break;
4324 case "Image":
4325 control.Image = Wix.YesNoType.yes;
4326 break;
4327 case "Multiline":
4328 control.Multiline = Wix.YesNoType.yes;
4329 break;
4330 case "NoPrefix":
4331 control.NoPrefix = Wix.YesNoType.yes;
4332 break;
4333 case "NoWrap":
4334 control.NoWrap = Wix.YesNoType.yes;
4335 break;
4336 case "Password":
4337 control.Password = Wix.YesNoType.yes;
4338 break;
4339 case "ProgressBlocks":
4340 control.ProgressBlocks = Wix.YesNoType.yes;
4341 break;
4342 case "PushLike":
4343 control.PushLike = Wix.YesNoType.yes;
4344 break;
4345 case "RAMDisk":
4346 control.RAMDisk = Wix.YesNoType.yes;
4347 break;
4348 case "Remote":
4349 control.Remote = Wix.YesNoType.yes;
4350 break;
4351 case "Removable":
4352 control.Removable = Wix.YesNoType.yes;
4353 break;
4354 case "ShowRollbackCost":
4355 control.ShowRollbackCost = Wix.YesNoType.yes;
4356 break;
4357 case "Sorted":
4358 control.Sorted = Wix.YesNoType.yes;
4359 break;
4360 case "Transparent":
4361 control.Transparent = Wix.YesNoType.yes;
4362 break;
4363 case "UserLanguage":
4364 control.UserLanguage = Wix.YesNoType.yes;
4365 break;
4366 default:
4367 throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnknowControlAttribute, attribute));
4368 }
4369 }
4370 }
4371 }
4372 else if (0 < (controlRow.Attributes & 0xFFFF0000))
4373 {
4374 this.core.OnMessage(WixWarnings.IllegalColumnValue(controlRow.SourceLineNumbers, table.Name, controlRow.Fields[7].Column.Name, controlRow.Attributes));
4375 }
4376 }
4377
4378 // FinalizeCheckBoxTable adds Control/@Property|@CheckBoxPropertyRef
4379 if (null != controlRow.Property && 0 != String.CompareOrdinal("CheckBox", control.Type))
4380 {
4381 control.Property = controlRow.Property;
4382 }
4383
4384 if (null != controlRow.Text)
4385 {
4386 control.Text = controlRow.Text;
4387 }
4388
4389 if (null != controlRow.Help)
4390 {
4391 string[] help = controlRow.Help.Split('|');
4392
4393 if (2 == help.Length)
4394 {
4395 if (0 < help[0].Length)
4396 {
4397 control.ToolTip = help[0];
4398 }
4399
4400 if (0 < help[1].Length)
4401 {
4402 control.Help = help[1];
4403 }
4404 }
4405 }
4406
4407 this.core.IndexElement(controlRow, control);
4408 }
4409 }
4410
4411 /// <summary>
4412 /// Decompile the ControlCondition table.
4413 /// </summary>
4414 /// <param name="table">The table to decompile.</param>
4415 private void DecompileControlConditionTable(Table table)
4416 {
4417 foreach (Row row in table.Rows)
4418 {
4419 Wix.Condition condition = new Wix.Condition();
4420
4421 switch (Convert.ToString(row[2]))
4422 {
4423 case "Default":
4424 condition.Action = Wix.Condition.ActionType.@default;
4425 break;
4426 case "Disable":
4427 condition.Action = Wix.Condition.ActionType.disable;
4428 break;
4429 case "Enable":
4430 condition.Action = Wix.Condition.ActionType.enable;
4431 break;
4432 case "Hide":
4433 condition.Action = Wix.Condition.ActionType.hide;
4434 break;
4435 case "Show":
4436 condition.Action = Wix.Condition.ActionType.show;
4437 break;
4438 default:
4439 this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[2].Column.Name, row[2]));
4440 break;
4441 }
4442
4443 condition.Content = Convert.ToString(row[3]);
4444
4445 Wix.Control control = (Wix.Control)this.core.GetIndexedElement("Control", Convert.ToString(row[0]), Convert.ToString(row[1]));
4446 if (null != control)
4447 {
4448 control.AddChild(condition);
4449 }
4450 else
4451 {
4452 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", Convert.ToString(row[0]), "Control_", Convert.ToString(row[1]), "Control"));
4453 }
4454 }
4455 }
4456
4457 /// <summary>
4458 /// Decompile the ControlEvent table.
4459 /// </summary>
4460 /// <param name="table">The table to decompile.</param>
4461 private void DecompileControlEventTable(Table table)
4462 {
4463 SortedList controlEvents = new SortedList();
4464
4465 foreach (Row row in table.Rows)
4466 {
4467 Wix.Publish publish = new Wix.Publish();
4468
4469 string publishEvent = Convert.ToString(row[2]);
4470 if (publishEvent.StartsWith("[", StringComparison.Ordinal) && publishEvent.EndsWith("]", StringComparison.Ordinal))
4471 {
4472 publish.Property = publishEvent.Substring(1, publishEvent.Length - 2);
4473
4474 if ("{}" != Convert.ToString(row[3]))
4475 {
4476 publish.Value = Convert.ToString(row[3]);
4477 }
4478 }
4479 else
4480 {
4481 publish.Event = publishEvent;
4482 publish.Value = Convert.ToString(row[3]);
4483 }
4484
4485 if (null != row[4])
4486 {
4487 publish.Content = Convert.ToString(row[4]);
4488 }
4489
4490 controlEvents.Add(String.Format(CultureInfo.InvariantCulture, "{0}|{1}|{2:0000000000}|{3}|{4}|{5}", row[0], row[1], (null == row[5] ? 0 : Convert.ToInt32(row[5])), row[2], row[3], row[4]), row);
4491
4492 this.core.IndexElement(row, publish);
4493 }
4494
4495 foreach (Row row in controlEvents.Values)
4496 {
4497 Wix.Control control = (Wix.Control)this.core.GetIndexedElement("Control", Convert.ToString(row[0]), Convert.ToString(row[1]));
4498 Wix.Publish publish = (Wix.Publish)this.core.GetIndexedElement(row);
4499
4500 if (null != control)
4501 {
4502 control.AddChild(publish);
4503 }
4504 else
4505 {
4506 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", Convert.ToString(row[0]), "Control_", Convert.ToString(row[1]), "Control"));
4507 }
4508 }
4509 }
4510
4511 /// <summary>
4512 /// Decompile a custom table.
4513 /// </summary>
4514 /// <param name="table">The table to decompile.</param>
4515 private void DecompileCustomTable(Table table)
4516 {
4517 if (0 < table.Rows.Count || this.suppressDroppingEmptyTables)
4518 {
4519 Wix.CustomTable customTable = new Wix.CustomTable();
4520
4521 this.core.OnMessage(WixWarnings.DecompilingAsCustomTable(table.Rows[0].SourceLineNumbers, table.Name));
4522
4523 customTable.Id = table.Name;
4524
4525 foreach (ColumnDefinition columnDefinition in table.Definition.Columns)
4526 {
4527 Wix.Column column = new Wix.Column();
4528
4529 column.Id = columnDefinition.Name;
4530
4531 if (ColumnCategory.Unknown != columnDefinition.Category)
4532 {
4533 switch (columnDefinition.Category)
4534 {
4535 case ColumnCategory.Text:
4536 column.Category = Wix.Column.CategoryType.Text;
4537 break;
4538 case ColumnCategory.UpperCase:
4539 column.Category = Wix.Column.CategoryType.UpperCase;
4540 break;
4541 case ColumnCategory.LowerCase:
4542 column.Category = Wix.Column.CategoryType.LowerCase;
4543 break;
4544 case ColumnCategory.Integer:
4545 column.Category = Wix.Column.CategoryType.Integer;
4546 break;
4547 case ColumnCategory.DoubleInteger:
4548 column.Category = Wix.Column.CategoryType.DoubleInteger;
4549 break;
4550 case ColumnCategory.TimeDate:
4551 column.Category = Wix.Column.CategoryType.TimeDate;
4552 break;
4553 case ColumnCategory.Identifier:
4554 column.Category = Wix.Column.CategoryType.Identifier;
4555 break;
4556 case ColumnCategory.Property:
4557 column.Category = Wix.Column.CategoryType.Property;
4558 break;
4559 case ColumnCategory.Filename:
4560 column.Category = Wix.Column.CategoryType.Filename;
4561 break;
4562 case ColumnCategory.WildCardFilename:
4563 column.Category = Wix.Column.CategoryType.WildCardFilename;
4564 break;
4565 case ColumnCategory.Path:
4566 column.Category = Wix.Column.CategoryType.Path;
4567 break;
4568 case ColumnCategory.Paths:
4569 column.Category = Wix.Column.CategoryType.Paths;
4570 break;
4571 case ColumnCategory.AnyPath:
4572 column.Category = Wix.Column.CategoryType.AnyPath;
4573 break;
4574 case ColumnCategory.DefaultDir:
4575 column.Category = Wix.Column.CategoryType.DefaultDir;
4576 break;
4577 case ColumnCategory.RegPath:
4578 column.Category = Wix.Column.CategoryType.RegPath;
4579 break;
4580 case ColumnCategory.Formatted:
4581 column.Category = Wix.Column.CategoryType.Formatted;
4582 break;
4583 case ColumnCategory.FormattedSDDLText:
4584 column.Category = Wix.Column.CategoryType.FormattedSddl;
4585 break;
4586 case ColumnCategory.Template:
4587 column.Category = Wix.Column.CategoryType.Template;
4588 break;
4589 case ColumnCategory.Condition:
4590 column.Category = Wix.Column.CategoryType.Condition;
4591 break;
4592 case ColumnCategory.Guid:
4593 column.Category = Wix.Column.CategoryType.Guid;
4594 break;
4595 case ColumnCategory.Version:
4596 column.Category = Wix.Column.CategoryType.Version;
4597 break;
4598 case ColumnCategory.Language:
4599 column.Category = Wix.Column.CategoryType.Language;
4600 break;
4601 case ColumnCategory.Binary:
4602 column.Category = Wix.Column.CategoryType.Binary;
4603 break;
4604 case ColumnCategory.CustomSource:
4605 column.Category = Wix.Column.CategoryType.CustomSource;
4606 break;
4607 case ColumnCategory.Cabinet:
4608 column.Category = Wix.Column.CategoryType.Cabinet;
4609 break;
4610 case ColumnCategory.Shortcut:
4611 column.Category = Wix.Column.CategoryType.Shortcut;
4612 break;
4613 default:
4614 throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnknownCustomColumnCategory, columnDefinition.Category.ToString()));
4615 }
4616 }
4617
4618 if (null != columnDefinition.Description)
4619 {
4620 column.Description = columnDefinition.Description;
4621 }
4622
4623 if (columnDefinition.IsKeyColumnSet)
4624 {
4625 column.KeyColumn = columnDefinition.KeyColumn;
4626 }
4627
4628 if (null != columnDefinition.KeyTable)
4629 {
4630 column.KeyTable = columnDefinition.KeyTable;
4631 }
4632
4633 if (columnDefinition.IsLocalizable)
4634 {
4635 column.Localizable = Wix.YesNoType.yes;
4636 }
4637
4638 if (columnDefinition.IsMaxValueSet)
4639 {
4640 column.MaxValue = columnDefinition.MaxValue;
4641 }
4642
4643 if (columnDefinition.IsMinValueSet)
4644 {
4645 column.MinValue = columnDefinition.MinValue;
4646 }
4647
4648 if (ColumnModularizeType.None != columnDefinition.ModularizeType)
4649 {
4650 switch (columnDefinition.ModularizeType)
4651 {
4652 case ColumnModularizeType.Column:
4653 column.Modularize = Wix.Column.ModularizeType.Column;
4654 break;
4655 case ColumnModularizeType.Condition:
4656 column.Modularize = Wix.Column.ModularizeType.Condition;
4657 break;
4658 case ColumnModularizeType.Icon:
4659 column.Modularize = Wix.Column.ModularizeType.Icon;
4660 break;
4661 case ColumnModularizeType.Property:
4662 column.Modularize = Wix.Column.ModularizeType.Property;
4663 break;
4664 case ColumnModularizeType.SemicolonDelimited:
4665 column.Modularize = Wix.Column.ModularizeType.SemicolonDelimited;
4666 break;
4667 default:
4668 throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnknownCustomColumnModularizationType, columnDefinition.ModularizeType.ToString()));
4669 }
4670 }
4671
4672 if (columnDefinition.Nullable)
4673 {
4674 column.Nullable = Wix.YesNoType.yes;
4675 }
4676
4677 if (columnDefinition.PrimaryKey)
4678 {
4679 column.PrimaryKey = Wix.YesNoType.yes;
4680 }
4681
4682 if (null != columnDefinition.Possibilities)
4683 {
4684 column.Set = columnDefinition.Possibilities;
4685 }
4686
4687 if (ColumnType.Unknown != columnDefinition.Type)
4688 {
4689 switch (columnDefinition.Type)
4690 {
4691 case ColumnType.Localized:
4692 column.Localizable = Wix.YesNoType.yes;
4693 column.Type = Wix.Column.TypeType.@string;
4694 break;
4695 case ColumnType.Number:
4696 column.Type = Wix.Column.TypeType.@int;
4697 break;
4698 case ColumnType.Object:
4699 column.Type = Wix.Column.TypeType.binary;
4700 break;
4701 case ColumnType.Preserved:
4702 case ColumnType.String:
4703 column.Type = Wix.Column.TypeType.@string;
4704 break;
4705 default:
4706 throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnknownCustomColumnType, columnDefinition.Type.ToString()));
4707 }
4708 }
4709
4710 column.Width = columnDefinition.Length;
4711
4712 customTable.AddChild(column);
4713 }
4714
4715 foreach (Row row in table.Rows)
4716 {
4717 Wix.Row wixRow = new Wix.Row();
4718
4719 foreach (Field field in row.Fields)
4720 {
4721 Wix.Data data = new Wix.Data();
4722
4723 data.Column = field.Column.Name;
4724
4725 data.Content = Convert.ToString(field.Data, CultureInfo.InvariantCulture);
4726
4727 wixRow.AddChild(data);
4728 }
4729
4730 customTable.AddChild(wixRow);
4731 }
4732
4733 this.core.RootElement.AddChild(customTable);
4734 }
4735 }
4736
4737 /// <summary>
4738 /// Decompile the CreateFolder table.
4739 /// </summary>
4740 /// <param name="table">The table to decompile.</param>
4741 private void DecompileCreateFolderTable(Table table)
4742 {
4743 foreach (Row row in table.Rows)
4744 {
4745 Wix.CreateFolder createFolder = new Wix.CreateFolder();
4746
4747 createFolder.Directory = Convert.ToString(row[0]);
4748
4749 Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1]));
4750 if (null != component)
4751 {
4752 component.AddChild(createFolder);
4753 }
4754 else
4755 {
4756 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component"));
4757 }
4758 this.core.IndexElement(row, createFolder);
4759 }
4760 }
4761
4762 /// <summary>
4763 /// Decompile the CustomAction table.
4764 /// </summary>
4765 /// <param name="table">The table to decompile.</param>
4766 private void DecompileCustomActionTable(Table table)
4767 {
4768 foreach (Row row in table.Rows)
4769 {
4770 Wix.CustomAction customAction = new Wix.CustomAction();
4771
4772 customAction.Id = Convert.ToString(row[0]);
4773
4774 int type = Convert.ToInt32(row[1]);
4775
4776 if (MsiInterop.MsidbCustomActionTypeHideTarget == (type & MsiInterop.MsidbCustomActionTypeHideTarget))
4777 {
4778 customAction.HideTarget = Wix.YesNoType.yes;
4779 }
4780
4781 if (MsiInterop.MsidbCustomActionTypeNoImpersonate == (type & MsiInterop.MsidbCustomActionTypeNoImpersonate))
4782 {
4783 customAction.Impersonate = Wix.YesNoType.no;
4784 }
4785
4786 if (MsiInterop.MsidbCustomActionTypeTSAware == (type & MsiInterop.MsidbCustomActionTypeTSAware))
4787 {
4788 customAction.TerminalServerAware = Wix.YesNoType.yes;
4789 }
4790
4791 if (MsiInterop.MsidbCustomActionType64BitScript == (type & MsiInterop.MsidbCustomActionType64BitScript))
4792 {
4793 customAction.Win64 = Wix.YesNoType.yes;
4794 }
4795
4796 switch (type & MsiInterop.MsidbCustomActionTypeExecuteBits)
4797 {
4798 case 0:
4799 // this is the default value
4800 break;
4801 case MsiInterop.MsidbCustomActionTypeFirstSequence:
4802 customAction.Execute = Wix.CustomAction.ExecuteType.firstSequence;
4803 break;
4804 case MsiInterop.MsidbCustomActionTypeOncePerProcess:
4805 customAction.Execute = Wix.CustomAction.ExecuteType.oncePerProcess;
4806 break;
4807 case MsiInterop.MsidbCustomActionTypeClientRepeat:
4808 customAction.Execute = Wix.CustomAction.ExecuteType.secondSequence;
4809 break;
4810 case MsiInterop.MsidbCustomActionTypeInScript:
4811 customAction.Execute = Wix.CustomAction.ExecuteType.deferred;
4812 break;
4813 case MsiInterop.MsidbCustomActionTypeInScript + MsiInterop.MsidbCustomActionTypeRollback:
4814 customAction.Execute = Wix.CustomAction.ExecuteType.rollback;
4815 break;
4816 case MsiInterop.MsidbCustomActionTypeInScript + MsiInterop.MsidbCustomActionTypeCommit:
4817 customAction.Execute = Wix.CustomAction.ExecuteType.commit;
4818 break;
4819 default:
4820 this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1]));
4821 break;
4822 }
4823
4824 switch (type & MsiInterop.MsidbCustomActionTypeReturnBits)
4825 {
4826 case 0:
4827 // this is the default value
4828 break;
4829 case MsiInterop.MsidbCustomActionTypeContinue:
4830 customAction.Return = Wix.CustomAction.ReturnType.ignore;
4831 break;
4832 case MsiInterop.MsidbCustomActionTypeAsync:
4833 customAction.Return = Wix.CustomAction.ReturnType.asyncWait;
4834 break;
4835 case MsiInterop.MsidbCustomActionTypeAsync + MsiInterop.MsidbCustomActionTypeContinue:
4836 customAction.Return = Wix.CustomAction.ReturnType.asyncNoWait;
4837 break;
4838 default:
4839 this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1]));
4840 break;
4841 }
4842
4843 int source = type & MsiInterop.MsidbCustomActionTypeSourceBits;
4844 switch (source)
4845 {
4846 case MsiInterop.MsidbCustomActionTypeBinaryData:
4847 customAction.BinaryKey = Convert.ToString(row[2]);
4848 break;
4849 case MsiInterop.MsidbCustomActionTypeSourceFile:
4850 if (null != row[2])
4851 {
4852 customAction.FileKey = Convert.ToString(row[2]);
4853 }
4854 break;
4855 case MsiInterop.MsidbCustomActionTypeDirectory:
4856 if (null != row[2])
4857 {
4858 customAction.Directory = Convert.ToString(row[2]);
4859 }
4860 break;
4861 case MsiInterop.MsidbCustomActionTypeProperty:
4862 customAction.Property = Convert.ToString(row[2]);
4863 break;
4864 default:
4865 this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1]));
4866 break;
4867 }
4868
4869 switch (type & MsiInterop.MsidbCustomActionTypeTargetBits)
4870 {
4871 case MsiInterop.MsidbCustomActionTypeDll:
4872 customAction.DllEntry = Convert.ToString(row[3]);
4873 break;
4874 case MsiInterop.MsidbCustomActionTypeExe:
4875 customAction.ExeCommand = Convert.ToString(row[3]);
4876 break;
4877 case MsiInterop.MsidbCustomActionTypeTextData:
4878 if (MsiInterop.MsidbCustomActionTypeSourceFile == source)
4879 {
4880 customAction.Error = Convert.ToString(row[3]);
4881 }
4882 else
4883 {
4884 customAction.Value = Convert.ToString(row[3]);
4885 }
4886 break;
4887 case MsiInterop.MsidbCustomActionTypeJScript:
4888 if (MsiInterop.MsidbCustomActionTypeDirectory == source)
4889 {
4890 customAction.Script = Wix.CustomAction.ScriptType.jscript;
4891 customAction.Content = Convert.ToString(row[3]);
4892 }
4893 else
4894 {
4895 customAction.JScriptCall = Convert.ToString(row[3]);
4896 }
4897 break;
4898 case MsiInterop.MsidbCustomActionTypeVBScript:
4899 if (MsiInterop.MsidbCustomActionTypeDirectory == source)
4900 {
4901 customAction.Script = Wix.CustomAction.ScriptType.vbscript;
4902 customAction.Content = Convert.ToString(row[3]);
4903 }
4904 else
4905 {
4906 customAction.VBScriptCall = Convert.ToString(row[3]);
4907 }
4908 break;
4909 case MsiInterop.MsidbCustomActionTypeInstall:
4910 this.core.OnMessage(WixWarnings.NestedInstall(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1]));
4911 continue;
4912 default:
4913 this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1]));
4914 break;
4915 }
4916
4917 int extype = 4 < row.Fields.Length && null != row[4] ? Convert.ToInt32(row[4]) : 0;
4918 if (MsiInterop.MsidbCustomActionTypePatchUninstall == (extype & MsiInterop.MsidbCustomActionTypePatchUninstall))
4919 {
4920 customAction.PatchUninstall = Wix.YesNoType.yes;
4921 }
4922
4923 this.core.RootElement.AddChild(customAction);
4924 this.core.IndexElement(row, customAction);
4925 }
4926 }
4927
4928 /// <summary>
4929 /// Decompile the CompLocator table.
4930 /// </summary>
4931 /// <param name="table">The table to decompile.</param>
4932 private void DecompileCompLocatorTable(Table table)
4933 {
4934 foreach (Row row in table.Rows)
4935 {
4936 Wix.ComponentSearch componentSearch = new Wix.ComponentSearch();
4937
4938 componentSearch.Id = Convert.ToString(row[0]);
4939
4940 componentSearch.Guid = Convert.ToString(row[1]);
4941
4942 if (null != row[2])
4943 {
4944 switch (Convert.ToInt32(row[2]))
4945 {
4946 case MsiInterop.MsidbLocatorTypeDirectory:
4947 componentSearch.Type = Wix.ComponentSearch.TypeType.directory;
4948 break;
4949 case MsiInterop.MsidbLocatorTypeFileName:
4950 // this is the default value
4951 break;
4952 default:
4953 this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[2].Column.Name, row[2]));
4954 break;
4955 }
4956 }
4957
4958 this.core.IndexElement(row, componentSearch);
4959 }
4960 }
4961
4962 /// <summary>
4963 /// Decompile the Complus table.
4964 /// </summary>
4965 /// <param name="table">The table to decompile.</param>
4966 private void DecompileComplusTable(Table table)
4967 {
4968 foreach (Row row in table.Rows)
4969 {
4970 if (null != row[1])
4971 {
4972 Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[0]));
4973
4974 if (null != component)
4975 {
4976 component.ComPlusFlags = Convert.ToInt32(row[1]);
4977 }
4978 else
4979 {
4980 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[0]), "Component"));
4981 }
4982 }
4983 }
4984 }
4985
4986 /// <summary>
4987 /// Decompile the Component table.
4988 /// </summary>
4989 /// <param name="table">The table to decompile.</param>
4990 private void DecompileComponentTable(Table table)
4991 {
4992 foreach (Row row in table.Rows)
4993 {
4994 Wix.Component component = new Wix.Component();
4995
4996 component.Id = Convert.ToString(row[0]);
4997
4998 component.Guid = Convert.ToString(row[1]);
4999
5000 int attributes = Convert.ToInt32(row[3]);
5001
5002 if (MsiInterop.MsidbComponentAttributesSourceOnly == (attributes & MsiInterop.MsidbComponentAttributesSourceOnly))
5003 {
5004 component.Location = Wix.Component.LocationType.source;
5005 }
5006 else if (MsiInterop.MsidbComponentAttributesOptional == (attributes & MsiInterop.MsidbComponentAttributesOptional))
5007 {
5008 component.Location = Wix.Component.LocationType.either;
5009 }
5010
5011 if (MsiInterop.MsidbComponentAttributesSharedDllRefCount == (attributes & MsiInterop.MsidbComponentAttributesSharedDllRefCount))
5012 {
5013 component.SharedDllRefCount = Wix.YesNoType.yes;
5014 }
5015
5016 if (MsiInterop.MsidbComponentAttributesPermanent == (attributes & MsiInterop.MsidbComponentAttributesPermanent))
5017 {
5018 component.Permanent = Wix.YesNoType.yes;
5019 }
5020
5021 if (MsiInterop.MsidbComponentAttributesTransitive == (attributes & MsiInterop.MsidbComponentAttributesTransitive))
5022 {
5023 component.Transitive = Wix.YesNoType.yes;
5024 }
5025
5026 if (MsiInterop.MsidbComponentAttributesNeverOverwrite == (attributes & MsiInterop.MsidbComponentAttributesNeverOverwrite))
5027 {
5028 component.NeverOverwrite = Wix.YesNoType.yes;
5029 }
5030
5031 if (MsiInterop.MsidbComponentAttributes64bit == (attributes & MsiInterop.MsidbComponentAttributes64bit))
5032 {
5033 component.Win64 = Wix.YesNoType.yes;
5034 }
5035
5036 if (MsiInterop.MsidbComponentAttributesDisableRegistryReflection == (attributes & MsiInterop.MsidbComponentAttributesDisableRegistryReflection))
5037 {
5038 component.DisableRegistryReflection = Wix.YesNoType.yes;
5039 }
5040
5041 if (MsiInterop.MsidbComponentAttributesUninstallOnSupersedence == (attributes & MsiInterop.MsidbComponentAttributesUninstallOnSupersedence))
5042 {
5043 component.UninstallWhenSuperseded = Wix.YesNoType.yes;
5044 }
5045
5046 if (MsiInterop.MsidbComponentAttributesShared == (attributes & MsiInterop.MsidbComponentAttributesShared))
5047 {
5048 component.Shared = Wix.YesNoType.yes;
5049 }
5050
5051 if (null != row[4])
5052 {
5053 Wix.Condition condition = new Wix.Condition();
5054
5055 condition.Content = Convert.ToString(row[4]);
5056
5057 component.AddChild(condition);
5058 }
5059
5060 Wix.Directory directory = (Wix.Directory)this.core.GetIndexedElement("Directory", Convert.ToString(row[2]));
5061 if (null != directory)
5062 {
5063 directory.AddChild(component);
5064 }
5065 else
5066 {
5067 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Directory_", Convert.ToString(row[2]), "Directory"));
5068 }
5069 this.core.IndexElement(row, component);
5070 }
5071 }
5072
5073 /// <summary>
5074 /// Decompile the Condition table.
5075 /// </summary>
5076 /// <param name="table">The table to decompile.</param>
5077 private void DecompileConditionTable(Table table)
5078 {
5079 foreach (Row row in table.Rows)
5080 {
5081 Wix.Condition condition = new Wix.Condition();
5082
5083 condition.Level = Convert.ToInt32(row[1]);
5084
5085 if (null != row[2])
5086 {
5087 condition.Content = Convert.ToString(row[2]);
5088 }
5089
5090 Wix.Feature feature = (Wix.Feature)this.core.GetIndexedElement("Feature", Convert.ToString(row[0]));
5091 if (null != feature)
5092 {
5093 feature.AddChild(condition);
5094 }
5095 else
5096 {
5097 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Feature_", Convert.ToString(row[0]), "Feature"));
5098 }
5099 }
5100 }
5101
5102 /// <summary>
5103 /// Decompile the Dialog table.
5104 /// </summary>
5105 /// <param name="table">The table to decompile.</param>
5106 private void DecompileDialogTable(Table table)
5107 {
5108 foreach (Row row in table.Rows)
5109 {
5110 Wix.Dialog dialog = new Wix.Dialog();
5111
5112 dialog.Id = Convert.ToString(row[0]);
5113
5114 dialog.X = Convert.ToInt32(row[1]);
5115
5116 dialog.Y = Convert.ToInt32(row[2]);
5117
5118 dialog.Width = Convert.ToInt32(row[3]);
5119
5120 dialog.Height = Convert.ToInt32(row[4]);
5121
5122 if (null != row[5])
5123 {
5124 int attributes = Convert.ToInt32(row[5]);
5125
5126 if (0 == (attributes & MsiInterop.MsidbDialogAttributesVisible))
5127 {
5128 dialog.Hidden = Wix.YesNoType.yes;
5129 }
5130
5131 if (0 == (attributes & MsiInterop.MsidbDialogAttributesModal))
5132 {
5133 dialog.Modeless = Wix.YesNoType.yes;
5134 }
5135
5136 if (0 == (attributes & MsiInterop.MsidbDialogAttributesMinimize))
5137 {
5138 dialog.NoMinimize = Wix.YesNoType.yes;
5139 }
5140
5141 if (MsiInterop.MsidbDialogAttributesSysModal == (attributes & MsiInterop.MsidbDialogAttributesSysModal))
5142 {
5143 dialog.SystemModal = Wix.YesNoType.yes;
5144 }
5145
5146 if (MsiInterop.MsidbDialogAttributesKeepModeless == (attributes & MsiInterop.MsidbDialogAttributesKeepModeless))
5147 {
5148 dialog.KeepModeless = Wix.YesNoType.yes;
5149 }
5150
5151 if (MsiInterop.MsidbDialogAttributesTrackDiskSpace == (attributes & MsiInterop.MsidbDialogAttributesTrackDiskSpace))
5152 {
5153 dialog.TrackDiskSpace = Wix.YesNoType.yes;
5154 }
5155
5156 if (MsiInterop.MsidbDialogAttributesUseCustomPalette == (attributes & MsiInterop.MsidbDialogAttributesUseCustomPalette))
5157 {
5158 dialog.CustomPalette = Wix.YesNoType.yes;
5159 }
5160
5161 if (MsiInterop.MsidbDialogAttributesRTLRO == (attributes & MsiInterop.MsidbDialogAttributesRTLRO))
5162 {
5163 dialog.RightToLeft = Wix.YesNoType.yes;
5164 }
5165
5166 if (MsiInterop.MsidbDialogAttributesRightAligned == (attributes & MsiInterop.MsidbDialogAttributesRightAligned))
5167 {
5168 dialog.RightAligned = Wix.YesNoType.yes;
5169 }
5170
5171 if (MsiInterop.MsidbDialogAttributesLeftScroll == (attributes & MsiInterop.MsidbDialogAttributesLeftScroll))
5172 {
5173 dialog.LeftScroll = Wix.YesNoType.yes;
5174 }
5175
5176 if (MsiInterop.MsidbDialogAttributesError == (attributes & MsiInterop.MsidbDialogAttributesError))
5177 {
5178 dialog.ErrorDialog = Wix.YesNoType.yes;
5179 }
5180 }
5181
5182 if (null != row[6])
5183 {
5184 dialog.Title = Convert.ToString(row[6]);
5185 }
5186
5187 this.core.UIElement.AddChild(dialog);
5188 this.core.IndexElement(row, dialog);
5189 }
5190 }
5191
5192 /// <summary>
5193 /// Decompile the Directory table.
5194 /// </summary>
5195 /// <param name="table">The table to decompile.</param>
5196 private void DecompileDirectoryTable(Table table)
5197 {
5198 foreach (Row row in table.Rows)
5199 {
5200 Wix.Directory directory = new Wix.Directory();
5201
5202 directory.Id = Convert.ToString(row[0]);
5203
5204 string[] names = Installer.GetNames(Convert.ToString(row[2]));
5205
5206 if (String.Equals(directory.Id, "TARGETDIR", StringComparison.Ordinal) && !String.Equals(names[0], "SourceDir", StringComparison.Ordinal))
5207 {
5208 this.core.OnMessage(WixWarnings.TargetDirCorrectedDefaultDir());
5209 directory.Name = "SourceDir";
5210 }
5211 else
5212 {
5213 if (null != names[0] && "." != names[0])
5214 {
5215 if (null != names[1])
5216 {
5217 directory.ShortName = names[0];
5218 }
5219 else
5220 {
5221 directory.Name = names[0];
5222 }
5223 }
5224
5225 if (null != names[1])
5226 {
5227 directory.Name = names[1];
5228 }
5229 }
5230
5231 if (null != names[2])
5232 {
5233 if (null != names[3])
5234 {
5235 directory.ShortSourceName = names[2];
5236 }
5237 else
5238 {
5239 directory.SourceName = names[2];
5240 }
5241 }
5242
5243 if (null != names[3])
5244 {
5245 directory.SourceName = names[3];
5246 }
5247
5248 this.core.IndexElement(row, directory);
5249 }
5250
5251 // nest the directories
5252 foreach (Row row in table.Rows)
5253 {
5254 Wix.Directory directory = (Wix.Directory)this.core.GetIndexedElement(row);
5255
5256 if (null == row[1])
5257 {
5258 this.core.RootElement.AddChild(directory);
5259 }
5260 else
5261 {
5262 Wix.Directory parentDirectory = (Wix.Directory)this.core.GetIndexedElement("Directory", Convert.ToString(row[1]));
5263
5264 if (null == parentDirectory)
5265 {
5266 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Directory_Parent", Convert.ToString(row[1]), "Directory"));
5267 }
5268 else if (parentDirectory == directory) // another way to specify a root directory
5269 {
5270 this.core.RootElement.AddChild(directory);
5271 }
5272 else
5273 {
5274 parentDirectory.AddChild(directory);
5275 }
5276 }
5277 }
5278 }
5279
5280 /// <summary>
5281 /// Decompile the DrLocator table.
5282 /// </summary>
5283 /// <param name="table">The table to decompile.</param>
5284 private void DecompileDrLocatorTable(Table table)
5285 {
5286 foreach (Row row in table.Rows)
5287 {
5288 Wix.DirectorySearch directorySearch = new Wix.DirectorySearch();
5289
5290 directorySearch.Id = Convert.ToString(row[0]);
5291
5292 if (null != row[2])
5293 {
5294 directorySearch.Path = Convert.ToString(row[2]);
5295 }
5296
5297 if (null != row[3])
5298 {
5299 directorySearch.Depth = Convert.ToInt32(row[3]);
5300 }
5301
5302 this.core.IndexElement(row, directorySearch);
5303 }
5304 }
5305
5306 /// <summary>
5307 /// Decompile the DuplicateFile table.
5308 /// </summary>
5309 /// <param name="table">The table to decompile.</param>
5310 private void DecompileDuplicateFileTable(Table table)
5311 {
5312 foreach (Row row in table.Rows)
5313 {
5314 Wix.CopyFile copyFile = new Wix.CopyFile();
5315
5316 copyFile.Id = Convert.ToString(row[0]);
5317
5318 copyFile.FileId = Convert.ToString(row[2]);
5319
5320 if (null != row[3])
5321 {
5322 string[] names = Installer.GetNames(Convert.ToString(row[3]));
5323 if (null != names[0] && null != names[1])
5324 {
5325 copyFile.DestinationShortName = names[0];
5326 copyFile.DestinationName = names[1];
5327 }
5328 else if (null != names[0])
5329 {
5330 copyFile.DestinationName = names[0];
5331 }
5332 }
5333
5334 // destination directory/property is set in FinalizeDuplicateMoveFileTables
5335
5336 Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1]));
5337 if (null != component)
5338 {
5339 component.AddChild(copyFile);
5340 }
5341 else
5342 {
5343 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component"));
5344 }
5345 this.core.IndexElement(row, copyFile);
5346 }
5347 }
5348
5349 /// <summary>
5350 /// Decompile the Environment table.
5351 /// </summary>
5352 /// <param name="table">The table to decompile.</param>
5353 private void DecompileEnvironmentTable(Table table)
5354 {
5355 foreach (Row row in table.Rows)
5356 {
5357 Wix.Environment environment = new Wix.Environment();
5358
5359 environment.Id = Convert.ToString(row[0]);
5360
5361 bool done = false;
5362 bool permanent = true;
5363 string name = Convert.ToString(row[1]);
5364 for (int i = 0; i < name.Length && !done; i++)
5365 {
5366 switch (name[i])
5367 {
5368 case '=':
5369 environment.Action = Wix.Environment.ActionType.set;
5370 break;
5371 case '+':
5372 environment.Action = Wix.Environment.ActionType.create;
5373 break;
5374 case '-':
5375 permanent = false;
5376 break;
5377 case '!':
5378 environment.Action = Wix.Environment.ActionType.remove;
5379 break;
5380 case '*':
5381 environment.System = Wix.YesNoType.yes;
5382 break;
5383 default:
5384 environment.Name = name.Substring(i);
5385 done = true;
5386 break;
5387 }
5388 }
5389
5390 if (permanent)
5391 {
5392 environment.Permanent = Wix.YesNoType.yes;
5393 }
5394
5395 if (null != row[2])
5396 {
5397 string value = Convert.ToString(row[2]);
5398
5399 if (value.StartsWith("[~]", StringComparison.Ordinal))
5400 {
5401 environment.Part = Wix.Environment.PartType.last;
5402
5403 if (3 < value.Length)
5404 {
5405 environment.Separator = value.Substring(3, 1);
5406 environment.Value = value.Substring(4);
5407 }
5408 }
5409 else if (value.EndsWith("[~]", StringComparison.Ordinal))
5410 {
5411 environment.Part = Wix.Environment.PartType.first;
5412
5413 if (3 < value.Length)
5414 {
5415 environment.Separator = value.Substring(value.Length - 4, 1);
5416 environment.Value = value.Substring(0, value.Length - 4);
5417 }
5418 }
5419 else
5420 {
5421 environment.Value = value;
5422 }
5423 }
5424
5425 Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[3]));
5426 if (null != component)
5427 {
5428 component.AddChild(environment);
5429 }
5430 else
5431 {
5432 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[3]), "Component"));
5433 }
5434 }
5435 }
5436
5437 /// <summary>
5438 /// Decompile the Error table.
5439 /// </summary>
5440 /// <param name="table">The table to decompile.</param>
5441 private void DecompileErrorTable(Table table)
5442 {
5443 foreach (Row row in table.Rows)
5444 {
5445 Wix.Error error = new Wix.Error();
5446
5447 error.Id = Convert.ToInt32(row[0]);
5448
5449 error.Content = Convert.ToString(row[1]);
5450
5451 this.core.UIElement.AddChild(error);
5452 }
5453 }
5454
5455 /// <summary>
5456 /// Decompile the EventMapping table.
5457 /// </summary>
5458 /// <param name="table">The table to decompile.</param>
5459 private void DecompileEventMappingTable(Table table)
5460 {
5461 foreach (Row row in table.Rows)
5462 {
5463 Wix.Subscribe subscribe = new Wix.Subscribe();
5464
5465 subscribe.Event = Convert.ToString(row[2]);
5466
5467 subscribe.Attribute = Convert.ToString(row[3]);
5468
5469 Wix.Control control = (Wix.Control)this.core.GetIndexedElement("Control", Convert.ToString(row[0]), Convert.ToString(row[1]));
5470 if (null != control)
5471 {
5472 control.AddChild(subscribe);
5473 }
5474 else
5475 {
5476 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", Convert.ToString(row[0]), "Control_", Convert.ToString(row[1]), "Control"));
5477 }
5478 }
5479 }
5480
5481 /// <summary>
5482 /// Decompile the Extension table.
5483 /// </summary>
5484 /// <param name="table">The table to decompile.</param>
5485 private void DecompileExtensionTable(Table table)
5486 {
5487 foreach (Row row in table.Rows)
5488 {
5489 Wix.Extension extension = new Wix.Extension();
5490
5491 extension.Advertise = Wix.YesNoType.yes;
5492
5493 extension.Id = Convert.ToString(row[0]);
5494
5495 if (null != row[3])
5496 {
5497 Wix.MIME mime = (Wix.MIME)this.core.GetIndexedElement("MIME", Convert.ToString(row[3]));
5498
5499 if (null != mime)
5500 {
5501 mime.Default = Wix.YesNoType.yes;
5502 }
5503 else
5504 {
5505 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "MIME_", Convert.ToString(row[3]), "MIME"));
5506 }
5507 }
5508
5509 if (null != row[2])
5510 {
5511 Wix.ProgId progId = (Wix.ProgId)this.core.GetIndexedElement("ProgId", Convert.ToString(row[2]));
5512
5513 if (null != progId)
5514 {
5515 progId.AddChild(extension);
5516 }
5517 else
5518 {
5519 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "ProgId_", Convert.ToString(row[2]), "ProgId"));
5520 }
5521 }
5522 else
5523 {
5524 Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1]));
5525
5526 if (null != component)
5527 {
5528 component.AddChild(extension);
5529 }
5530 else
5531 {
5532 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component"));
5533 }
5534 }
5535
5536 this.core.IndexElement(row, extension);
5537 }
5538 }
5539
5540 /// <summary>
5541 /// Decompile the ExternalFiles table.
5542 /// </summary>
5543 /// <param name="table">The table to decompile.</param>
5544 private void DecompileExternalFilesTable(Table table)
5545 {
5546 foreach (Row row in table.Rows)
5547 {
5548 Wix.ExternalFile externalFile = new Wix.ExternalFile();
5549
5550 externalFile.File = Convert.ToString(row[1]);
5551
5552 externalFile.Source = Convert.ToString(row[2]);
5553
5554 if (null != row[3])
5555 {
5556 string[] symbolPaths = (Convert.ToString(row[3])).Split(';');
5557
5558 foreach (string symbolPathString in symbolPaths)
5559 {
5560 Wix.SymbolPath symbolPath = new Wix.SymbolPath();
5561
5562 symbolPath.Path = symbolPathString;
5563
5564 externalFile.AddChild(symbolPath);
5565 }
5566 }
5567
5568 if (null != row[4] && null != row[5])
5569 {
5570 string[] ignoreOffsets = (Convert.ToString(row[4])).Split(',');
5571 string[] ignoreLengths = (Convert.ToString(row[5])).Split(',');
5572
5573 if (ignoreOffsets.Length == ignoreLengths.Length)
5574 {
5575 for (int i = 0; i < ignoreOffsets.Length; i++)
5576 {
5577 Wix.IgnoreRange ignoreRange = new Wix.IgnoreRange();
5578
5579 if (ignoreOffsets[i].StartsWith("0x", StringComparison.Ordinal))
5580 {
5581 ignoreRange.Offset = Convert.ToInt32(ignoreOffsets[i].Substring(2), 16);
5582 }
5583 else
5584 {
5585 ignoreRange.Offset = Convert.ToInt32(ignoreOffsets[i], CultureInfo.InvariantCulture);
5586 }
5587
5588 if (ignoreLengths[i].StartsWith("0x", StringComparison.Ordinal))
5589 {
5590 ignoreRange.Length = Convert.ToInt32(ignoreLengths[i].Substring(2), 16);
5591 }
5592 else
5593 {
5594 ignoreRange.Length = Convert.ToInt32(ignoreLengths[i], CultureInfo.InvariantCulture);
5595 }
5596
5597 externalFile.AddChild(ignoreRange);
5598 }
5599 }
5600 else
5601 {
5602 // TODO: warn
5603 }
5604 }
5605 else if (null != row[4] || null != row[5])
5606 {
5607 // TODO: warn about mismatch between columns
5608 }
5609
5610 // the RetainOffsets column is handled in FinalizeFamilyFileRangesTable
5611
5612 if (null != row[7])
5613 {
5614 externalFile.Order = Convert.ToInt32(row[7]);
5615 }
5616
5617 Wix.Family family = (Wix.Family)this.core.GetIndexedElement("ImageFamilies", Convert.ToString(row[0]));
5618 if (null != family)
5619 {
5620 family.AddChild(externalFile);
5621 }
5622 else
5623 {
5624 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Family", Convert.ToString(row[0]), "ImageFamilies"));
5625 }
5626 this.core.IndexElement(row, externalFile);
5627 }
5628 }
5629
5630 /// <summary>
5631 /// Decompile the Feature table.
5632 /// </summary>
5633 /// <param name="table">The table to decompile.</param>
5634 private void DecompileFeatureTable(Table table)
5635 {
5636 SortedList sortedFeatures = new SortedList();
5637
5638 foreach (Row row in table.Rows)
5639 {
5640 Wix.Feature feature = new Wix.Feature();
5641
5642 feature.Id = Convert.ToString(row[0]);
5643
5644 if (null != row[2])
5645 {
5646 feature.Title = Convert.ToString(row[2]);
5647 }
5648
5649 if (null != row[3])
5650 {
5651 feature.Description = Convert.ToString(row[3]);
5652 }
5653
5654 if (null == row[4])
5655 {
5656 feature.Display = "hidden";
5657 }
5658 else
5659 {
5660 int display = Convert.ToInt32(row[4]);
5661
5662 if (0 == display)
5663 {
5664 feature.Display = "hidden";
5665 }
5666 else if (1 == display % 2)
5667 {
5668 feature.Display = "expand";
5669 }
5670 }
5671
5672 feature.Level = Convert.ToInt32(row[5]);
5673
5674 if (null != row[6])
5675 {
5676 feature.ConfigurableDirectory = Convert.ToString(row[6]);
5677 }
5678
5679 int attributes = Convert.ToInt32(row[7]);
5680
5681 if (MsiInterop.MsidbFeatureAttributesFavorSource == (attributes & MsiInterop.MsidbFeatureAttributesFavorSource) && MsiInterop.MsidbFeatureAttributesFollowParent == (attributes & MsiInterop.MsidbFeatureAttributesFollowParent))
5682 {
5683 // TODO: display a warning for setting favor local and follow parent together
5684 }
5685 else if (MsiInterop.MsidbFeatureAttributesFavorSource == (attributes & MsiInterop.MsidbFeatureAttributesFavorSource))
5686 {
5687 feature.InstallDefault = Wix.Feature.InstallDefaultType.source;
5688 }
5689 else if (MsiInterop.MsidbFeatureAttributesFollowParent == (attributes & MsiInterop.MsidbFeatureAttributesFollowParent))
5690 {
5691 feature.InstallDefault = Wix.Feature.InstallDefaultType.followParent;
5692 }
5693
5694 if (MsiInterop.MsidbFeatureAttributesFavorAdvertise == (attributes & MsiInterop.MsidbFeatureAttributesFavorAdvertise))
5695 {
5696 feature.TypicalDefault = Wix.Feature.TypicalDefaultType.advertise;
5697 }
5698
5699 if (MsiInterop.MsidbFeatureAttributesDisallowAdvertise == (attributes & MsiInterop.MsidbFeatureAttributesDisallowAdvertise) &&
5700 MsiInterop.MsidbFeatureAttributesNoUnsupportedAdvertise == (attributes & MsiInterop.MsidbFeatureAttributesNoUnsupportedAdvertise))
5701 {
5702 this.core.OnMessage(WixWarnings.InvalidAttributeCombination(row.SourceLineNumbers, "msidbFeatureAttributesDisallowAdvertise", "msidbFeatureAttributesNoUnsupportedAdvertise", "Feature.AllowAdvertiseType", "no"));
5703 feature.AllowAdvertise = Wix.Feature.AllowAdvertiseType.no;
5704 }
5705 else if (MsiInterop.MsidbFeatureAttributesDisallowAdvertise == (attributes & MsiInterop.MsidbFeatureAttributesDisallowAdvertise))
5706 {
5707 feature.AllowAdvertise = Wix.Feature.AllowAdvertiseType.no;
5708 }
5709 else if (MsiInterop.MsidbFeatureAttributesNoUnsupportedAdvertise == (attributes & MsiInterop.MsidbFeatureAttributesNoUnsupportedAdvertise))
5710 {
5711 feature.AllowAdvertise = Wix.Feature.AllowAdvertiseType.system;
5712 }
5713
5714 if (MsiInterop.MsidbFeatureAttributesUIDisallowAbsent == (attributes & MsiInterop.MsidbFeatureAttributesUIDisallowAbsent))
5715 {
5716 feature.Absent = Wix.Feature.AbsentType.disallow;
5717 }
5718
5719 this.core.IndexElement(row, feature);
5720
5721 // sort the features by their display column (and append the identifier to ensure unique keys)
5722 sortedFeatures.Add(String.Format(CultureInfo.InvariantCulture, "{0:00000}|{1}", Convert.ToInt32(row[4], CultureInfo.InvariantCulture), row[0]), row);
5723 }
5724
5725 // nest the features
5726 foreach (Row row in sortedFeatures.Values)
5727 {
5728 Wix.Feature feature = (Wix.Feature)this.core.GetIndexedElement("Feature", Convert.ToString(row[0]));
5729
5730 if (null == row[1])
5731 {
5732 this.core.RootElement.AddChild(feature);
5733 }
5734 else
5735 {
5736 Wix.Feature parentFeature = (Wix.Feature)this.core.GetIndexedElement("Feature", Convert.ToString(row[1]));
5737
5738 if (null == parentFeature)
5739 {
5740 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Feature_Parent", Convert.ToString(row[1]), "Feature"));
5741 }
5742 else if (parentFeature == feature)
5743 {
5744 // TODO: display a warning about self-nesting
5745 }
5746 else
5747 {
5748 parentFeature.AddChild(feature);
5749 }
5750 }
5751 }
5752 }
5753
5754 /// <summary>
5755 /// Decompile the FeatureComponents table.
5756 /// </summary>
5757 /// <param name="table">The table to decompile.</param>
5758 private void DecompileFeatureComponentsTable(Table table)
5759 {
5760 foreach (Row row in table.Rows)
5761 {
5762 Wix.ComponentRef componentRef = new Wix.ComponentRef();
5763
5764 componentRef.Id = Convert.ToString(row[1]);
5765
5766 Wix.Feature parentFeature = (Wix.Feature)this.core.GetIndexedElement("Feature", Convert.ToString(row[0]));
5767 if (null != parentFeature)
5768 {
5769 parentFeature.AddChild(componentRef);
5770 }
5771 else
5772 {
5773 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Feature_", Convert.ToString(row[0]), "Feature"));
5774 }
5775 this.core.IndexElement(row, componentRef);
5776 }
5777 }
5778
5779 /// <summary>
5780 /// Decompile the File table.
5781 /// </summary>
5782 /// <param name="table">The table to decompile.</param>
5783 private void DecompileFileTable(Table table)
5784 {
5785 foreach (FileRow fileRow in table.Rows)
5786 {
5787 Wix.File file = new Wix.File();
5788
5789 file.Id = fileRow.File;
5790
5791 string[] names = Installer.GetNames(fileRow.FileName);
5792 if (null != names[0] && null != names[1])
5793 {
5794 file.ShortName = names[0];
5795 file.Name = names[1];
5796 }
5797 else if (null != names[0])
5798 {
5799 file.Name = names[0];
5800 }
5801
5802 if (null != fileRow.Version && 0 < fileRow.Version.Length)
5803 {
5804 if (!Char.IsDigit(fileRow.Version[0]))
5805 {
5806 file.CompanionFile = fileRow.Version;
5807 }
5808 }
5809
5810 if (MsiInterop.MsidbFileAttributesReadOnly == (fileRow.Attributes & MsiInterop.MsidbFileAttributesReadOnly))
5811 {
5812 file.ReadOnly = Wix.YesNoType.yes;
5813 }
5814
5815 if (MsiInterop.MsidbFileAttributesHidden == (fileRow.Attributes & MsiInterop.MsidbFileAttributesHidden))
5816 {
5817 file.Hidden = Wix.YesNoType.yes;
5818 }
5819
5820 if (MsiInterop.MsidbFileAttributesSystem == (fileRow.Attributes & MsiInterop.MsidbFileAttributesSystem))
5821 {
5822 file.System = Wix.YesNoType.yes;
5823 }
5824
5825 if (MsiInterop.MsidbFileAttributesVital != (fileRow.Attributes & MsiInterop.MsidbFileAttributesVital))
5826 {
5827 file.Vital = Wix.YesNoType.no;
5828 }
5829
5830 if (MsiInterop.MsidbFileAttributesChecksum == (fileRow.Attributes & MsiInterop.MsidbFileAttributesChecksum))
5831 {
5832 file.Checksum = Wix.YesNoType.yes;
5833 }
5834
5835 if (MsiInterop.MsidbFileAttributesNoncompressed == (fileRow.Attributes & MsiInterop.MsidbFileAttributesNoncompressed) &&
5836 MsiInterop.MsidbFileAttributesCompressed == (fileRow.Attributes & MsiInterop.MsidbFileAttributesCompressed))
5837 {
5838 // TODO: error
5839 }
5840 else if (MsiInterop.MsidbFileAttributesNoncompressed == (fileRow.Attributes & MsiInterop.MsidbFileAttributesNoncompressed))
5841 {
5842 file.Compressed = Wix.YesNoDefaultType.no;
5843 }
5844 else if (MsiInterop.MsidbFileAttributesCompressed == (fileRow.Attributes & MsiInterop.MsidbFileAttributesCompressed))
5845 {
5846 file.Compressed = Wix.YesNoDefaultType.yes;
5847 }
5848
5849 this.core.IndexElement(fileRow, file);
5850 }
5851 }
5852
5853 /// <summary>
5854 /// Decompile the FileSFPCatalog table.
5855 /// </summary>
5856 /// <param name="table">The table to decompile.</param>
5857 private void DecompileFileSFPCatalogTable(Table table)
5858 {
5859 foreach (Row row in table.Rows)
5860 {
5861 Wix.SFPFile sfpFile = new Wix.SFPFile();
5862
5863 sfpFile.Id = Convert.ToString(row[0]);
5864
5865 Wix.SFPCatalog sfpCatalog = (Wix.SFPCatalog)this.core.GetIndexedElement("SFPCatalog", Convert.ToString(row[1]));
5866 if (null != sfpCatalog)
5867 {
5868 sfpCatalog.AddChild(sfpFile);
5869 }
5870 else
5871 {
5872 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "SFPCatalog_", Convert.ToString(row[1]), "SFPCatalog"));
5873 }
5874 }
5875 }
5876
5877 /// <summary>
5878 /// Decompile the Font table.
5879 /// </summary>
5880 /// <param name="table">The table to decompile.</param>
5881 private void DecompileFontTable(Table table)
5882 {
5883 foreach (Row row in table.Rows)
5884 {
5885 Wix.File file = (Wix.File)this.core.GetIndexedElement("File", Convert.ToString(row[0]));
5886
5887 if (null != file)
5888 {
5889 if (null != row[1])
5890 {
5891 file.FontTitle = Convert.ToString(row[1]);
5892 }
5893 else
5894 {
5895 file.TrueType = Wix.YesNoType.yes;
5896 }
5897 }
5898 else
5899 {
5900 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "File_", Convert.ToString(row[0]), "File"));
5901 }
5902 }
5903 }
5904
5905 /// <summary>
5906 /// Decompile the Icon table.
5907 /// </summary>
5908 /// <param name="table">The table to decompile.</param>
5909 private void DecompileIconTable(Table table)
5910 {
5911 foreach (Row row in table.Rows)
5912 {
5913 Wix.Icon icon = new Wix.Icon();
5914
5915 icon.Id = Convert.ToString(row[0]);
5916
5917 icon.SourceFile = Convert.ToString(row[1]);
5918
5919 this.core.RootElement.AddChild(icon);
5920 }
5921 }
5922
5923 /// <summary>
5924 /// Decompile the ImageFamilies table.
5925 /// </summary>
5926 /// <param name="table">The table to decompile.</param>
5927 private void DecompileImageFamiliesTable(Table table)
5928 {
5929 foreach (Row row in table.Rows)
5930 {
5931 Wix.Family family = new Wix.Family();
5932
5933 family.Name = Convert.ToString(row[0]);
5934
5935 if (null != row[1])
5936 {
5937 family.MediaSrcProp = Convert.ToString(row[1]);
5938 }
5939
5940 if (null != row[2])
5941 {
5942 family.DiskId = Convert.ToString(Convert.ToInt32(row[2]));
5943 }
5944
5945 if (null != row[3])
5946 {
5947 family.SequenceStart = Convert.ToInt32(row[3]);
5948 }
5949
5950 if (null != row[4])
5951 {
5952 family.DiskPrompt = Convert.ToString(row[4]);
5953 }
5954
5955 if (null != row[5])
5956 {
5957 family.VolumeLabel = Convert.ToString(row[5]);
5958 }
5959
5960 this.core.RootElement.AddChild(family);
5961 this.core.IndexElement(row, family);
5962 }
5963 }
5964
5965 /// <summary>
5966 /// Decompile the IniFile table.
5967 /// </summary>
5968 /// <param name="table">The table to decompile.</param>
5969 private void DecompileIniFileTable(Table table)
5970 {
5971 foreach (Row row in table.Rows)
5972 {
5973 Wix.IniFile iniFile = new Wix.IniFile();
5974
5975 iniFile.Id = Convert.ToString(row[0]);
5976
5977 string[] names = Installer.GetNames(Convert.ToString(row[1]));
5978
5979 if (null != names[0])
5980 {
5981 if (null == names[1])
5982 {
5983 iniFile.Name = names[0];
5984 }
5985 else
5986 {
5987 iniFile.ShortName = names[0];
5988 }
5989 }
5990
5991 if (null != names[1])
5992 {
5993 iniFile.Name = names[1];
5994 }
5995
5996 if (null != row[2])
5997 {
5998 iniFile.Directory = Convert.ToString(row[2]);
5999 }
6000
6001 iniFile.Section = Convert.ToString(row[3]);
6002
6003 iniFile.Key = Convert.ToString(row[4]);
6004
6005 iniFile.Value = Convert.ToString(row[5]);
6006
6007 switch (Convert.ToInt32(row[6]))
6008 {
6009 case MsiInterop.MsidbIniFileActionAddLine:
6010 iniFile.Action = Wix.IniFile.ActionType.addLine;
6011 break;
6012 case MsiInterop.MsidbIniFileActionCreateLine:
6013 iniFile.Action = Wix.IniFile.ActionType.createLine;
6014 break;
6015 case MsiInterop.MsidbIniFileActionAddTag:
6016 iniFile.Action = Wix.IniFile.ActionType.addTag;
6017 break;
6018 default:
6019 this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6]));
6020 break;
6021 }
6022
6023 Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[7]));
6024 if (null != component)
6025 {
6026 component.AddChild(iniFile);
6027 }
6028 else
6029 {
6030 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[7]), "Component"));
6031 }
6032 }
6033 }
6034
6035 /// <summary>
6036 /// Decompile the IniLocator table.
6037 /// </summary>
6038 /// <param name="table">The table to decompile.</param>
6039 private void DecompileIniLocatorTable(Table table)
6040 {
6041 foreach (Row row in table.Rows)
6042 {
6043 Wix.IniFileSearch iniFileSearch = new Wix.IniFileSearch();
6044
6045 iniFileSearch.Id = Convert.ToString(row[0]);
6046
6047 string[] names = Installer.GetNames(Convert.ToString(row[1]));
6048 if (null != names[0] && null != names[1])
6049 {
6050 iniFileSearch.ShortName = names[0];
6051 iniFileSearch.Name = names[1];
6052 }
6053 else if (null != names[0])
6054 {
6055 iniFileSearch.Name = names[0];
6056 }
6057
6058 iniFileSearch.Section = Convert.ToString(row[2]);
6059
6060 iniFileSearch.Key = Convert.ToString(row[3]);
6061
6062 if (null != row[4])
6063 {
6064 int field = Convert.ToInt32(row[4]);
6065
6066 if (0 != field)
6067 {
6068 iniFileSearch.Field = field;
6069 }
6070 }
6071
6072 if (null != row[5])
6073 {
6074 switch (Convert.ToInt32(row[5]))
6075 {
6076 case MsiInterop.MsidbLocatorTypeDirectory:
6077 iniFileSearch.Type = Wix.IniFileSearch.TypeType.directory;
6078 break;
6079 case MsiInterop.MsidbLocatorTypeFileName:
6080 // this is the default value
6081 break;
6082 case MsiInterop.MsidbLocatorTypeRawValue:
6083 iniFileSearch.Type = Wix.IniFileSearch.TypeType.raw;
6084 break;
6085 default:
6086 this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[5].Column.Name, row[5]));
6087 break;
6088 }
6089 }
6090
6091 this.core.IndexElement(row, iniFileSearch);
6092 }
6093 }
6094
6095 /// <summary>
6096 /// Decompile the IsolatedComponent table.
6097 /// </summary>
6098 /// <param name="table">The table to decompile.</param>
6099 private void DecompileIsolatedComponentTable(Table table)
6100 {
6101 foreach (Row row in table.Rows)
6102 {
6103 Wix.IsolateComponent isolateComponent = new Wix.IsolateComponent();
6104
6105 isolateComponent.Shared = Convert.ToString(row[0]);
6106
6107 Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1]));
6108 if (null != component)
6109 {
6110 component.AddChild(isolateComponent);
6111 }
6112 else
6113 {
6114 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component"));
6115 }
6116 }
6117 }
6118
6119 /// <summary>
6120 /// Decompile the LaunchCondition table.
6121 /// </summary>
6122 /// <param name="table">The table to decompile.</param>
6123 private void DecompileLaunchConditionTable(Table table)
6124 {
6125 foreach (Row row in table.Rows)
6126 {
6127 if (Compiler.DowngradePreventedCondition == Convert.ToString(row[0]) || Compiler.UpgradePreventedCondition == Convert.ToString(row[0]))
6128 {
6129 continue; // MajorUpgrade rows processed in FinalizeUpgradeTable
6130 }
6131
6132 Wix.Condition condition = new Wix.Condition();
6133
6134 condition.Content = Convert.ToString(row[0]);
6135
6136 condition.Message = Convert.ToString(row[1]);
6137
6138 this.core.RootElement.AddChild(condition);
6139 }
6140 }
6141
6142 /// <summary>
6143 /// Decompile the ListBox table.
6144 /// </summary>
6145 /// <param name="table">The table to decompile.</param>
6146 private void DecompileListBoxTable(Table table)
6147 {
6148 Wix.ListBox listBox = null;
6149 SortedList listBoxRows = new SortedList();
6150
6151 // sort the list boxes by their property and order
6152 foreach (Row row in table.Rows)
6153 {
6154 listBoxRows.Add(String.Concat("{0}|{1:0000000000}", row[0], row[1]), row);
6155 }
6156
6157 foreach (Row row in listBoxRows.Values)
6158 {
6159 if (null == listBox || Convert.ToString(row[0]) != listBox.Property)
6160 {
6161 listBox = new Wix.ListBox();
6162
6163 listBox.Property = Convert.ToString(row[0]);
6164
6165 this.core.UIElement.AddChild(listBox);
6166 }
6167
6168 Wix.ListItem listItem = new Wix.ListItem();
6169
6170 listItem.Value = Convert.ToString(row[2]);
6171
6172 if (null != row[3])
6173 {
6174 listItem.Text = Convert.ToString(row[3]);
6175 }
6176
6177 listBox.AddChild(listItem);
6178 }
6179 }
6180
6181 /// <summary>
6182 /// Decompile the ListView table.
6183 /// </summary>
6184 /// <param name="table">The table to decompile.</param>
6185 private void DecompileListViewTable(Table table)
6186 {
6187 Wix.ListView listView = null;
6188 SortedList listViewRows = new SortedList();
6189
6190 // sort the list views by their property and order
6191 foreach (Row row in table.Rows)
6192 {
6193 listViewRows.Add(String.Concat("{0}|{1:0000000000}", row[0], row[1]), row);
6194 }
6195
6196 foreach (Row row in listViewRows.Values)
6197 {
6198 if (null == listView || Convert.ToString(row[0]) != listView.Property)
6199 {
6200 listView = new Wix.ListView();
6201
6202 listView.Property = Convert.ToString(row[0]);
6203
6204 this.core.UIElement.AddChild(listView);
6205 }
6206
6207 Wix.ListItem listItem = new Wix.ListItem();
6208
6209 listItem.Value = Convert.ToString(row[2]);
6210
6211 if (null != row[3])
6212 {
6213 listItem.Text = Convert.ToString(row[3]);
6214 }
6215
6216 if (null != row[4])
6217 {
6218 listItem.Icon = Convert.ToString(row[4]);
6219 }
6220
6221 listView.AddChild(listItem);
6222 }
6223 }
6224
6225 /// <summary>
6226 /// Decompile the LockPermissions table.
6227 /// </summary>
6228 /// <param name="table">The table to decompile.</param>
6229 private void DecompileLockPermissionsTable(Table table)
6230 {
6231 foreach (Row row in table.Rows)
6232 {
6233 Wix.Permission permission = new Wix.Permission();
6234 string[] specialPermissions;
6235
6236 switch (Convert.ToString(row[1]))
6237 {
6238 case "CreateFolder":
6239 specialPermissions = Common.FolderPermissions;
6240 break;
6241 case "File":
6242 specialPermissions = Common.FilePermissions;
6243 break;
6244 case "Registry":
6245 specialPermissions = Common.RegistryPermissions;
6246 break;
6247 default:
6248 this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, row.Table.Name, row.Fields[1].Column.Name, row[1]));
6249 return;
6250 }
6251
6252 int permissionBits = Convert.ToInt32(row[4]);
6253 for (int i = 0; i < 32; i++)
6254 {
6255 if (0 != ((permissionBits >> i) & 1))
6256 {
6257 string name = null;
6258
6259 if (specialPermissions.Length > i)
6260 {
6261 name = specialPermissions[i];
6262 }
6263 else if (16 > i && specialPermissions.Length <= i)
6264 {
6265 name = "SpecificRightsAll";
6266 }
6267 else if (28 > i && Common.StandardPermissions.Length > (i - 16))
6268 {
6269 name = Common.StandardPermissions[i - 16];
6270 }
6271 else if (0 <= (i - 28) && Common.GenericPermissions.Length > (i - 28))
6272 {
6273 name = Common.GenericPermissions[i - 28];
6274 }
6275
6276 if (null == name)
6277 {
6278 this.core.OnMessage(WixWarnings.UnknownPermission(row.SourceLineNumbers, row.Table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), i));
6279 }
6280 else
6281 {
6282 switch (name)
6283 {
6284 case "Append":
6285 permission.Append = Wix.YesNoType.yes;
6286 break;
6287 case "ChangePermission":
6288 permission.ChangePermission = Wix.YesNoType.yes;
6289 break;
6290 case "CreateChild":
6291 permission.CreateChild = Wix.YesNoType.yes;
6292 break;
6293 case "CreateFile":
6294 permission.CreateFile = Wix.YesNoType.yes;
6295 break;
6296 case "CreateLink":
6297 permission.CreateLink = Wix.YesNoType.yes;
6298 break;
6299 case "CreateSubkeys":
6300 permission.CreateSubkeys = Wix.YesNoType.yes;
6301 break;
6302 case "Delete":
6303 permission.Delete = Wix.YesNoType.yes;
6304 break;
6305 case "DeleteChild":
6306 permission.DeleteChild = Wix.YesNoType.yes;
6307 break;
6308 case "EnumerateSubkeys":
6309 permission.EnumerateSubkeys = Wix.YesNoType.yes;
6310 break;
6311 case "Execute":
6312 permission.Execute = Wix.YesNoType.yes;
6313 break;
6314 case "FileAllRights":
6315 permission.FileAllRights = Wix.YesNoType.yes;
6316 break;
6317 case "GenericAll":
6318 permission.GenericAll = Wix.YesNoType.yes;
6319 break;
6320 case "GenericExecute":
6321 permission.GenericExecute = Wix.YesNoType.yes;
6322 break;
6323 case "GenericRead":
6324 permission.GenericRead = Wix.YesNoType.yes;
6325 break;
6326 case "GenericWrite":
6327 permission.GenericWrite = Wix.YesNoType.yes;
6328 break;
6329 case "Notify":
6330 permission.Notify = Wix.YesNoType.yes;
6331 break;
6332 case "Read":
6333 permission.Read = Wix.YesNoType.yes;
6334 break;
6335 case "ReadAttributes":
6336 permission.ReadAttributes = Wix.YesNoType.yes;
6337 break;
6338 case "ReadExtendedAttributes":
6339 permission.ReadExtendedAttributes = Wix.YesNoType.yes;
6340 break;
6341 case "ReadPermission":
6342 permission.ReadPermission = Wix.YesNoType.yes;
6343 break;
6344 case "SpecificRightsAll":
6345 permission.SpecificRightsAll = Wix.YesNoType.yes;
6346 break;
6347 case "Synchronize":
6348 permission.Synchronize = Wix.YesNoType.yes;
6349 break;
6350 case "TakeOwnership":
6351 permission.TakeOwnership = Wix.YesNoType.yes;
6352 break;
6353 case "Traverse":
6354 permission.Traverse = Wix.YesNoType.yes;
6355 break;
6356 case "Write":
6357 permission.Write = Wix.YesNoType.yes;
6358 break;
6359 case "WriteAttributes":
6360 permission.WriteAttributes = Wix.YesNoType.yes;
6361 break;
6362 case "WriteExtendedAttributes":
6363 permission.WriteExtendedAttributes = Wix.YesNoType.yes;
6364 break;
6365 default:
6366 throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnknownPermissionAttribute, name));
6367 }
6368 }
6369 }
6370 }
6371
6372 if (null != row[2])
6373 {
6374 permission.Domain = Convert.ToString(row[2]);
6375 }
6376
6377 permission.User = Convert.ToString(row[3]);
6378
6379 this.core.IndexElement(row, permission);
6380 }
6381 }
6382
6383 /// <summary>
6384 /// Decompile the Media table.
6385 /// </summary>
6386 /// <param name="table">The table to decompile.</param>
6387 private void DecompileMediaTable(Table table)
6388 {
6389 foreach (MediaRow mediaRow in table.Rows)
6390 {
6391 Wix.Media media = new Wix.Media();
6392
6393 media.Id = Convert.ToString(mediaRow.DiskId);
6394
6395 if (null != mediaRow.DiskPrompt)
6396 {
6397 media.DiskPrompt = mediaRow.DiskPrompt;
6398 }
6399
6400 if (null != mediaRow.Cabinet)
6401 {
6402 string cabinet = mediaRow.Cabinet;
6403
6404 if (cabinet.StartsWith("#", StringComparison.Ordinal))
6405 {
6406 media.EmbedCab = Wix.YesNoType.yes;
6407 cabinet = cabinet.Substring(1);
6408 }
6409
6410 media.Cabinet = cabinet;
6411 }
6412
6413 if (null != mediaRow.VolumeLabel)
6414 {
6415 media.VolumeLabel = mediaRow.VolumeLabel;
6416 }
6417
6418 this.core.RootElement.AddChild(media);
6419 this.core.IndexElement(mediaRow, media);
6420 }
6421 }
6422
6423 /// <summary>
6424 /// Decompile the MIME table.
6425 /// </summary>
6426 /// <param name="table">The table to decompile.</param>
6427 private void DecompileMIMETable(Table table)
6428 {
6429 foreach (Row row in table.Rows)
6430 {
6431 Wix.MIME mime = new Wix.MIME();
6432
6433 mime.ContentType = Convert.ToString(row[0]);
6434
6435 if (null != row[2])
6436 {
6437 mime.Class = Convert.ToString(row[2]);
6438 }
6439
6440 this.core.IndexElement(row, mime);
6441 }
6442 }
6443
6444 /// <summary>
6445 /// Decompile the ModuleConfiguration table.
6446 /// </summary>
6447 /// <param name="table">The table to decompile.</param>
6448 private void DecompileModuleConfigurationTable(Table table)
6449 {
6450 foreach (Row row in table.Rows)
6451 {
6452 Wix.Configuration configuration = new Wix.Configuration();
6453
6454 configuration.Name = Convert.ToString(row[0]);
6455
6456 switch (Convert.ToInt32(row[1]))
6457 {
6458 case 0:
6459 configuration.Format = Wix.Configuration.FormatType.Text;
6460 break;
6461 case 1:
6462 configuration.Format = Wix.Configuration.FormatType.Key;
6463 break;
6464 case 2:
6465 configuration.Format = Wix.Configuration.FormatType.Integer;
6466 break;
6467 case 3:
6468 configuration.Format = Wix.Configuration.FormatType.Bitfield;
6469 break;
6470 default:
6471 this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1]));
6472 break;
6473 }
6474
6475 if (null != row[2])
6476 {
6477 configuration.Type = Convert.ToString(row[2]);
6478 }
6479
6480 if (null != row[3])
6481 {
6482 configuration.ContextData = Convert.ToString(row[3]);
6483 }
6484
6485 if (null != row[4])
6486 {
6487 configuration.DefaultValue = Convert.ToString(row[4]);
6488 }
6489
6490 if (null != row[5])
6491 {
6492 int attributes = Convert.ToInt32(row[5]);
6493
6494 if (MsiInterop.MsidbMsmConfigurableOptionKeyNoOrphan == (attributes & MsiInterop.MsidbMsmConfigurableOptionKeyNoOrphan))
6495 {
6496 configuration.KeyNoOrphan = Wix.YesNoType.yes;
6497 }
6498
6499 if (MsiInterop.MsidbMsmConfigurableOptionNonNullable == (attributes & MsiInterop.MsidbMsmConfigurableOptionNonNullable))
6500 {
6501 configuration.NonNullable = Wix.YesNoType.yes;
6502 }
6503
6504 if (3 < attributes)
6505 {
6506 this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[5].Column.Name, row[5]));
6507 }
6508 }
6509
6510 if (null != row[6])
6511 {
6512 configuration.DisplayName = Convert.ToString(row[6]);
6513 }
6514
6515 if (null != row[7])
6516 {
6517 configuration.Description = Convert.ToString(row[7]);
6518 }
6519
6520 if (null != row[8])
6521 {
6522 configuration.HelpLocation = Convert.ToString(row[8]);
6523 }
6524
6525 if (null != row[9])
6526 {
6527 configuration.HelpKeyword = Convert.ToString(row[9]);
6528 }
6529
6530 this.core.RootElement.AddChild(configuration);
6531 }
6532 }
6533
6534 /// <summary>
6535 /// Decompile the ModuleDependency table.
6536 /// </summary>
6537 /// <param name="table">The table to decompile.</param>
6538 private void DecompileModuleDependencyTable(Table table)
6539 {
6540 foreach (Row row in table.Rows)
6541 {
6542 Wix.Dependency dependency = new Wix.Dependency();
6543
6544 dependency.RequiredId = Convert.ToString(row[2]);
6545
6546 dependency.RequiredLanguage = Convert.ToInt32(row[3], CultureInfo.InvariantCulture);
6547
6548 if (null != row[4])
6549 {
6550 dependency.RequiredVersion = Convert.ToString(row[4]);
6551 }
6552
6553 this.core.RootElement.AddChild(dependency);
6554 }
6555 }
6556
6557 /// <summary>
6558 /// Decompile the ModuleExclusion table.
6559 /// </summary>
6560 /// <param name="table">The table to decompile.</param>
6561 private void DecompileModuleExclusionTable(Table table)
6562 {
6563 foreach (Row row in table.Rows)
6564 {
6565 Wix.Exclusion exclusion = new Wix.Exclusion();
6566
6567 exclusion.ExcludedId = Convert.ToString(row[2]);
6568
6569 int excludedLanguage = Convert.ToInt32(Convert.ToString(row[3]), CultureInfo.InvariantCulture);
6570 if (0 < excludedLanguage)
6571 {
6572 exclusion.ExcludeLanguage = excludedLanguage;
6573 }
6574 else if (0 > excludedLanguage)
6575 {
6576 exclusion.ExcludeExceptLanguage = -excludedLanguage;
6577 }
6578
6579 if (null != row[4])
6580 {
6581 exclusion.ExcludedMinVersion = Convert.ToString(row[4]);
6582 }
6583
6584 if (null != row[5])
6585 {
6586 exclusion.ExcludedMinVersion = Convert.ToString(row[5]);
6587 }
6588
6589 this.core.RootElement.AddChild(exclusion);
6590 }
6591 }
6592
6593 /// <summary>
6594 /// Decompile the ModuleIgnoreTable table.
6595 /// </summary>
6596 /// <param name="table">The table to decompile.</param>
6597 private void DecompileModuleIgnoreTableTable(Table table)
6598 {
6599 foreach (Row row in table.Rows)
6600 {
6601 string tableName = Convert.ToString(row[0]);
6602
6603 // the linker automatically adds a ModuleIgnoreTable row for some tables
6604 if ("ModuleConfiguration" != tableName && "ModuleSubstitution" != tableName)
6605 {
6606 Wix.IgnoreTable ignoreTable = new Wix.IgnoreTable();
6607
6608 ignoreTable.Id = tableName;
6609
6610 this.core.RootElement.AddChild(ignoreTable);
6611 }
6612 }
6613 }
6614
6615 /// <summary>
6616 /// Decompile the ModuleSignature table.
6617 /// </summary>
6618 /// <param name="table">The table to decompile.</param>
6619 private void DecompileModuleSignatureTable(Table table)
6620 {
6621 if (1 == table.Rows.Count)
6622 {
6623 Row row = table.Rows[0];
6624
6625 Wix.Module module = (Wix.Module)this.core.RootElement;
6626
6627 module.Id = Convert.ToString(row[0]);
6628
6629 // support Language columns that are treated as integers as well as strings (the WiX default, to support localizability)
6630 module.Language = Convert.ToString(row[1], CultureInfo.InvariantCulture);
6631
6632 module.Version = Convert.ToString(row[2]);
6633 }
6634 else
6635 {
6636 // TODO: warn
6637 }
6638 }
6639
6640 /// <summary>
6641 /// Decompile the ModuleSubstitution table.
6642 /// </summary>
6643 /// <param name="table">The table to decompile.</param>
6644 private void DecompileModuleSubstitutionTable(Table table)
6645 {
6646 foreach (Row row in table.Rows)
6647 {
6648 Wix.Substitution substitution = new Wix.Substitution();
6649
6650 substitution.Table = Convert.ToString(row[0]);
6651
6652 substitution.Row = Convert.ToString(row[1]);
6653
6654 substitution.Column = Convert.ToString(row[2]);
6655
6656 if (null != row[3])
6657 {
6658 substitution.Value = Convert.ToString(row[3]);
6659 }
6660
6661 this.core.RootElement.AddChild(substitution);
6662 }
6663 }
6664
6665 /// <summary>
6666 /// Decompile the MoveFile table.
6667 /// </summary>
6668 /// <param name="table">The table to decompile.</param>
6669 private void DecompileMoveFileTable(Table table)
6670 {
6671 foreach (Row row in table.Rows)
6672 {
6673 Wix.CopyFile copyFile = new Wix.CopyFile();
6674
6675 copyFile.Id = Convert.ToString(row[0]);
6676
6677 if (null != row[2])
6678 {
6679 copyFile.SourceName = Convert.ToString(row[2]);
6680 }
6681
6682 if (null != row[3])
6683 {
6684 string[] names = Installer.GetNames(Convert.ToString(row[3]));
6685 if (null != names[0] && null != names[1])
6686 {
6687 copyFile.DestinationShortName = names[0];
6688 copyFile.DestinationName = names[1];
6689 }
6690 else if (null != names[0])
6691 {
6692 copyFile.DestinationName = names[0];
6693 }
6694 }
6695
6696 // source/destination directory/property is set in FinalizeDuplicateMoveFileTables
6697
6698 switch (Convert.ToInt32(row[6]))
6699 {
6700 case 0:
6701 break;
6702 case MsiInterop.MsidbMoveFileOptionsMove:
6703 copyFile.Delete = Wix.YesNoType.yes;
6704 break;
6705 default:
6706 this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6]));
6707 break;
6708 }
6709
6710 Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1]));
6711 if (null != component)
6712 {
6713 component.AddChild(copyFile);
6714 }
6715 else
6716 {
6717 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component"));
6718 }
6719 this.core.IndexElement(row, copyFile);
6720 }
6721 }
6722
6723 /// <summary>
6724 /// Decompile the MsiDigitalCertificate table.
6725 /// </summary>
6726 /// <param name="table">The table to decompile.</param>
6727 private void DecompileMsiDigitalCertificateTable(Table table)
6728 {
6729 foreach (Row row in table.Rows)
6730 {
6731 Wix.DigitalCertificate digitalCertificate = new Wix.DigitalCertificate();
6732
6733 digitalCertificate.Id = Convert.ToString(row[0]);
6734
6735 digitalCertificate.SourceFile = Convert.ToString(row[1]);
6736
6737 this.core.IndexElement(row, digitalCertificate);
6738 }
6739 }
6740
6741 /// <summary>
6742 /// Decompile the MsiDigitalSignature table.
6743 /// </summary>
6744 /// <param name="table">The table to decompile.</param>
6745 private void DecompileMsiDigitalSignatureTable(Table table)
6746 {
6747 foreach (Row row in table.Rows)
6748 {
6749 Wix.DigitalSignature digitalSignature = new Wix.DigitalSignature();
6750
6751 if (null != row[3])
6752 {
6753 digitalSignature.SourceFile = Convert.ToString(row[3]);
6754 }
6755
6756 Wix.DigitalCertificate digitalCertificate = (Wix.DigitalCertificate)this.core.GetIndexedElement("MsiDigitalCertificate", Convert.ToString(row[2]));
6757 if (null != digitalCertificate)
6758 {
6759 digitalSignature.AddChild(digitalCertificate);
6760 }
6761 else
6762 {
6763 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "DigitalCertificate_", Convert.ToString(row[2]), "MsiDigitalCertificate"));
6764 }
6765
6766 Wix.IParentElement parentElement = (Wix.IParentElement)this.core.GetIndexedElement(Convert.ToString(row[0]), Convert.ToString(row[1]));
6767 if (null != parentElement)
6768 {
6769 parentElement.AddChild(digitalSignature);
6770 }
6771 else
6772 {
6773 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "SignObject", Convert.ToString(row[1]), Convert.ToString(row[0])));
6774 }
6775 }
6776 }
6777
6778 /// <summary>
6779 /// Decompile the MsiEmbeddedChainer table.
6780 /// </summary>
6781 /// <param name="table">The table to decompile.</param>
6782 private void DecompileMsiEmbeddedChainerTable(Table table)
6783 {
6784 foreach (Row row in table.Rows)
6785 {
6786 Wix.EmbeddedChainer embeddedChainer = new Wix.EmbeddedChainer();
6787
6788 embeddedChainer.Id = Convert.ToString(row[0]);
6789
6790 embeddedChainer.Content = Convert.ToString(row[1]);
6791
6792 if (null != row[2])
6793 {
6794 embeddedChainer.CommandLine = Convert.ToString(row[2]);
6795 }
6796
6797 switch (Convert.ToInt32(row[4]))
6798 {
6799 case MsiInterop.MsidbCustomActionTypeExe + MsiInterop.MsidbCustomActionTypeBinaryData:
6800 embeddedChainer.BinarySource = Convert.ToString(row[3]);
6801 break;
6802 case MsiInterop.MsidbCustomActionTypeExe + MsiInterop.MsidbCustomActionTypeSourceFile:
6803 embeddedChainer.FileSource = Convert.ToString(row[3]);
6804 break;
6805 case MsiInterop.MsidbCustomActionTypeExe + MsiInterop.MsidbCustomActionTypeProperty:
6806 embeddedChainer.PropertySource = Convert.ToString(row[3]);
6807 break;
6808 default:
6809 this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4]));
6810 break;
6811 }
6812
6813 this.core.RootElement.AddChild(embeddedChainer);
6814 }
6815 }
6816
6817 /// <summary>
6818 /// Decompile the MsiEmbeddedUI table.
6819 /// </summary>
6820 /// <param name="table">The table to decompile.</param>
6821 private void DecompileMsiEmbeddedUITable(Table table)
6822 {
6823 Wix.EmbeddedUI embeddedUI = new Wix.EmbeddedUI();
6824 bool foundEmbeddedUI = false;
6825 bool foundEmbeddedResources = false;
6826
6827 foreach (Row row in table.Rows)
6828 {
6829 int attributes = Convert.ToInt32(row[2]);
6830
6831 if (MsiInterop.MsidbEmbeddedUI == (attributes & MsiInterop.MsidbEmbeddedUI))
6832 {
6833 if (foundEmbeddedUI)
6834 {
6835 this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[2].Column.Name, row[2]));
6836 }
6837 else
6838 {
6839 embeddedUI.Id = Convert.ToString(row[0]);
6840 embeddedUI.Name = Convert.ToString(row[1]);
6841
6842 int messageFilter = Convert.ToInt32(row[3]);
6843 if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_FATALEXIT))
6844 {
6845 embeddedUI.IgnoreFatalExit = Wix.YesNoType.yes;
6846 }
6847
6848 if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_ERROR))
6849 {
6850 embeddedUI.IgnoreError = Wix.YesNoType.yes;
6851 }
6852
6853 if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_WARNING))
6854 {
6855 embeddedUI.IgnoreWarning = Wix.YesNoType.yes;
6856 }
6857
6858 if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_USER))
6859 {
6860 embeddedUI.IgnoreUser = Wix.YesNoType.yes;
6861 }
6862
6863 if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_INFO))
6864 {
6865 embeddedUI.IgnoreInfo = Wix.YesNoType.yes;
6866 }
6867
6868 if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_FILESINUSE))
6869 {
6870 embeddedUI.IgnoreFilesInUse = Wix.YesNoType.yes;
6871 }
6872
6873 if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_RESOLVESOURCE))
6874 {
6875 embeddedUI.IgnoreResolveSource = Wix.YesNoType.yes;
6876 }
6877
6878 if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_OUTOFDISKSPACE))
6879 {
6880 embeddedUI.IgnoreOutOfDiskSpace = Wix.YesNoType.yes;
6881 }
6882
6883 if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_ACTIONSTART))
6884 {
6885 embeddedUI.IgnoreActionStart = Wix.YesNoType.yes;
6886 }
6887
6888 if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_ACTIONDATA))
6889 {
6890 embeddedUI.IgnoreActionData = Wix.YesNoType.yes;
6891 }
6892
6893 if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_PROGRESS))
6894 {
6895 embeddedUI.IgnoreProgress = Wix.YesNoType.yes;
6896 }
6897
6898 if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_COMMONDATA))
6899 {
6900 embeddedUI.IgnoreCommonData = Wix.YesNoType.yes;
6901 }
6902
6903 if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_INITIALIZE))
6904 {
6905 embeddedUI.IgnoreInitialize = Wix.YesNoType.yes;
6906 }
6907
6908 if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_TERMINATE))
6909 {
6910 embeddedUI.IgnoreTerminate = Wix.YesNoType.yes;
6911 }
6912
6913 if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_SHOWDIALOG))
6914 {
6915 embeddedUI.IgnoreShowDialog = Wix.YesNoType.yes;
6916 }
6917
6918 if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_RMFILESINUSE))
6919 {
6920 embeddedUI.IgnoreRMFilesInUse = Wix.YesNoType.yes;
6921 }
6922
6923 if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_INSTALLSTART))
6924 {
6925 embeddedUI.IgnoreInstallStart = Wix.YesNoType.yes;
6926 }
6927
6928 if (0 == (messageFilter & MsiInterop.INSTALLLOGMODE_INSTALLEND))
6929 {
6930 embeddedUI.IgnoreInstallEnd = Wix.YesNoType.yes;
6931 }
6932
6933 if (MsiInterop.MsidbEmbeddedHandlesBasic == (attributes & MsiInterop.MsidbEmbeddedHandlesBasic))
6934 {
6935 embeddedUI.SupportBasicUI = Wix.YesNoType.yes;
6936 }
6937
6938 embeddedUI.SourceFile = Convert.ToString(row[4]);
6939
6940 this.core.UIElement.AddChild(embeddedUI);
6941 foundEmbeddedUI = true;
6942 }
6943 }
6944 else
6945 {
6946 Wix.EmbeddedUIResource embeddedResource = new Wix.EmbeddedUIResource();
6947
6948 embeddedResource.Id = Convert.ToString(row[0]);
6949 embeddedResource.Name = Convert.ToString(row[1]);
6950 embeddedResource.SourceFile = Convert.ToString(row[4]);
6951
6952 embeddedUI.AddChild(embeddedResource);
6953 foundEmbeddedResources = true;
6954 }
6955 }
6956
6957 if (!foundEmbeddedUI && foundEmbeddedResources)
6958 {
6959 // TODO: warn
6960 }
6961 }
6962
6963 /// <summary>
6964 /// Decompile the MsiLockPermissionsEx table.
6965 /// </summary>
6966 /// <param name="table">The table to decompile.</param>
6967 private void DecompileMsiLockPermissionsExTable(Table table)
6968 {
6969 foreach (Row row in table.Rows)
6970 {
6971 Wix.PermissionEx permissionEx = new Wix.PermissionEx();
6972 permissionEx.Id = Convert.ToString(row[0]);
6973 permissionEx.Sddl = Convert.ToString(row[3]);
6974
6975 if (null != row[4])
6976 {
6977 Wix.Condition condition = new Wix.Condition();
6978 condition.Content = Convert.ToString(row[4]);
6979 permissionEx.AddChild(condition);
6980 }
6981
6982 switch (Convert.ToString(row[2]))
6983 {
6984 case "CreateFolder":
6985 case "File":
6986 case "Registry":
6987 case "ServiceInstall":
6988 break;
6989 default:
6990 this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, row.Table.Name, row.Fields[1].Column.Name, row[1]));
6991 return;
6992 }
6993
6994 this.core.IndexElement(row, permissionEx);
6995 }
6996 }
6997
6998 /// <summary>
6999 /// Decompile the MsiPackageCertificate table.
7000 /// </summary>
7001 /// <param name="table">The table to decompile.</param>
7002 private void DecompileMsiPackageCertificateTable(Table table)
7003 {
7004 if (0 < table.Rows.Count)
7005 {
7006 Wix.PackageCertificates packageCertificates = new Wix.PackageCertificates();
7007 this.core.RootElement.AddChild(packageCertificates);
7008 AddCertificates(table, packageCertificates);
7009 }
7010 }
7011
7012 /// <summary>
7013 /// Decompile the MsiPatchCertificate table.
7014 /// </summary>
7015 /// <param name="table">The table to decompile.</param>
7016 private void DecompileMsiPatchCertificateTable(Table table)
7017 {
7018 if (0 < table.Rows.Count)
7019 {
7020 Wix.PatchCertificates patchCertificates = new Wix.PatchCertificates();
7021 this.core.RootElement.AddChild(patchCertificates);
7022 AddCertificates(table, patchCertificates);
7023 }
7024 }
7025
7026 /// <summary>
7027 /// Insert DigitalCertificate records associated with passed msiPackageCertificate or msiPatchCertificate table.
7028 /// </summary>
7029 /// <param name="table">The table being decompiled.</param>
7030 /// <param name="parent">DigitalCertificate parent</param>
7031 private void AddCertificates(Table table, Wix.IParentElement parent)
7032 {
7033 foreach (Row row in table.Rows)
7034 {
7035 Wix.DigitalCertificate digitalCertificate = (Wix.DigitalCertificate)this.core.GetIndexedElement("MsiDigitalCertificate", Convert.ToString(row[1]));
7036
7037 if (null != digitalCertificate)
7038 {
7039 parent.AddChild(digitalCertificate);
7040 }
7041 else
7042 {
7043 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "DigitalCertificate_", Convert.ToString(row[1]), "MsiDigitalCertificate"));
7044 }
7045 }
7046 }
7047
7048 /// <summary>
7049 /// Decompile the MsiShortcutProperty table.
7050 /// </summary>
7051 /// <param name="table">The table to decompile.</param>
7052 private void DecompileMsiShortcutPropertyTable(Table table)
7053 {
7054 foreach (Row row in table.Rows)
7055 {
7056 Wix.ShortcutProperty property = new Wix.ShortcutProperty();
7057 property.Id = Convert.ToString(row[0]);
7058 property.Key = Convert.ToString(row[2]);
7059 property.Value = Convert.ToString(row[3]);
7060
7061 Wix.Shortcut shortcut = (Wix.Shortcut)this.core.GetIndexedElement("Shortcut", Convert.ToString(row[1]));
7062 if (null != shortcut)
7063 {
7064 shortcut.AddChild(property);
7065 }
7066 else
7067 {
7068 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Shortcut_", Convert.ToString(row[1]), "Shortcut"));
7069 }
7070 }
7071 }
7072
7073 /// <summary>
7074 /// Decompile the ODBCAttribute table.
7075 /// </summary>
7076 /// <param name="table">The table to decompile.</param>
7077 private void DecompileODBCAttributeTable(Table table)
7078 {
7079 foreach (Row row in table.Rows)
7080 {
7081 Wix.Property property = new Wix.Property();
7082
7083 property.Id = Convert.ToString(row[1]);
7084
7085 if (null != row[2])
7086 {
7087 property.Value = Convert.ToString(row[2]);
7088 }
7089
7090 Wix.ODBCDriver odbcDriver = (Wix.ODBCDriver)this.core.GetIndexedElement("ODBCDriver", Convert.ToString(row[0]));
7091 if (null != odbcDriver)
7092 {
7093 odbcDriver.AddChild(property);
7094 }
7095 else
7096 {
7097 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Driver_", Convert.ToString(row[0]), "ODBCDriver"));
7098 }
7099 }
7100 }
7101
7102 /// <summary>
7103 /// Decompile the ODBCDataSource table.
7104 /// </summary>
7105 /// <param name="table">The table to decompile.</param>
7106 private void DecompileODBCDataSourceTable(Table table)
7107 {
7108 foreach (Row row in table.Rows)
7109 {
7110 Wix.ODBCDataSource odbcDataSource = new Wix.ODBCDataSource();
7111
7112 odbcDataSource.Id = Convert.ToString(row[0]);
7113
7114 odbcDataSource.Name = Convert.ToString(row[2]);
7115
7116 odbcDataSource.DriverName = Convert.ToString(row[3]);
7117
7118 switch (Convert.ToInt32(row[4]))
7119 {
7120 case MsiInterop.MsidbODBCDataSourceRegistrationPerMachine:
7121 odbcDataSource.Registration = Wix.ODBCDataSource.RegistrationType.machine;
7122 break;
7123 case MsiInterop.MsidbODBCDataSourceRegistrationPerUser:
7124 odbcDataSource.Registration = Wix.ODBCDataSource.RegistrationType.user;
7125 break;
7126 default:
7127 this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4]));
7128 break;
7129 }
7130
7131 this.core.IndexElement(row, odbcDataSource);
7132 }
7133 }
7134
7135 /// <summary>
7136 /// Decompile the ODBCDriver table.
7137 /// </summary>
7138 /// <param name="table">The table to decompile.</param>
7139 private void DecompileODBCDriverTable(Table table)
7140 {
7141 foreach (Row row in table.Rows)
7142 {
7143 Wix.ODBCDriver odbcDriver = new Wix.ODBCDriver();
7144
7145 odbcDriver.Id = Convert.ToString(row[0]);
7146
7147 odbcDriver.Name = Convert.ToString(row[2]);
7148
7149 odbcDriver.File = Convert.ToString(row[3]);
7150
7151 if (null != row[4])
7152 {
7153 odbcDriver.SetupFile = Convert.ToString(row[4]);
7154 }
7155
7156 Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1]));
7157 if (null != component)
7158 {
7159 component.AddChild(odbcDriver);
7160 }
7161 else
7162 {
7163 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component"));
7164 }
7165 this.core.IndexElement(row, odbcDriver);
7166 }
7167 }
7168
7169 /// <summary>
7170 /// Decompile the ODBCSourceAttribute table.
7171 /// </summary>
7172 /// <param name="table">The table to decompile.</param>
7173 private void DecompileODBCSourceAttributeTable(Table table)
7174 {
7175 foreach (Row row in table.Rows)
7176 {
7177 Wix.Property property = new Wix.Property();
7178
7179 property.Id = Convert.ToString(row[1]);
7180
7181 if (null != row[2])
7182 {
7183 property.Value = Convert.ToString(row[2]);
7184 }
7185
7186 Wix.ODBCDataSource odbcDataSource = (Wix.ODBCDataSource)this.core.GetIndexedElement("ODBCDataSource", Convert.ToString(row[0]));
7187 if (null != odbcDataSource)
7188 {
7189 odbcDataSource.AddChild(property);
7190 }
7191 else
7192 {
7193 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "DataSource_", Convert.ToString(row[0]), "ODBCDataSource"));
7194 }
7195 }
7196 }
7197
7198 /// <summary>
7199 /// Decompile the ODBCTranslator table.
7200 /// </summary>
7201 /// <param name="table">The table to decompile.</param>
7202 private void DecompileODBCTranslatorTable(Table table)
7203 {
7204 foreach (Row row in table.Rows)
7205 {
7206 Wix.ODBCTranslator odbcTranslator = new Wix.ODBCTranslator();
7207
7208 odbcTranslator.Id = Convert.ToString(row[0]);
7209
7210 odbcTranslator.Name = Convert.ToString(row[2]);
7211
7212 odbcTranslator.File = Convert.ToString(row[3]);
7213
7214 if (null != row[4])
7215 {
7216 odbcTranslator.SetupFile = Convert.ToString(row[4]);
7217 }
7218
7219 Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1]));
7220 if (null != component)
7221 {
7222 component.AddChild(odbcTranslator);
7223 }
7224 else
7225 {
7226 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component"));
7227 }
7228 }
7229 }
7230
7231 /// <summary>
7232 /// Decompile the PatchMetadata table.
7233 /// </summary>
7234 /// <param name="table">The table to decompile.</param>
7235 private void DecompilePatchMetadataTable(Table table)
7236 {
7237 if (0 < table.Rows.Count)
7238 {
7239 Wix.PatchMetadata patchMetadata = new Wix.PatchMetadata();
7240
7241 foreach (Row row in table.Rows)
7242 {
7243 string value = Convert.ToString(row[2]);
7244
7245 switch (Convert.ToString(row[1]))
7246 {
7247 case "AllowRemoval":
7248 if ("1" == value)
7249 {
7250 patchMetadata.AllowRemoval = Wix.YesNoType.yes;
7251 }
7252 break;
7253 case "Classification":
7254 if (null != value)
7255 {
7256 patchMetadata.Classification = value;
7257 }
7258 break;
7259 case "CreationTimeUTC":
7260 if (null != value)
7261 {
7262 patchMetadata.CreationTimeUTC = value;
7263 }
7264 break;
7265 case "Description":
7266 if (null != value)
7267 {
7268 patchMetadata.Description = value;
7269 }
7270 break;
7271 case "DisplayName":
7272 if (null != value)
7273 {
7274 patchMetadata.DisplayName = value;
7275 }
7276 break;
7277 case "ManufacturerName":
7278 if (null != value)
7279 {
7280 patchMetadata.ManufacturerName = value;
7281 }
7282 break;
7283 case "MinorUpdateTargetRTM":
7284 if (null != value)
7285 {
7286 patchMetadata.MinorUpdateTargetRTM = value;
7287 }
7288 break;
7289 case "MoreInfoURL":
7290 if (null != value)
7291 {
7292 patchMetadata.MoreInfoURL = value;
7293 }
7294 break;
7295 case "OptimizeCA":
7296 Wix.OptimizeCustomActions optimizeCustomActions = new Wix.OptimizeCustomActions();
7297 int optimizeCA = Int32.Parse(value, CultureInfo.InvariantCulture);
7298 if (0 != (Convert.ToInt32(OptimizeCA.SkipAssignment) & optimizeCA))
7299 {
7300 optimizeCustomActions.SkipAssignment = Wix.YesNoType.yes;
7301 }
7302
7303 if (0 != (Convert.ToInt32(OptimizeCA.SkipImmediate) & optimizeCA))
7304 {
7305 optimizeCustomActions.SkipImmediate = Wix.YesNoType.yes;
7306 }
7307
7308 if (0 != (Convert.ToInt32(OptimizeCA.SkipDeferred) & optimizeCA))
7309 {
7310 optimizeCustomActions.SkipDeferred = Wix.YesNoType.yes;
7311 }
7312
7313 patchMetadata.AddChild(optimizeCustomActions);
7314 break;
7315 case "OptimizedInstallMode":
7316 if ("1" == value)
7317 {
7318 patchMetadata.OptimizedInstallMode = Wix.YesNoType.yes;
7319 }
7320 break;
7321 case "TargetProductName":
7322 if (null != value)
7323 {
7324 patchMetadata.TargetProductName = value;
7325 }
7326 break;
7327 default:
7328 Wix.CustomProperty customProperty = new Wix.CustomProperty();
7329
7330 if (null != row[0])
7331 {
7332 customProperty.Company = Convert.ToString(row[0]);
7333 }
7334
7335 customProperty.Property = Convert.ToString(row[1]);
7336
7337 if (null != row[2])
7338 {
7339 customProperty.Value = Convert.ToString(row[2]);
7340 }
7341
7342 patchMetadata.AddChild(customProperty);
7343 break;
7344 }
7345 }
7346
7347 this.core.RootElement.AddChild(patchMetadata);
7348 }
7349 }
7350
7351 /// <summary>
7352 /// Decompile the PatchSequence table.
7353 /// </summary>
7354 /// <param name="table">The table to decompile.</param>
7355 private void DecompilePatchSequenceTable(Table table)
7356 {
7357 foreach (Row row in table.Rows)
7358 {
7359 Wix.PatchSequence patchSequence = new Wix.PatchSequence();
7360
7361 patchSequence.PatchFamily = Convert.ToString(row[0]);
7362
7363 if (null != row[1])
7364 {
7365 try
7366 {
7367 Guid guid = new Guid(Convert.ToString(row[1]));
7368
7369 patchSequence.ProductCode = Convert.ToString(row[1]);
7370 }
7371 catch // non-guid value
7372 {
7373 patchSequence.TargetImage = Convert.ToString(row[1]);
7374 }
7375 }
7376
7377 if (null != row[2])
7378 {
7379 patchSequence.Sequence = Convert.ToString(row[2]);
7380 }
7381
7382 if (null != row[3] && 0x1 == Convert.ToInt32(row[3]))
7383 {
7384 patchSequence.Supersede = Wix.YesNoType.yes;
7385 }
7386
7387 this.core.RootElement.AddChild(patchSequence);
7388 }
7389 }
7390
7391 /// <summary>
7392 /// Decompile the ProgId table.
7393 /// </summary>
7394 /// <param name="table">The table to decompile.</param>
7395 private void DecompileProgIdTable(Table table)
7396 {
7397 foreach (Row row in table.Rows)
7398 {
7399 Wix.ProgId progId = new Wix.ProgId();
7400
7401 progId.Advertise = Wix.YesNoType.yes;
7402
7403 progId.Id = Convert.ToString(row[0]);
7404
7405 if (null != row[3])
7406 {
7407 progId.Description = Convert.ToString(row[3]);
7408 }
7409
7410 if (null != row[4])
7411 {
7412 progId.Icon = Convert.ToString(row[4]);
7413 }
7414
7415 if (null != row[5])
7416 {
7417 progId.IconIndex = Convert.ToInt32(row[5]);
7418 }
7419
7420 this.core.IndexElement(row, progId);
7421 }
7422
7423 // nest the ProgIds
7424 foreach (Row row in table.Rows)
7425 {
7426 Wix.ProgId progId = (Wix.ProgId)this.core.GetIndexedElement(row);
7427
7428 if (null != row[1])
7429 {
7430 Wix.ProgId parentProgId = (Wix.ProgId)this.core.GetIndexedElement("ProgId", Convert.ToString(row[1]));
7431
7432 if (null != parentProgId)
7433 {
7434 parentProgId.AddChild(progId);
7435 }
7436 else
7437 {
7438 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "ProgId_Parent", Convert.ToString(row[1]), "ProgId"));
7439 }
7440 }
7441 else if (null != row[2])
7442 {
7443 // nesting is handled in FinalizeProgIdTable
7444 }
7445 else
7446 {
7447 // TODO: warn for orphaned ProgId
7448 }
7449 }
7450 }
7451
7452 /// <summary>
7453 /// Decompile the Properties table.
7454 /// </summary>
7455 /// <param name="table">The table to decompile.</param>
7456 private void DecompilePropertiesTable(Table table)
7457 {
7458 Wix.PatchCreation patchCreation = (Wix.PatchCreation)this.core.RootElement;
7459
7460 foreach (Row row in table.Rows)
7461 {
7462 string name = Convert.ToString(row[0]);
7463 string value = Convert.ToString(row[1]);
7464
7465 switch (name)
7466 {
7467 case "AllowProductCodeMismatches":
7468 if ("1" == value)
7469 {
7470 patchCreation.AllowProductCodeMismatches = Wix.YesNoType.yes;
7471 }
7472 break;
7473 case "AllowProductVersionMajorMismatches":
7474 if ("1" == value)
7475 {
7476 patchCreation.AllowMajorVersionMismatches = Wix.YesNoType.yes;
7477 }
7478 break;
7479 case "ApiPatchingSymbolFlags":
7480 if (null != value)
7481 {
7482 try
7483 {
7484 // remove the leading "0x" if its present
7485 if (value.StartsWith("0x", StringComparison.Ordinal))
7486 {
7487 value = value.Substring(2);
7488 }
7489
7490 patchCreation.SymbolFlags = Convert.ToInt32(value, 16);
7491 }
7492 catch
7493 {
7494 this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1]));
7495 }
7496 }
7497 break;
7498 case "DontRemoveTempFolderWhenFinished":
7499 if ("1" == value)
7500 {
7501 patchCreation.CleanWorkingFolder = Wix.YesNoType.no;
7502 }
7503 break;
7504 case "IncludeWholeFilesOnly":
7505 if ("1" == value)
7506 {
7507 patchCreation.WholeFilesOnly = Wix.YesNoType.yes;
7508 }
7509 break;
7510 case "ListOfPatchGUIDsToReplace":
7511 if (null != value)
7512 {
7513 Regex 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}\}");
7514 MatchCollection guidMatches = guidRegex.Matches(value);
7515
7516 foreach (Match guidMatch in guidMatches)
7517 {
7518 Wix.ReplacePatch replacePatch = new Wix.ReplacePatch();
7519
7520 replacePatch.Id = guidMatch.Value;
7521
7522 this.core.RootElement.AddChild(replacePatch);
7523 }
7524 }
7525 break;
7526 case "ListOfTargetProductCodes":
7527 if (null != value)
7528 {
7529 string[] targetProductCodes = value.Split(';');
7530
7531 foreach (string targetProductCodeString in targetProductCodes)
7532 {
7533 Wix.TargetProductCode targetProductCode = new Wix.TargetProductCode();
7534
7535 targetProductCode.Id = targetProductCodeString;
7536
7537 this.core.RootElement.AddChild(targetProductCode);
7538 }
7539 }
7540 break;
7541 case "PatchGUID":
7542 patchCreation.Id = value;
7543 break;
7544 case "PatchSourceList":
7545 patchCreation.SourceList = value;
7546 break;
7547 case "PatchOutputPath":
7548 patchCreation.OutputPath = value;
7549 break;
7550 default:
7551 Wix.PatchProperty patchProperty = new Wix.PatchProperty();
7552
7553 patchProperty.Name = name;
7554
7555 patchProperty.Value = value;
7556
7557 this.core.RootElement.AddChild(patchProperty);
7558 break;
7559 }
7560 }
7561 }
7562
7563 /// <summary>
7564 /// Decompile the Property table.
7565 /// </summary>
7566 /// <param name="table">The table to decompile.</param>
7567 private void DecompilePropertyTable(Table table)
7568 {
7569 foreach (Row row in table.Rows)
7570 {
7571 string id = Convert.ToString(row[0]);
7572 string value = Convert.ToString(row[1]);
7573
7574 if ("AdminProperties" == id || "MsiHiddenProperties" == id || "SecureCustomProperties" == id)
7575 {
7576 if (0 < value.Length)
7577 {
7578 foreach (string propertyId in value.Split(';'))
7579 {
7580 string property = propertyId;
7581 bool suppressModulularization = false;
7582 if (OutputType.Module == this.outputType)
7583 {
7584 if (propertyId.EndsWith(this.modularizationGuid.Substring(1, 36).Replace('-', '_'), StringComparison.Ordinal))
7585 {
7586 property = propertyId.Substring(0, propertyId.Length - this.modularizationGuid.Length + 1);
7587 }
7588 else
7589 {
7590 suppressModulularization = true;
7591 }
7592 }
7593
7594 Wix.Property specialProperty = this.EnsureProperty(property);
7595 if (suppressModulularization)
7596 {
7597 specialProperty.SuppressModularization = Wix.YesNoType.yes;
7598 }
7599
7600 switch (id)
7601 {
7602 case "AdminProperties":
7603 specialProperty.Admin = Wix.YesNoType.yes;
7604 break;
7605 case "MsiHiddenProperties":
7606 specialProperty.Hidden = Wix.YesNoType.yes;
7607 break;
7608 case "SecureCustomProperties":
7609 specialProperty.Secure = Wix.YesNoType.yes;
7610 break;
7611 }
7612 }
7613 }
7614
7615 continue;
7616 }
7617 else if (OutputType.Product == this.outputType)
7618 {
7619 Wix.Product product = (Wix.Product)this.core.RootElement;
7620
7621 switch (id)
7622 {
7623 case "Manufacturer":
7624 product.Manufacturer = value;
7625 continue;
7626 case "ProductCode":
7627 product.Id = value.ToUpper(CultureInfo.InvariantCulture);
7628 continue;
7629 case "ProductLanguage":
7630 product.Language = value;
7631 continue;
7632 case "ProductName":
7633 product.Name = value;
7634 continue;
7635 case "ProductVersion":
7636 product.Version = value;
7637 continue;
7638 case "UpgradeCode":
7639 product.UpgradeCode = value;
7640 continue;
7641 }
7642 }
7643
7644 if (!this.suppressUI || "ErrorDialog" != id)
7645 {
7646 Wix.Property property = this.EnsureProperty(id);
7647
7648 property.Value = value;
7649 }
7650 }
7651 }
7652
7653 /// <summary>
7654 /// Decompile the PublishComponent table.
7655 /// </summary>
7656 /// <param name="table">The table to decompile.</param>
7657 private void DecompilePublishComponentTable(Table table)
7658 {
7659 foreach (Row row in table.Rows)
7660 {
7661 Wix.Category category = new Wix.Category();
7662
7663 category.Id = Convert.ToString(row[0]);
7664
7665 category.Qualifier = Convert.ToString(row[1]);
7666
7667 if (null != row[3])
7668 {
7669 category.AppData = Convert.ToString(row[3]);
7670 }
7671
7672 Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[2]));
7673 if (null != component)
7674 {
7675 component.AddChild(category);
7676 }
7677 else
7678 {
7679 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[2]), "Component"));
7680 }
7681 }
7682 }
7683
7684 /// <summary>
7685 /// Decompile the RadioButton table.
7686 /// </summary>
7687 /// <param name="table">The table to decompile.</param>
7688 private void DecompileRadioButtonTable(Table table)
7689 {
7690 SortedList radioButtons = new SortedList();
7691 Hashtable radioButtonGroups = new Hashtable();
7692
7693 foreach (Row row in table.Rows)
7694 {
7695 Wix.RadioButton radioButton = new Wix.RadioButton();
7696
7697 radioButton.Value = Convert.ToString(row[2]);
7698
7699 radioButton.X = Convert.ToString(row[3], CultureInfo.InvariantCulture);
7700
7701 radioButton.Y = Convert.ToString(row[4], CultureInfo.InvariantCulture);
7702
7703 radioButton.Width = Convert.ToString(row[5], CultureInfo.InvariantCulture);
7704
7705 radioButton.Height = Convert.ToString(row[6], CultureInfo.InvariantCulture);
7706
7707 if (null != row[7])
7708 {
7709 radioButton.Text = Convert.ToString(row[7]);
7710 }
7711
7712 if (null != row[8])
7713 {
7714 string[] help = (Convert.ToString(row[8])).Split('|');
7715
7716 if (2 == help.Length)
7717 {
7718 if (0 < help[0].Length)
7719 {
7720 radioButton.ToolTip = help[0];
7721 }
7722
7723 if (0 < help[1].Length)
7724 {
7725 radioButton.Help = help[1];
7726 }
7727 }
7728 }
7729
7730 radioButtons.Add(String.Format(CultureInfo.InvariantCulture, "{0}|{1:0000000000}", row[0], row[1]), row);
7731 this.core.IndexElement(row, radioButton);
7732 }
7733
7734 // nest the radio buttons
7735 foreach (Row row in radioButtons.Values)
7736 {
7737 Wix.RadioButton radioButton = (Wix.RadioButton)this.core.GetIndexedElement(row);
7738 Wix.RadioButtonGroup radioButtonGroup = (Wix.RadioButtonGroup)radioButtonGroups[Convert.ToString(row[0])];
7739
7740 if (null == radioButtonGroup)
7741 {
7742 radioButtonGroup = new Wix.RadioButtonGroup();
7743
7744 radioButtonGroup.Property = Convert.ToString(row[0]);
7745
7746 this.core.UIElement.AddChild(radioButtonGroup);
7747 radioButtonGroups.Add(Convert.ToString(row[0]), radioButtonGroup);
7748 }
7749
7750 radioButtonGroup.AddChild(radioButton);
7751 }
7752 }
7753
7754 /// <summary>
7755 /// Decompile the Registry table.
7756 /// </summary>
7757 /// <param name="table">The table to decompile.</param>
7758 private void DecompileRegistryTable(Table table)
7759 {
7760 foreach (Row row in table.Rows)
7761 {
7762 if (("-" == Convert.ToString(row[3]) || "+" == Convert.ToString(row[3]) || "*" == Convert.ToString(row[3])) && null == row[4])
7763 {
7764 Wix.RegistryKey registryKey = new Wix.RegistryKey();
7765
7766 registryKey.Id = Convert.ToString(row[0]);
7767
7768 Wix.RegistryRootType registryRootType;
7769 if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out registryRootType))
7770 {
7771 registryKey.Root = registryRootType;
7772 }
7773
7774 registryKey.Key = Convert.ToString(row[2]);
7775
7776 switch (Convert.ToString(row[3]))
7777 {
7778 case "+":
7779 registryKey.ForceCreateOnInstall = Wix.YesNoType.yes;
7780 break;
7781 case "-":
7782 registryKey.ForceDeleteOnUninstall = Wix.YesNoType.yes;
7783 break;
7784 case "*":
7785 registryKey.ForceDeleteOnUninstall = Wix.YesNoType.yes;
7786 registryKey.ForceCreateOnInstall = Wix.YesNoType.yes;
7787 break;
7788 }
7789
7790 this.core.IndexElement(row, registryKey);
7791 }
7792 else
7793 {
7794 Wix.RegistryValue registryValue = new Wix.RegistryValue();
7795
7796 registryValue.Id = Convert.ToString(row[0]);
7797
7798 Wix.RegistryRootType registryRootType;
7799 if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out registryRootType))
7800 {
7801 registryValue.Root = registryRootType;
7802 }
7803
7804 registryValue.Key = Convert.ToString(row[2]);
7805
7806 if (null != row[3])
7807 {
7808 registryValue.Name = Convert.ToString(row[3]);
7809 }
7810
7811 if (null != row[4])
7812 {
7813 string value = Convert.ToString(row[4]);
7814
7815 if (value.StartsWith("#x", StringComparison.Ordinal))
7816 {
7817 registryValue.Type = Wix.RegistryValue.TypeType.binary;
7818 registryValue.Value = value.Substring(2);
7819 }
7820 else if (value.StartsWith("#%", StringComparison.Ordinal))
7821 {
7822 registryValue.Type = Wix.RegistryValue.TypeType.expandable;
7823 registryValue.Value = value.Substring(2);
7824 }
7825 else if (value.StartsWith("#", StringComparison.Ordinal) && !value.StartsWith("##", StringComparison.Ordinal))
7826 {
7827 registryValue.Type = Wix.RegistryValue.TypeType.integer;
7828 registryValue.Value = value.Substring(1);
7829 }
7830 else
7831 {
7832 if (value.StartsWith("##", StringComparison.Ordinal))
7833 {
7834 value = value.Substring(1);
7835 }
7836
7837 if (0 <= value.IndexOf("[~]", StringComparison.Ordinal))
7838 {
7839 registryValue.Type = Wix.RegistryValue.TypeType.multiString;
7840
7841 if ("[~]" == value)
7842 {
7843 value = string.Empty;
7844 }
7845 else if (value.StartsWith("[~]", StringComparison.Ordinal) && value.EndsWith("[~]", StringComparison.Ordinal))
7846 {
7847 value = value.Substring(3, value.Length - 6);
7848 }
7849 else if (value.StartsWith("[~]", StringComparison.Ordinal))
7850 {
7851 registryValue.Action = Wix.RegistryValue.ActionType.append;
7852 value = value.Substring(3);
7853 }
7854 else if (value.EndsWith("[~]", StringComparison.Ordinal))
7855 {
7856 registryValue.Action = Wix.RegistryValue.ActionType.prepend;
7857 value = value.Substring(0, value.Length - 3);
7858 }
7859
7860 string[] multiValues = NullSplitter.Split(value);
7861 foreach (string multiValue in multiValues)
7862 {
7863 Wix.MultiStringValue multiStringValue = new Wix.MultiStringValue();
7864
7865 multiStringValue.Content = multiValue;
7866
7867 registryValue.AddChild(multiStringValue);
7868 }
7869 }
7870 else
7871 {
7872 registryValue.Type = Wix.RegistryValue.TypeType.@string;
7873 registryValue.Value = value;
7874 }
7875 }
7876 }
7877 else
7878 {
7879 registryValue.Type = Wix.RegistryValue.TypeType.@string;
7880 registryValue.Value = String.Empty;
7881 }
7882
7883 this.core.IndexElement(row, registryValue);
7884 }
7885 }
7886 }
7887
7888 /// <summary>
7889 /// Decompile the RegLocator table.
7890 /// </summary>
7891 /// <param name="table">The table to decompile.</param>
7892 private void DecompileRegLocatorTable(Table table)
7893 {
7894 foreach (Row row in table.Rows)
7895 {
7896 Wix.RegistrySearch registrySearch = new Wix.RegistrySearch();
7897
7898 registrySearch.Id = Convert.ToString(row[0]);
7899
7900 switch (Convert.ToInt32(row[1]))
7901 {
7902 case MsiInterop.MsidbRegistryRootClassesRoot:
7903 registrySearch.Root = Wix.RegistrySearch.RootType.HKCR;
7904 break;
7905 case MsiInterop.MsidbRegistryRootCurrentUser:
7906 registrySearch.Root = Wix.RegistrySearch.RootType.HKCU;
7907 break;
7908 case MsiInterop.MsidbRegistryRootLocalMachine:
7909 registrySearch.Root = Wix.RegistrySearch.RootType.HKLM;
7910 break;
7911 case MsiInterop.MsidbRegistryRootUsers:
7912 registrySearch.Root = Wix.RegistrySearch.RootType.HKU;
7913 break;
7914 default:
7915 this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1]));
7916 break;
7917 }
7918
7919 registrySearch.Key = Convert.ToString(row[2]);
7920
7921 if (null != row[3])
7922 {
7923 registrySearch.Name = Convert.ToString(row[3]);
7924 }
7925
7926 if (null == row[4])
7927 {
7928 registrySearch.Type = Wix.RegistrySearch.TypeType.file;
7929 }
7930 else
7931 {
7932 int type = Convert.ToInt32(row[4]);
7933
7934 if (MsiInterop.MsidbLocatorType64bit == (type & MsiInterop.MsidbLocatorType64bit))
7935 {
7936 registrySearch.Win64 = Wix.YesNoType.yes;
7937 type &= ~MsiInterop.MsidbLocatorType64bit;
7938 }
7939
7940 switch (type)
7941 {
7942 case MsiInterop.MsidbLocatorTypeDirectory:
7943 registrySearch.Type = Wix.RegistrySearch.TypeType.directory;
7944 break;
7945 case MsiInterop.MsidbLocatorTypeFileName:
7946 registrySearch.Type = Wix.RegistrySearch.TypeType.file;
7947 break;
7948 case MsiInterop.MsidbLocatorTypeRawValue:
7949 registrySearch.Type = Wix.RegistrySearch.TypeType.raw;
7950 break;
7951 default:
7952 this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4]));
7953 break;
7954 }
7955 }
7956
7957 this.core.IndexElement(row, registrySearch);
7958 }
7959 }
7960
7961 /// <summary>
7962 /// Decompile the RemoveFile table.
7963 /// </summary>
7964 /// <param name="table">The table to decompile.</param>
7965 private void DecompileRemoveFileTable(Table table)
7966 {
7967 foreach (Row row in table.Rows)
7968 {
7969 if (null == row[2])
7970 {
7971 Wix.RemoveFolder removeFolder = new Wix.RemoveFolder();
7972
7973 removeFolder.Id = Convert.ToString(row[0]);
7974
7975 // directory/property is set in FinalizeDecompile
7976
7977 switch (Convert.ToInt32(row[4]))
7978 {
7979 case MsiInterop.MsidbRemoveFileInstallModeOnInstall:
7980 removeFolder.On = Wix.InstallUninstallType.install;
7981 break;
7982 case MsiInterop.MsidbRemoveFileInstallModeOnRemove:
7983 removeFolder.On = Wix.InstallUninstallType.uninstall;
7984 break;
7985 case MsiInterop.MsidbRemoveFileInstallModeOnBoth:
7986 removeFolder.On = Wix.InstallUninstallType.both;
7987 break;
7988 default:
7989 this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4]));
7990 break;
7991 }
7992
7993 Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1]));
7994 if (null != component)
7995 {
7996 component.AddChild(removeFolder);
7997 }
7998 else
7999 {
8000 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component"));
8001 }
8002 this.core.IndexElement(row, removeFolder);
8003 }
8004 else
8005 {
8006 Wix.RemoveFile removeFile = new Wix.RemoveFile();
8007
8008 removeFile.Id = Convert.ToString(row[0]);
8009
8010 string[] names = Installer.GetNames(Convert.ToString(row[2]));
8011 if (null != names[0] && null != names[1])
8012 {
8013 removeFile.ShortName = names[0];
8014 removeFile.Name = names[1];
8015 }
8016 else if (null != names[0])
8017 {
8018 removeFile.Name = names[0];
8019 }
8020
8021 // directory/property is set in FinalizeDecompile
8022
8023 switch (Convert.ToInt32(row[4]))
8024 {
8025 case MsiInterop.MsidbRemoveFileInstallModeOnInstall:
8026 removeFile.On = Wix.InstallUninstallType.install;
8027 break;
8028 case MsiInterop.MsidbRemoveFileInstallModeOnRemove:
8029 removeFile.On = Wix.InstallUninstallType.uninstall;
8030 break;
8031 case MsiInterop.MsidbRemoveFileInstallModeOnBoth:
8032 removeFile.On = Wix.InstallUninstallType.both;
8033 break;
8034 default:
8035 this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4]));
8036 break;
8037 }
8038
8039 Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1]));
8040 if (null != component)
8041 {
8042 component.AddChild(removeFile);
8043 }
8044 else
8045 {
8046 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component"));
8047 }
8048 this.core.IndexElement(row, removeFile);
8049 }
8050 }
8051 }
8052
8053 /// <summary>
8054 /// Decompile the RemoveIniFile table.
8055 /// </summary>
8056 /// <param name="table">The table to decompile.</param>
8057 private void DecompileRemoveIniFileTable(Table table)
8058 {
8059 foreach (Row row in table.Rows)
8060 {
8061 Wix.IniFile iniFile = new Wix.IniFile();
8062
8063 iniFile.Id = Convert.ToString(row[0]);
8064
8065 string[] names = Installer.GetNames(Convert.ToString(row[1]));
8066 if (null != names[0] && null != names[1])
8067 {
8068 iniFile.ShortName = names[0];
8069 iniFile.Name = names[1];
8070 }
8071 else if (null != names[0])
8072 {
8073 iniFile.Name = names[0];
8074 }
8075
8076 if (null != row[2])
8077 {
8078 iniFile.Directory = Convert.ToString(row[2]);
8079 }
8080
8081 iniFile.Section = Convert.ToString(row[3]);
8082
8083 iniFile.Key = Convert.ToString(row[4]);
8084
8085 if (null != row[5])
8086 {
8087 iniFile.Value = Convert.ToString(row[5]);
8088 }
8089
8090 switch (Convert.ToInt32(row[6]))
8091 {
8092 case MsiInterop.MsidbIniFileActionRemoveLine:
8093 iniFile.Action = Wix.IniFile.ActionType.removeLine;
8094 break;
8095 case MsiInterop.MsidbIniFileActionRemoveTag:
8096 iniFile.Action = Wix.IniFile.ActionType.removeTag;
8097 break;
8098 default:
8099 this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6]));
8100 break;
8101 }
8102
8103 Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[7]));
8104 if (null != component)
8105 {
8106 component.AddChild(iniFile);
8107 }
8108 else
8109 {
8110 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[7]), "Component"));
8111 }
8112 }
8113 }
8114
8115 /// <summary>
8116 /// Decompile the RemoveRegistry table.
8117 /// </summary>
8118 /// <param name="table">The table to decompile.</param>
8119 private void DecompileRemoveRegistryTable(Table table)
8120 {
8121 foreach (Row row in table.Rows)
8122 {
8123 if ("-" == Convert.ToString(row[3]))
8124 {
8125 Wix.RemoveRegistryKey removeRegistryKey = new Wix.RemoveRegistryKey();
8126
8127 removeRegistryKey.Id = Convert.ToString(row[0]);
8128
8129 Wix.RegistryRootType registryRootType;
8130 if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out registryRootType))
8131 {
8132 removeRegistryKey.Root = registryRootType;
8133 }
8134
8135 removeRegistryKey.Key = Convert.ToString(row[2]);
8136
8137 removeRegistryKey.Action = Wix.RemoveRegistryKey.ActionType.removeOnInstall;
8138
8139 Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[4]));
8140 if (null != component)
8141 {
8142 component.AddChild(removeRegistryKey);
8143 }
8144 else
8145 {
8146 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[4]), "Component"));
8147 }
8148 }
8149 else
8150 {
8151 Wix.RemoveRegistryValue removeRegistryValue = new Wix.RemoveRegistryValue();
8152
8153 removeRegistryValue.Id = Convert.ToString(row[0]);
8154
8155 Wix.RegistryRootType registryRootType;
8156 if (this.GetRegistryRootType(row.SourceLineNumbers, table.Name, row.Fields[1], out registryRootType))
8157 {
8158 removeRegistryValue.Root = registryRootType;
8159 }
8160
8161 removeRegistryValue.Key = Convert.ToString(row[2]);
8162
8163 if (null != row[3])
8164 {
8165 removeRegistryValue.Name = Convert.ToString(row[3]);
8166 }
8167
8168 Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[4]));
8169 if (null != component)
8170 {
8171 component.AddChild(removeRegistryValue);
8172 }
8173 else
8174 {
8175 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[4]), "Component"));
8176 }
8177 }
8178 }
8179 }
8180
8181 /// <summary>
8182 /// Decompile the ReserveCost table.
8183 /// </summary>
8184 /// <param name="table">The table to decompile.</param>
8185 private void DecompileReserveCostTable(Table table)
8186 {
8187 foreach (Row row in table.Rows)
8188 {
8189 Wix.ReserveCost reserveCost = new Wix.ReserveCost();
8190
8191 reserveCost.Id = Convert.ToString(row[0]);
8192
8193 if (null != row[2])
8194 {
8195 reserveCost.Directory = Convert.ToString(row[2]);
8196 }
8197
8198 reserveCost.RunLocal = Convert.ToInt32(row[3]);
8199
8200 reserveCost.RunFromSource = Convert.ToInt32(row[4]);
8201
8202 Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[1]));
8203 if (null != component)
8204 {
8205 component.AddChild(reserveCost);
8206 }
8207 else
8208 {
8209 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[1]), "Component"));
8210 }
8211 }
8212 }
8213
8214 /// <summary>
8215 /// Decompile the SelfReg table.
8216 /// </summary>
8217 /// <param name="table">The table to decompile.</param>
8218 private void DecompileSelfRegTable(Table table)
8219 {
8220 foreach (Row row in table.Rows)
8221 {
8222 Wix.File file = (Wix.File)this.core.GetIndexedElement("File", Convert.ToString(row[0]));
8223
8224 if (null != file)
8225 {
8226 if (null != row[1])
8227 {
8228 file.SelfRegCost = Convert.ToInt32(row[1]);
8229 }
8230 else
8231 {
8232 file.SelfRegCost = 0;
8233 }
8234 }
8235 else
8236 {
8237 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "File_", Convert.ToString(row[0]), "File"));
8238 }
8239 }
8240 }
8241
8242 /// <summary>
8243 /// Decompile the ServiceControl table.
8244 /// </summary>
8245 /// <param name="table">The table to decompile.</param>
8246 private void DecompileServiceControlTable(Table table)
8247 {
8248 foreach (Row row in table.Rows)
8249 {
8250 Wix.ServiceControl serviceControl = new Wix.ServiceControl();
8251
8252 serviceControl.Id = Convert.ToString(row[0]);
8253
8254 serviceControl.Name = Convert.ToString(row[1]);
8255
8256 int eventValue = Convert.ToInt32(row[2]);
8257 if (MsiInterop.MsidbServiceControlEventStart == (eventValue & MsiInterop.MsidbServiceControlEventStart) &&
8258 MsiInterop.MsidbServiceControlEventUninstallStart == (eventValue & MsiInterop.MsidbServiceControlEventUninstallStart))
8259 {
8260 serviceControl.Start = Wix.InstallUninstallType.both;
8261 }
8262 else if (MsiInterop.MsidbServiceControlEventStart == (eventValue & MsiInterop.MsidbServiceControlEventStart))
8263 {
8264 serviceControl.Start = Wix.InstallUninstallType.install;
8265 }
8266 else if (MsiInterop.MsidbServiceControlEventUninstallStart == (eventValue & MsiInterop.MsidbServiceControlEventUninstallStart))
8267 {
8268 serviceControl.Start = Wix.InstallUninstallType.uninstall;
8269 }
8270
8271 if (MsiInterop.MsidbServiceControlEventStop == (eventValue & MsiInterop.MsidbServiceControlEventStop) &&
8272 MsiInterop.MsidbServiceControlEventUninstallStop == (eventValue & MsiInterop.MsidbServiceControlEventUninstallStop))
8273 {
8274 serviceControl.Stop = Wix.InstallUninstallType.both;
8275 }
8276 else if (MsiInterop.MsidbServiceControlEventStop == (eventValue & MsiInterop.MsidbServiceControlEventStop))
8277 {
8278 serviceControl.Stop = Wix.InstallUninstallType.install;
8279 }
8280 else if (MsiInterop.MsidbServiceControlEventUninstallStop == (eventValue & MsiInterop.MsidbServiceControlEventUninstallStop))
8281 {
8282 serviceControl.Stop = Wix.InstallUninstallType.uninstall;
8283 }
8284
8285 if (MsiInterop.MsidbServiceControlEventDelete == (eventValue & MsiInterop.MsidbServiceControlEventDelete) &&
8286 MsiInterop.MsidbServiceControlEventUninstallDelete == (eventValue & MsiInterop.MsidbServiceControlEventUninstallDelete))
8287 {
8288 serviceControl.Remove = Wix.InstallUninstallType.both;
8289 }
8290 else if (MsiInterop.MsidbServiceControlEventDelete == (eventValue & MsiInterop.MsidbServiceControlEventDelete))
8291 {
8292 serviceControl.Remove = Wix.InstallUninstallType.install;
8293 }
8294 else if (MsiInterop.MsidbServiceControlEventUninstallDelete == (eventValue & MsiInterop.MsidbServiceControlEventUninstallDelete))
8295 {
8296 serviceControl.Remove = Wix.InstallUninstallType.uninstall;
8297 }
8298
8299 if (null != row[3])
8300 {
8301 string[] arguments = NullSplitter.Split(Convert.ToString(row[3]));
8302
8303 foreach (string argument in arguments)
8304 {
8305 Wix.ServiceArgument serviceArgument = new Wix.ServiceArgument();
8306
8307 serviceArgument.Content = argument;
8308
8309 serviceControl.AddChild(serviceArgument);
8310 }
8311 }
8312
8313 if (null != row[4])
8314 {
8315 if (0 == Convert.ToInt32(row[4]))
8316 {
8317 serviceControl.Wait = Wix.YesNoType.no;
8318 }
8319 else
8320 {
8321 serviceControl.Wait = Wix.YesNoType.yes;
8322 }
8323 }
8324
8325 Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[5]));
8326 if (null != component)
8327 {
8328 component.AddChild(serviceControl);
8329 }
8330 else
8331 {
8332 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[5]), "Component"));
8333 }
8334 }
8335 }
8336
8337 /// <summary>
8338 /// Decompile the ServiceInstall table.
8339 /// </summary>
8340 /// <param name="table">The table to decompile.</param>
8341 private void DecompileServiceInstallTable(Table table)
8342 {
8343 foreach (Row row in table.Rows)
8344 {
8345 Wix.ServiceInstall serviceInstall = new Wix.ServiceInstall();
8346
8347 serviceInstall.Id = Convert.ToString(row[0]);
8348
8349 serviceInstall.Name = Convert.ToString(row[1]);
8350
8351 if (null != row[2])
8352 {
8353 serviceInstall.DisplayName = Convert.ToString(row[2]);
8354 }
8355
8356 int serviceType = Convert.ToInt32(row[3]);
8357 if (MsiInterop.MsidbServiceInstallInteractive == (serviceType & MsiInterop.MsidbServiceInstallInteractive))
8358 {
8359 serviceInstall.Interactive = Wix.YesNoType.yes;
8360 }
8361
8362 if (MsiInterop.MsidbServiceInstallOwnProcess == (serviceType & MsiInterop.MsidbServiceInstallOwnProcess) &&
8363 MsiInterop.MsidbServiceInstallShareProcess == (serviceType & MsiInterop.MsidbServiceInstallShareProcess))
8364 {
8365 // TODO: warn
8366 }
8367 else if (MsiInterop.MsidbServiceInstallOwnProcess == (serviceType & MsiInterop.MsidbServiceInstallOwnProcess))
8368 {
8369 serviceInstall.Type = Wix.ServiceInstall.TypeType.ownProcess;
8370 }
8371 else if (MsiInterop.MsidbServiceInstallShareProcess == (serviceType & MsiInterop.MsidbServiceInstallShareProcess))
8372 {
8373 serviceInstall.Type = Wix.ServiceInstall.TypeType.shareProcess;
8374 }
8375
8376 int startType = Convert.ToInt32(row[4]);
8377 if (MsiInterop.MsidbServiceInstallDisabled == startType)
8378 {
8379 serviceInstall.Start = Wix.ServiceInstall.StartType.disabled;
8380 }
8381 else if (MsiInterop.MsidbServiceInstallDemandStart == startType)
8382 {
8383 serviceInstall.Start = Wix.ServiceInstall.StartType.demand;
8384 }
8385 else if (MsiInterop.MsidbServiceInstallAutoStart == startType)
8386 {
8387 serviceInstall.Start = Wix.ServiceInstall.StartType.auto;
8388 }
8389 else
8390 {
8391 this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[4].Column.Name, row[4]));
8392 }
8393
8394 int errorControl = Convert.ToInt32(row[5]);
8395 if (MsiInterop.MsidbServiceInstallErrorCritical == (errorControl & MsiInterop.MsidbServiceInstallErrorCritical))
8396 {
8397 serviceInstall.ErrorControl = Wix.ServiceInstall.ErrorControlType.critical;
8398 }
8399 else if (MsiInterop.MsidbServiceInstallErrorNormal == (errorControl & MsiInterop.MsidbServiceInstallErrorNormal))
8400 {
8401 serviceInstall.ErrorControl = Wix.ServiceInstall.ErrorControlType.normal;
8402 }
8403 else
8404 {
8405 serviceInstall.ErrorControl = Wix.ServiceInstall.ErrorControlType.ignore;
8406 }
8407
8408 if (MsiInterop.MsidbServiceInstallErrorControlVital == (errorControl & MsiInterop.MsidbServiceInstallErrorControlVital))
8409 {
8410 serviceInstall.Vital = Wix.YesNoType.yes;
8411 }
8412
8413 if (null != row[6])
8414 {
8415 serviceInstall.LoadOrderGroup = Convert.ToString(row[6]);
8416 }
8417
8418 if (null != row[7])
8419 {
8420 string[] dependencies = NullSplitter.Split(Convert.ToString(row[7]));
8421
8422 foreach (string dependency in dependencies)
8423 {
8424 if (0 < dependency.Length)
8425 {
8426 Wix.ServiceDependency serviceDependency = new Wix.ServiceDependency();
8427
8428 if (dependency.StartsWith("+", StringComparison.Ordinal))
8429 {
8430 serviceDependency.Group = Wix.YesNoType.yes;
8431 serviceDependency.Id = dependency.Substring(1);
8432 }
8433 else
8434 {
8435 serviceDependency.Id = dependency;
8436 }
8437
8438 serviceInstall.AddChild(serviceDependency);
8439 }
8440 }
8441 }
8442
8443 if (null != row[8])
8444 {
8445 serviceInstall.Account = Convert.ToString(row[8]);
8446 }
8447
8448 if (null != row[9])
8449 {
8450 serviceInstall.Password = Convert.ToString(row[9]);
8451 }
8452
8453 if (null != row[10])
8454 {
8455 serviceInstall.Arguments = Convert.ToString(row[10]);
8456 }
8457
8458 if (null != row[12])
8459 {
8460 serviceInstall.Description = Convert.ToString(row[12]);
8461 }
8462
8463 Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[11]));
8464 if (null != component)
8465 {
8466 component.AddChild(serviceInstall);
8467 }
8468 else
8469 {
8470 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[11]), "Component"));
8471 }
8472 this.core.IndexElement(row, serviceInstall);
8473 }
8474 }
8475
8476 /// <summary>
8477 /// Decompile the SFPCatalog table.
8478 /// </summary>
8479 /// <param name="table">The table to decompile.</param>
8480 private void DecompileSFPCatalogTable(Table table)
8481 {
8482 foreach (Row row in table.Rows)
8483 {
8484 Wix.SFPCatalog sfpCatalog = new Wix.SFPCatalog();
8485
8486 sfpCatalog.Name = Convert.ToString(row[0]);
8487
8488 sfpCatalog.SourceFile = Convert.ToString(row[1]);
8489
8490 this.core.IndexElement(row, sfpCatalog);
8491 }
8492
8493 // nest the SFPCatalog elements
8494 foreach (Row row in table.Rows)
8495 {
8496 Wix.SFPCatalog sfpCatalog = (Wix.SFPCatalog)this.core.GetIndexedElement(row);
8497
8498 if (null != row[2])
8499 {
8500 Wix.SFPCatalog parentSFPCatalog = (Wix.SFPCatalog)this.core.GetIndexedElement("SFPCatalog", Convert.ToString(row[2]));
8501
8502 if (null != parentSFPCatalog)
8503 {
8504 parentSFPCatalog.AddChild(sfpCatalog);
8505 }
8506 else
8507 {
8508 sfpCatalog.Dependency = Convert.ToString(row[2]);
8509
8510 this.core.RootElement.AddChild(sfpCatalog);
8511 }
8512 }
8513 else
8514 {
8515 this.core.RootElement.AddChild(sfpCatalog);
8516 }
8517 }
8518 }
8519
8520 /// <summary>
8521 /// Decompile the Shortcut table.
8522 /// </summary>
8523 /// <param name="table">The table to decompile.</param>
8524 private void DecompileShortcutTable(Table table)
8525 {
8526 foreach (Row row in table.Rows)
8527 {
8528 Wix.Shortcut shortcut = new Wix.Shortcut();
8529
8530 shortcut.Id = Convert.ToString(row[0]);
8531
8532 shortcut.Directory = Convert.ToString(row[1]);
8533
8534 string[] names = Installer.GetNames(Convert.ToString(row[2]));
8535 if (null != names[0] && null != names[1])
8536 {
8537 shortcut.ShortName = names[0];
8538 shortcut.Name = names[1];
8539 }
8540 else if (null != names[0])
8541 {
8542 shortcut.Name = names[0];
8543 }
8544
8545 string target = Convert.ToString(row[4]);
8546 if (target.StartsWith("[", StringComparison.Ordinal) && target.EndsWith("]", StringComparison.Ordinal))
8547 {
8548 // TODO: use this value to do a "more-correct" nesting under the indicated File or CreateDirectory element
8549 shortcut.Target = target;
8550 }
8551 else
8552 {
8553 shortcut.Advertise = Wix.YesNoType.yes;
8554
8555 // primary feature is set in FinalizeFeatureComponentsTable
8556 }
8557
8558 if (null != row[5])
8559 {
8560 shortcut.Arguments = Convert.ToString(row[5]);
8561 }
8562
8563 if (null != row[6])
8564 {
8565 shortcut.Description = Convert.ToString(row[6]);
8566 }
8567
8568 if (null != row[7])
8569 {
8570 shortcut.Hotkey = Convert.ToInt32(row[7]);
8571 }
8572
8573 if (null != row[8])
8574 {
8575 shortcut.Icon = Convert.ToString(row[8]);
8576 }
8577
8578 if (null != row[9])
8579 {
8580 shortcut.IconIndex = Convert.ToInt32(row[9]);
8581 }
8582
8583 if (null != row[10])
8584 {
8585 switch (Convert.ToInt32(row[10]))
8586 {
8587 case 1:
8588 shortcut.Show = Wix.Shortcut.ShowType.normal;
8589 break;
8590 case 3:
8591 shortcut.Show = Wix.Shortcut.ShowType.maximized;
8592 break;
8593 case 7:
8594 shortcut.Show = Wix.Shortcut.ShowType.minimized;
8595 break;
8596 default:
8597 this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[10].Column.Name, row[10]));
8598 break;
8599 }
8600 }
8601
8602 if (null != row[11])
8603 {
8604 shortcut.WorkingDirectory = Convert.ToString(row[11]);
8605 }
8606
8607 // Only try to read the MSI 4.0-specific columns if they actually exist
8608 if (15 < row.Fields.Length)
8609 {
8610 if (null != row[12])
8611 {
8612 shortcut.DisplayResourceDll = Convert.ToString(row[12]);
8613 }
8614
8615 if (null != row[13])
8616 {
8617 shortcut.DisplayResourceId = Convert.ToInt32(row[13]);
8618 }
8619
8620 if (null != row[14])
8621 {
8622 shortcut.DescriptionResourceDll = Convert.ToString(row[14]);
8623 }
8624
8625 if (null != row[15])
8626 {
8627 shortcut.DescriptionResourceId = Convert.ToInt32(row[15]);
8628 }
8629 }
8630
8631 Wix.Component component = (Wix.Component)this.core.GetIndexedElement("Component", Convert.ToString(row[3]));
8632 if (null != component)
8633 {
8634 component.AddChild(shortcut);
8635 }
8636 else
8637 {
8638 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", Convert.ToString(row[3]), "Component"));
8639 }
8640
8641 this.core.IndexElement(row, shortcut);
8642 }
8643 }
8644
8645 /// <summary>
8646 /// Decompile the Signature table.
8647 /// </summary>
8648 /// <param name="table">The table to decompile.</param>
8649 private void DecompileSignatureTable(Table table)
8650 {
8651 foreach (Row row in table.Rows)
8652 {
8653 Wix.FileSearch fileSearch = new Wix.FileSearch();
8654
8655 fileSearch.Id = Convert.ToString(row[0]);
8656
8657 string[] names = Installer.GetNames(Convert.ToString(row[1]));
8658 if (null != names[0])
8659 {
8660 // it is permissable to just have a long name
8661 if (!this.core.IsValidShortFilename(names[0], false) && null == names[1])
8662 {
8663 fileSearch.Name = names[0];
8664 }
8665 else
8666 {
8667 fileSearch.ShortName = names[0];
8668 }
8669 }
8670
8671 if (null != names[1])
8672 {
8673 fileSearch.Name = names[1];
8674 }
8675
8676 if (null != row[2])
8677 {
8678 fileSearch.MinVersion = Convert.ToString(row[2]);
8679 }
8680
8681 if (null != row[3])
8682 {
8683 fileSearch.MaxVersion = Convert.ToString(row[3]);
8684 }
8685
8686 if (null != row[4])
8687 {
8688 fileSearch.MinSize = Convert.ToInt32(row[4]);
8689 }
8690
8691 if (null != row[5])
8692 {
8693 fileSearch.MaxSize = Convert.ToInt32(row[5]);
8694 }
8695
8696 if (null != row[6])
8697 {
8698 fileSearch.MinDate = this.core.ConvertIntegerToDateTime(Convert.ToInt32(row[6]));
8699 }
8700
8701 if (null != row[7])
8702 {
8703 fileSearch.MaxDate = this.core.ConvertIntegerToDateTime(Convert.ToInt32(row[7]));
8704 }
8705
8706 if (null != row[8])
8707 {
8708 fileSearch.Languages = Convert.ToString(row[8]);
8709 }
8710
8711 this.core.IndexElement(row, fileSearch);
8712 }
8713 }
8714
8715 /// <summary>
8716 /// Decompile the TargetFiles_OptionalData table.
8717 /// </summary>
8718 /// <param name="table">The table to decompile.</param>
8719 private void DecompileTargetFiles_OptionalDataTable(Table table)
8720 {
8721 foreach (Row row in table.Rows)
8722 {
8723 Wix.TargetFile targetFile = (Wix.TargetFile)this.patchTargetFiles[row[0]];
8724 if (null == targetFile)
8725 {
8726 targetFile = new Wix.TargetFile();
8727
8728 targetFile.Id = Convert.ToString(row[1]);
8729
8730 Wix.TargetImage targetImage = (Wix.TargetImage)this.core.GetIndexedElement("TargetImages", Convert.ToString(row[0]));
8731 if (null != targetImage)
8732 {
8733 targetImage.AddChild(targetFile);
8734 }
8735 else
8736 {
8737 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Target", Convert.ToString(row[0]), "TargetImages"));
8738 }
8739 this.patchTargetFiles.Add(row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), targetFile);
8740 }
8741
8742 if (null != row[2])
8743 {
8744 string[] symbolPaths = (Convert.ToString(row[2])).Split(';');
8745
8746 foreach (string symbolPathString in symbolPaths)
8747 {
8748 Wix.SymbolPath symbolPath = new Wix.SymbolPath();
8749
8750 symbolPath.Path = symbolPathString;
8751
8752 targetFile.AddChild(symbolPath);
8753 }
8754 }
8755
8756 if (null != row[3] && null != row[4])
8757 {
8758 string[] ignoreOffsets = (Convert.ToString(row[3])).Split(',');
8759 string[] ignoreLengths = (Convert.ToString(row[4])).Split(',');
8760
8761 if (ignoreOffsets.Length == ignoreLengths.Length)
8762 {
8763 for (int i = 0; i < ignoreOffsets.Length; i++)
8764 {
8765 Wix.IgnoreRange ignoreRange = new Wix.IgnoreRange();
8766
8767 if (ignoreOffsets[i].StartsWith("0x", StringComparison.Ordinal))
8768 {
8769 ignoreRange.Offset = Convert.ToInt32(ignoreOffsets[i].Substring(2), 16);
8770 }
8771 else
8772 {
8773 ignoreRange.Offset = Convert.ToInt32(ignoreOffsets[i], CultureInfo.InvariantCulture);
8774 }
8775
8776 if (ignoreLengths[i].StartsWith("0x", StringComparison.Ordinal))
8777 {
8778 ignoreRange.Length = Convert.ToInt32(ignoreLengths[i].Substring(2), 16);
8779 }
8780 else
8781 {
8782 ignoreRange.Length = Convert.ToInt32(ignoreLengths[i], CultureInfo.InvariantCulture);
8783 }
8784
8785 targetFile.AddChild(ignoreRange);
8786 }
8787 }
8788 else
8789 {
8790 // TODO: warn
8791 }
8792 }
8793 else if (null != row[3] || null != row[4])
8794 {
8795 // TODO: warn about mismatch between columns
8796 }
8797
8798 // the RetainOffsets column is handled in FinalizeFamilyFileRangesTable
8799 }
8800 }
8801
8802 /// <summary>
8803 /// Decompile the TargetImages table.
8804 /// </summary>
8805 /// <param name="table">The table to decompile.</param>
8806 private void DecompileTargetImagesTable(Table table)
8807 {
8808 foreach (Row row in table.Rows)
8809 {
8810 Wix.TargetImage targetImage = new Wix.TargetImage();
8811
8812 targetImage.Id = Convert.ToString(row[0]);
8813
8814 targetImage.SourceFile = Convert.ToString(row[1]);
8815
8816 if (null != row[2])
8817 {
8818 string[] symbolPaths = (Convert.ToString(row[3])).Split(';');
8819
8820 foreach (string symbolPathString in symbolPaths)
8821 {
8822 Wix.SymbolPath symbolPath = new Wix.SymbolPath();
8823
8824 symbolPath.Path = symbolPathString;
8825
8826 targetImage.AddChild(symbolPath);
8827 }
8828 }
8829
8830 targetImage.Order = Convert.ToInt32(row[4]);
8831
8832 if (null != row[5])
8833 {
8834 targetImage.Validation = Convert.ToString(row[5]);
8835 }
8836
8837 if (0 != Convert.ToInt32(row[6]))
8838 {
8839 targetImage.IgnoreMissingFiles = Wix.YesNoType.yes;
8840 }
8841
8842 Wix.UpgradeImage upgradeImage = (Wix.UpgradeImage)this.core.GetIndexedElement("UpgradedImages", Convert.ToString(row[3]));
8843 if (null != upgradeImage)
8844 {
8845 upgradeImage.AddChild(targetImage);
8846 }
8847 else
8848 {
8849 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Upgraded", Convert.ToString(row[3]), "UpgradedImages"));
8850 }
8851 this.core.IndexElement(row, targetImage);
8852 }
8853 }
8854
8855 /// <summary>
8856 /// Decompile the TextStyle table.
8857 /// </summary>
8858 /// <param name="table">The table to decompile.</param>
8859 private void DecompileTextStyleTable(Table table)
8860 {
8861 foreach (Row row in table.Rows)
8862 {
8863 Wix.TextStyle textStyle = new Wix.TextStyle();
8864
8865 textStyle.Id = Convert.ToString(row[0]);
8866
8867 textStyle.FaceName = Convert.ToString(row[1]);
8868
8869 textStyle.Size = Convert.ToString(row[2]);
8870
8871 if (null != row[3])
8872 {
8873 int color = Convert.ToInt32(row[3]);
8874
8875 textStyle.Red = color & 0xFF;
8876
8877 textStyle.Green = (color & 0xFF00) >> 8;
8878
8879 textStyle.Blue = (color & 0xFF0000) >> 16;
8880 }
8881
8882 if (null != row[4])
8883 {
8884 int styleBits = Convert.ToInt32(row[4]);
8885
8886 if (MsiInterop.MsidbTextStyleStyleBitsBold == (styleBits & MsiInterop.MsidbTextStyleStyleBitsBold))
8887 {
8888 textStyle.Bold = Wix.YesNoType.yes;
8889 }
8890
8891 if (MsiInterop.MsidbTextStyleStyleBitsItalic == (styleBits & MsiInterop.MsidbTextStyleStyleBitsItalic))
8892 {
8893 textStyle.Italic = Wix.YesNoType.yes;
8894 }
8895
8896 if (MsiInterop.MsidbTextStyleStyleBitsUnderline == (styleBits & MsiInterop.MsidbTextStyleStyleBitsUnderline))
8897 {
8898 textStyle.Underline = Wix.YesNoType.yes;
8899 }
8900
8901 if (MsiInterop.MsidbTextStyleStyleBitsStrike == (styleBits & MsiInterop.MsidbTextStyleStyleBitsStrike))
8902 {
8903 textStyle.Strike = Wix.YesNoType.yes;
8904 }
8905 }
8906
8907 this.core.UIElement.AddChild(textStyle);
8908 }
8909 }
8910
8911 /// <summary>
8912 /// Decompile the TypeLib table.
8913 /// </summary>
8914 /// <param name="table">The table to decompile.</param>
8915 private void DecompileTypeLibTable(Table table)
8916 {
8917 foreach (Row row in table.Rows)
8918 {
8919 Wix.TypeLib typeLib = new Wix.TypeLib();
8920
8921 typeLib.Id = Convert.ToString(row[0]);
8922
8923 typeLib.Advertise = Wix.YesNoType.yes;
8924
8925 typeLib.Language = Convert.ToInt32(row[1]);
8926
8927 if (null != row[3])
8928 {
8929 int version = Convert.ToInt32(row[3]);
8930
8931 if (65536 == version)
8932 {
8933 this.core.OnMessage(WixWarnings.PossiblyIncorrectTypelibVersion(row.SourceLineNumbers, typeLib.Id));
8934 }
8935
8936 typeLib.MajorVersion = ((version & 0xFFFF00) >> 8);
8937 typeLib.MinorVersion = (version & 0xFF);
8938 }
8939
8940 if (null != row[4])
8941 {
8942 typeLib.Description = Convert.ToString(row[4]);
8943 }
8944
8945 if (null != row[5])
8946 {
8947 typeLib.HelpDirectory = Convert.ToString(row[5]);
8948 }
8949
8950 if (null != row[7])
8951 {
8952 typeLib.Cost = Convert.ToInt32(row[7]);
8953 }
8954
8955 // nested under the appropriate File element in FinalizeFileTable
8956 this.core.IndexElement(row, typeLib);
8957 }
8958 }
8959
8960 /// <summary>
8961 /// Decompile the Upgrade table.
8962 /// </summary>
8963 /// <param name="table">The table to decompile.</param>
8964 private void DecompileUpgradeTable(Table table)
8965 {
8966 Hashtable upgradeElements = new Hashtable();
8967
8968 foreach (UpgradeRow upgradeRow in table.Rows)
8969 {
8970 if (Compiler.UpgradeDetectedProperty == upgradeRow.ActionProperty || Compiler.DowngradeDetectedProperty == upgradeRow.ActionProperty)
8971 {
8972 continue; // MajorUpgrade rows processed in FinalizeUpgradeTable
8973 }
8974
8975 Wix.Upgrade upgrade = (Wix.Upgrade)upgradeElements[upgradeRow.UpgradeCode];
8976
8977 // create the parent Upgrade element if it doesn't already exist
8978 if (null == upgrade)
8979 {
8980 upgrade = new Wix.Upgrade();
8981
8982 upgrade.Id = upgradeRow.UpgradeCode;
8983
8984 this.core.RootElement.AddChild(upgrade);
8985 upgradeElements.Add(upgrade.Id, upgrade);
8986 }
8987
8988 Wix.UpgradeVersion upgradeVersion = new Wix.UpgradeVersion();
8989
8990 if (null != upgradeRow.VersionMin)
8991 {
8992 upgradeVersion.Minimum = upgradeRow.VersionMin;
8993 }
8994
8995 if (null != upgradeRow.VersionMax)
8996 {
8997 upgradeVersion.Maximum = upgradeRow.VersionMax;
8998 }
8999
9000 if (null != upgradeRow.Language)
9001 {
9002 upgradeVersion.Language = upgradeRow.Language;
9003 }
9004
9005 if (MsiInterop.MsidbUpgradeAttributesMigrateFeatures == (upgradeRow.Attributes & MsiInterop.MsidbUpgradeAttributesMigrateFeatures))
9006 {
9007 upgradeVersion.MigrateFeatures = Wix.YesNoType.yes;
9008 }
9009
9010 if (MsiInterop.MsidbUpgradeAttributesOnlyDetect == (upgradeRow.Attributes & MsiInterop.MsidbUpgradeAttributesOnlyDetect))
9011 {
9012 upgradeVersion.OnlyDetect = Wix.YesNoType.yes;
9013 }
9014
9015 if (MsiInterop.MsidbUpgradeAttributesIgnoreRemoveFailure == (upgradeRow.Attributes & MsiInterop.MsidbUpgradeAttributesIgnoreRemoveFailure))
9016 {
9017 upgradeVersion.IgnoreRemoveFailure = Wix.YesNoType.yes;
9018 }
9019
9020 if (MsiInterop.MsidbUpgradeAttributesVersionMinInclusive == (upgradeRow.Attributes & MsiInterop.MsidbUpgradeAttributesVersionMinInclusive))
9021 {
9022 upgradeVersion.IncludeMinimum = Wix.YesNoType.yes;
9023 }
9024
9025 if (MsiInterop.MsidbUpgradeAttributesVersionMaxInclusive == (upgradeRow.Attributes & MsiInterop.MsidbUpgradeAttributesVersionMaxInclusive))
9026 {
9027 upgradeVersion.IncludeMaximum = Wix.YesNoType.yes;
9028 }
9029
9030 if (MsiInterop.MsidbUpgradeAttributesLanguagesExclusive == (upgradeRow.Attributes & MsiInterop.MsidbUpgradeAttributesLanguagesExclusive))
9031 {
9032 upgradeVersion.ExcludeLanguages = Wix.YesNoType.yes;
9033 }
9034
9035 if (null != upgradeRow.Remove)
9036 {
9037 upgradeVersion.RemoveFeatures = upgradeRow.Remove;
9038 }
9039
9040 upgradeVersion.Property = upgradeRow.ActionProperty;
9041
9042 upgrade.AddChild(upgradeVersion);
9043 }
9044 }
9045
9046 /// <summary>
9047 /// Decompile the UpgradedFiles_OptionalData table.
9048 /// </summary>
9049 /// <param name="table">The table to decompile.</param>
9050 private void DecompileUpgradedFiles_OptionalDataTable(Table table)
9051 {
9052 foreach (Row row in table.Rows)
9053 {
9054 Wix.UpgradeFile upgradeFile = new Wix.UpgradeFile();
9055
9056 upgradeFile.File = Convert.ToString(row[1]);
9057
9058 if (null != row[2])
9059 {
9060 string[] symbolPaths = (Convert.ToString(row[2])).Split(';');
9061
9062 foreach (string symbolPathString in symbolPaths)
9063 {
9064 Wix.SymbolPath symbolPath = new Wix.SymbolPath();
9065
9066 symbolPath.Path = symbolPathString;
9067
9068 upgradeFile.AddChild(symbolPath);
9069 }
9070 }
9071
9072 if (null != row[3] && 1 == Convert.ToInt32(row[3]))
9073 {
9074 upgradeFile.AllowIgnoreOnError = Wix.YesNoType.yes;
9075 }
9076
9077 if (null != row[4] && 0 != Convert.ToInt32(row[4]))
9078 {
9079 upgradeFile.WholeFile = Wix.YesNoType.yes;
9080 }
9081
9082 upgradeFile.Ignore = Wix.YesNoType.no;
9083
9084 Wix.UpgradeImage upgradeImage = (Wix.UpgradeImage)this.core.GetIndexedElement("UpgradedImages", Convert.ToString(row[0]));
9085 if (null != upgradeImage)
9086 {
9087 upgradeImage.AddChild(upgradeFile);
9088 }
9089 else
9090 {
9091 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Upgraded", Convert.ToString(row[0]), "UpgradedImages"));
9092 }
9093 }
9094 }
9095
9096 /// <summary>
9097 /// Decompile the UpgradedFilesToIgnore table.
9098 /// </summary>
9099 /// <param name="table">The table to decompile.</param>
9100 private void DecompileUpgradedFilesToIgnoreTable(Table table)
9101 {
9102 foreach (Row row in table.Rows)
9103 {
9104 if ("*" != Convert.ToString(row[0]))
9105 {
9106 Wix.UpgradeFile upgradeFile = new Wix.UpgradeFile();
9107
9108 upgradeFile.File = Convert.ToString(row[1]);
9109
9110 upgradeFile.Ignore = Wix.YesNoType.yes;
9111
9112 Wix.UpgradeImage upgradeImage = (Wix.UpgradeImage)this.core.GetIndexedElement("UpgradedImages", Convert.ToString(row[0]));
9113 if (null != upgradeImage)
9114 {
9115 upgradeImage.AddChild(upgradeFile);
9116 }
9117 else
9118 {
9119 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Upgraded", Convert.ToString(row[0]), "UpgradedImages"));
9120 }
9121 }
9122 else
9123 {
9124 this.core.OnMessage(WixWarnings.UnrepresentableColumnValue(row.SourceLineNumbers, table.Name, row.Fields[0].Column.Name, row[0]));
9125 }
9126 }
9127 }
9128
9129 /// <summary>
9130 /// Decompile the UpgradedImages table.
9131 /// </summary>
9132 /// <param name="table">The table to decompile.</param>
9133 private void DecompileUpgradedImagesTable(Table table)
9134 {
9135 foreach (Row row in table.Rows)
9136 {
9137 Wix.UpgradeImage upgradeImage = new Wix.UpgradeImage();
9138
9139 upgradeImage.Id = Convert.ToString(row[0]);
9140
9141 upgradeImage.SourceFile = Convert.ToString(row[1]);
9142
9143 if (null != row[2])
9144 {
9145 upgradeImage.SourcePatch = Convert.ToString(row[2]);
9146 }
9147
9148 if (null != row[3])
9149 {
9150 string[] symbolPaths = (Convert.ToString(row[3])).Split(';');
9151
9152 foreach (string symbolPathString in symbolPaths)
9153 {
9154 Wix.SymbolPath symbolPath = new Wix.SymbolPath();
9155
9156 symbolPath.Path = symbolPathString;
9157
9158 upgradeImage.AddChild(symbolPath);
9159 }
9160 }
9161
9162 Wix.Family family = (Wix.Family)this.core.GetIndexedElement("ImageFamilies", Convert.ToString(row[4]));
9163 if (null != family)
9164 {
9165 family.AddChild(upgradeImage);
9166 }
9167 else
9168 {
9169 this.core.OnMessage(WixWarnings.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Family", Convert.ToString(row[4]), "ImageFamilies"));
9170 }
9171 this.core.IndexElement(row, upgradeImage);
9172 }
9173 }
9174
9175 /// <summary>
9176 /// Decompile the UIText table.
9177 /// </summary>
9178 /// <param name="table">The table to decompile.</param>
9179 private void DecompileUITextTable(Table table)
9180 {
9181 foreach (Row row in table.Rows)
9182 {
9183 Wix.UIText uiText = new Wix.UIText();
9184
9185 uiText.Id = Convert.ToString(row[0]);
9186
9187 uiText.Content = Convert.ToString(row[1]);
9188
9189 this.core.UIElement.AddChild(uiText);
9190 }
9191 }
9192
9193 /// <summary>
9194 /// Decompile the Verb table.
9195 /// </summary>
9196 /// <param name="table">The table to decompile.</param>
9197 private void DecompileVerbTable(Table table)
9198 {
9199 foreach (Row row in table.Rows)
9200 {
9201 Wix.Verb verb = new Wix.Verb();
9202
9203 verb.Id = Convert.ToString(row[1]);
9204
9205 if (null != row[2])
9206 {
9207 verb.Sequence = Convert.ToInt32(row[2]);
9208 }
9209
9210 if (null != row[3])
9211 {
9212 verb.Command = Convert.ToString(row[3]);
9213 }
9214
9215 if (null != row[4])
9216 {
9217 verb.Argument = Convert.ToString(row[4]);
9218 }
9219
9220 this.core.IndexElement(row, verb);
9221 }
9222 }
9223
9224 /// <summary>
9225 /// Gets the RegistryRootType from an integer representation of the root.
9226 /// </summary>
9227 /// <param name="sourceLineNumbers">The source line information for the root.</param>
9228 /// <param name="tableName">The name of the table containing the field.</param>
9229 /// <param name="field">The field containing the root value.</param>
9230 /// <param name="registryRootType">The strongly-typed representation of the root.</param>
9231 /// <returns>true if the value could be converted; false otherwise.</returns>
9232 private bool GetRegistryRootType(SourceLineNumber sourceLineNumbers, string tableName, Field field, out Wix.RegistryRootType registryRootType)
9233 {
9234 switch (Convert.ToInt32(field.Data))
9235 {
9236 case (-1):
9237 registryRootType = Wix.RegistryRootType.HKMU;
9238 return true;
9239 case MsiInterop.MsidbRegistryRootClassesRoot:
9240 registryRootType = Wix.RegistryRootType.HKCR;
9241 return true;
9242 case MsiInterop.MsidbRegistryRootCurrentUser:
9243 registryRootType = Wix.RegistryRootType.HKCU;
9244 return true;
9245 case MsiInterop.MsidbRegistryRootLocalMachine:
9246 registryRootType = Wix.RegistryRootType.HKLM;
9247 return true;
9248 case MsiInterop.MsidbRegistryRootUsers:
9249 registryRootType = Wix.RegistryRootType.HKU;
9250 return true;
9251 default:
9252 this.core.OnMessage(WixWarnings.IllegalColumnValue(sourceLineNumbers, tableName, field.Column.Name, field.Data));
9253 registryRootType = Wix.RegistryRootType.HKCR; // assign anything to satisfy the out parameter
9254 return false;
9255 }
9256 }
9257
9258 /// <summary>
9259 /// Set the primary feature for a component.
9260 /// </summary>
9261 /// <param name="row">The row which specifies a primary feature.</param>
9262 /// <param name="featureColumnIndex">The index of the column contaning the feature identifier.</param>
9263 /// <param name="componentColumnIndex">The index of the column containing the component identifier.</param>
9264 private void SetPrimaryFeature(Row row, int featureColumnIndex, int componentColumnIndex)
9265 {
9266 // only products contain primary features
9267 if (OutputType.Product == this.outputType)
9268 {
9269 Field featureField = row.Fields[featureColumnIndex];
9270 Field componentField = row.Fields[componentColumnIndex];
9271
9272 Wix.ComponentRef componentRef = (Wix.ComponentRef)this.core.GetIndexedElement("FeatureComponents", Convert.ToString(featureField.Data), Convert.ToString(componentField.Data));
9273
9274 if (null != componentRef)
9275 {
9276 componentRef.Primary = Wix.YesNoType.yes;
9277 }
9278 else
9279 {
9280 this.core.OnMessage(WixWarnings.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"));
9281 }
9282 }
9283 }
9284
9285 /// <summary>
9286 /// Checks the InstallExecuteSequence table to determine where RemoveExistingProducts is scheduled and removes it.
9287 /// </summary>
9288 /// <param name="tables">The collection of all tables.</param>
9289 private static Wix.MajorUpgrade.ScheduleType DetermineMajorUpgradeScheduling(TableIndexedCollection tables)
9290 {
9291 int sequenceRemoveExistingProducts = 0;
9292 int sequenceInstallValidate = 0;
9293 int sequenceInstallInitialize = 0;
9294 int sequenceInstallFinalize = 0;
9295 int sequenceInstallExecute = 0;
9296 int sequenceInstallExecuteAgain = 0;
9297
9298 Table installExecuteSequenceTable = tables["InstallExecuteSequence"];
9299 if (null != installExecuteSequenceTable)
9300 {
9301 int removeExistingProductsRow = -1;
9302 for (int i = 0; i < installExecuteSequenceTable.Rows.Count; i++)
9303 {
9304 Row row = installExecuteSequenceTable.Rows[i];
9305 string action = Convert.ToString(row[0]);
9306 int sequence = Convert.ToInt32(row[2]);
9307
9308 switch (action)
9309 {
9310 case "RemoveExistingProducts":
9311 sequenceRemoveExistingProducts = sequence;
9312 removeExistingProductsRow = i;
9313 break;
9314 case "InstallValidate":
9315 sequenceInstallValidate = sequence;
9316 break;
9317 case "InstallInitialize":
9318 sequenceInstallInitialize = sequence;
9319 break;
9320 case "InstallExecute":
9321 sequenceInstallExecute = sequence;
9322 break;
9323 case "InstallExecuteAgain":
9324 sequenceInstallExecuteAgain = sequence;
9325 break;
9326 case "InstallFinalize":
9327 sequenceInstallFinalize = sequence;
9328 break;
9329 }
9330 }
9331
9332 installExecuteSequenceTable.Rows.RemoveAt(removeExistingProductsRow);
9333 }
9334
9335 if (0 != sequenceInstallValidate && sequenceInstallValidate < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallInitialize)
9336 {
9337 return Wix.MajorUpgrade.ScheduleType.afterInstallValidate;
9338 }
9339 else if (0 != sequenceInstallInitialize && sequenceInstallInitialize < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallExecute)
9340 {
9341 return Wix.MajorUpgrade.ScheduleType.afterInstallInitialize;
9342 }
9343 else if (0 != sequenceInstallExecute && sequenceInstallExecute < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallExecuteAgain)
9344 {
9345 return Wix.MajorUpgrade.ScheduleType.afterInstallExecute;
9346 }
9347 else if (0 != sequenceInstallExecuteAgain && sequenceInstallExecuteAgain < sequenceRemoveExistingProducts && sequenceRemoveExistingProducts < sequenceInstallFinalize)
9348 {
9349 return Wix.MajorUpgrade.ScheduleType.afterInstallExecuteAgain;
9350 }
9351 else
9352 {
9353 return Wix.MajorUpgrade.ScheduleType.afterInstallFinalize;
9354 }
9355 }
9356 }
9357}
diff --git a/src/WixToolset.Core/DecompilerCore.cs b/src/WixToolset.Core/DecompilerCore.cs
new file mode 100644
index 00000000..203f2bc4
--- /dev/null
+++ b/src/WixToolset.Core/DecompilerCore.cs
@@ -0,0 +1,152 @@
1// Copyright (c) .NET Foundation and 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.Collections;
7 using WixToolset.Data;
8 using WixToolset.Extensibility;
9 using Wix = WixToolset.Data.Serialize;
10
11 /// <summary>
12 /// The base of the decompiler. Holds some variables used by the decompiler and extensions,
13 /// as well as some utility methods.
14 /// </summary>
15 internal class DecompilerCore : IDecompilerCore
16 {
17 private Hashtable elements;
18 private Wix.IParentElement rootElement;
19 private bool showPedanticMessages;
20 private Wix.UI uiElement;
21
22 /// <summary>
23 /// Instantiate a new decompiler core.
24 /// </summary>
25 /// <param name="rootElement">The root element of the decompiled database.</param>
26 /// <param name="messageHandler">The message handler.</param>
27 internal DecompilerCore(Wix.IParentElement rootElement)
28 {
29 this.elements = new Hashtable();
30 this.rootElement = rootElement;
31 }
32
33 /// <summary>
34 /// Gets whether the decompiler core encountered an error while processing.
35 /// </summary>
36 /// <value>Flag if core encountered an error during processing.</value>
37 public bool EncounteredError
38 {
39 get { return Messaging.Instance.EncounteredError; }
40 }
41
42 /// <summary>
43 /// Gets the root element of the decompiled output.
44 /// </summary>
45 /// <value>The root element of the decompiled output.</value>
46 public Wix.IParentElement RootElement
47 {
48 get { return this.rootElement; }
49 }
50
51 /// <summary>
52 /// Gets or sets the option to show pedantic messages.
53 /// </summary>
54 /// <value>The option to show pedantic messages.</value>
55 public bool ShowPedanticMessages
56 {
57 get { return this.showPedanticMessages; }
58 set { this.showPedanticMessages = value; }
59 }
60
61 /// <summary>
62 /// Gets the UI element.
63 /// </summary>
64 /// <value>The UI element.</value>
65 public Wix.UI UIElement
66 {
67 get
68 {
69 if (null == this.uiElement)
70 {
71 this.uiElement = new Wix.UI();
72 this.rootElement.AddChild(this.uiElement);
73 }
74
75 return this.uiElement;
76 }
77 }
78
79 /// <summary>
80 /// Verifies if a filename is a valid short filename.
81 /// </summary>
82 /// <param name="filename">Filename to verify.</param>
83 /// <param name="allowWildcards">true if wildcards are allowed in the filename.</param>
84 /// <returns>True if the filename is a valid short filename</returns>
85 public virtual bool IsValidShortFilename(string filename, bool allowWildcards)
86 {
87 return false;
88 }
89
90 /// <summary>
91 /// Convert an Int32 into a DateTime.
92 /// </summary>
93 /// <param name="value">The Int32 value.</param>
94 /// <returns>The DateTime.</returns>
95 public DateTime ConvertIntegerToDateTime(int value)
96 {
97 int date = value / 65536;
98 int time = value % 65536;
99
100 return new DateTime(1980 + (date / 512), (date % 512) / 32, date % 32, time / 2048, (time % 2048) / 32, (time % 32) * 2);
101 }
102
103 /// <summary>
104 /// Gets the element corresponding to the row it came from.
105 /// </summary>
106 /// <param name="row">The row corresponding to the element.</param>
107 /// <returns>The indexed element.</returns>
108 public Wix.ISchemaElement GetIndexedElement(Row row)
109 {
110 return this.GetIndexedElement(row.TableDefinition.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter));
111 }
112
113 /// <summary>
114 /// Gets the element corresponding to the primary key of the given table.
115 /// </summary>
116 /// <param name="table">The table corresponding to the element.</param>
117 /// <param name="primaryKey">The primary key corresponding to the element.</param>
118 /// <returns>The indexed element.</returns>
119 public Wix.ISchemaElement GetIndexedElement(string table, params string[] primaryKey)
120 {
121 return (Wix.ISchemaElement)this.elements[String.Concat(table, ':', String.Join(DecompilerConstants.PrimaryKeyDelimiterString, primaryKey))];
122 }
123
124 /// <summary>
125 /// Index an element by its corresponding row.
126 /// </summary>
127 /// <param name="row">The row corresponding to the element.</param>
128 /// <param name="element">The element to index.</param>
129 public void IndexElement(Row row, Wix.ISchemaElement element)
130 {
131 this.elements.Add(String.Concat(row.TableDefinition.Name, ':', row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)), element);
132 }
133
134 /// <summary>
135 /// Indicates the decompiler encountered and unexpected table to decompile.
136 /// </summary>
137 /// <param name="table">Unknown decompiled table.</param>
138 public void UnexpectedTable(Table table)
139 {
140 this.OnMessage(WixErrors.TableDecompilationUnimplemented(table.Name));
141 }
142
143 /// <summary>
144 /// Sends a message to the message delegate if there is one.
145 /// </summary>
146 /// <param name="mea">Message event arguments.</param>
147 public void OnMessage(MessageEventArgs e)
148 {
149 Messaging.Instance.OnMessage(e);
150 }
151 }
152}
diff --git a/src/WixToolset.Core/Differ.cs b/src/WixToolset.Core/Differ.cs
new file mode 100644
index 00000000..71a64327
--- /dev/null
+++ b/src/WixToolset.Core/Differ.cs
@@ -0,0 +1,621 @@
1// Copyright (c) .NET Foundation and 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.Collections;
7 using System.Collections.Generic;
8 using System.Globalization;
9 using WixToolset.Data;
10 using WixToolset.Data.Rows;
11 using WixToolset.Extensibility;
12 using WixToolset.Msi;
13
14 /// <summary>
15 /// Creates a transform by diffing two outputs.
16 /// </summary>
17 public sealed class Differ : IMessageHandler
18 {
19 private List<IInspectorExtension> inspectorExtensions;
20 private bool showPedanticMessages;
21 private bool suppressKeepingSpecialRows;
22 private bool preserveUnchangedRows;
23 private const char sectionDelimiter = '/';
24 private SummaryInformationStreams transformSummaryInfo;
25
26 /// <summary>
27 /// Instantiates a new Differ class.
28 /// </summary>
29 public Differ()
30 {
31 this.inspectorExtensions = new List<IInspectorExtension>();
32 }
33
34 /// <summary>
35 /// Gets or sets the option to show pedantic messages.
36 /// </summary>
37 /// <value>The option to show pedantic messages.</value>
38 public bool ShowPedanticMessages
39 {
40 get { return this.showPedanticMessages; }
41 set { this.showPedanticMessages = value; }
42 }
43
44 /// <summary>
45 /// Gets or sets the option to suppress keeping special rows.
46 /// </summary>
47 /// <value>The option to suppress keeping special rows.</value>
48 public bool SuppressKeepingSpecialRows
49 {
50 get { return this.suppressKeepingSpecialRows; }
51 set { this.suppressKeepingSpecialRows = value; }
52 }
53
54 /// <summary>
55 /// Gets or sets the flag to determine if all rows, even unchanged ones will be persisted in the output.
56 /// </summary>
57 /// <value>The option to keep all rows including unchanged rows.</value>
58 public bool PreserveUnchangedRows
59 {
60 get { return this.preserveUnchangedRows; }
61 set { this.preserveUnchangedRows = value; }
62 }
63
64 /// <summary>
65 /// Adds an extension.
66 /// </summary>
67 /// <param name="extension">The extension to add.</param>
68 public void AddExtension(IInspectorExtension extension)
69 {
70 this.inspectorExtensions.Add(extension);
71 }
72
73 /// <summary>
74 /// Creates a transform by diffing two outputs.
75 /// </summary>
76 /// <param name="targetOutput">The target output.</param>
77 /// <param name="updatedOutput">The updated output.</param>
78 /// <returns>The transform.</returns>
79 public Output Diff(Output targetOutput, Output updatedOutput)
80 {
81 return Diff(targetOutput, updatedOutput, 0);
82 }
83
84 /// <summary>
85 /// Creates a transform by diffing two outputs.
86 /// </summary>
87 /// <param name="targetOutput">The target output.</param>
88 /// <param name="updatedOutput">The updated output.</param>
89 /// <param name="validationFlags"></param>
90 /// <returns>The transform.</returns>
91 public Output Diff(Output targetOutput, Output updatedOutput, TransformFlags validationFlags)
92 {
93 Output transform = new Output(null);
94 transform.Type = OutputType.Transform;
95 transform.Codepage = updatedOutput.Codepage;
96 this.transformSummaryInfo = new SummaryInformationStreams();
97
98 // compare the codepages
99 if (targetOutput.Codepage != updatedOutput.Codepage && 0 == (TransformFlags.ErrorChangeCodePage & validationFlags))
100 {
101 this.OnMessage(WixErrors.OutputCodepageMismatch(targetOutput.SourceLineNumbers, targetOutput.Codepage, updatedOutput.Codepage));
102 if (null != updatedOutput.SourceLineNumbers)
103 {
104 this.OnMessage(WixErrors.OutputCodepageMismatch2(updatedOutput.SourceLineNumbers));
105 }
106 }
107
108 // compare the output types
109 if (targetOutput.Type != updatedOutput.Type)
110 {
111 throw new WixException(WixErrors.OutputTypeMismatch(targetOutput.SourceLineNumbers, targetOutput.Type.ToString(), updatedOutput.Type.ToString()));
112 }
113
114 // compare the contents of the tables
115 foreach (Table targetTable in targetOutput.Tables)
116 {
117 Table updatedTable = updatedOutput.Tables[targetTable.Name];
118 TableOperation operation = TableOperation.None;
119
120 List<Row> rows = this.CompareTables(targetOutput, targetTable, updatedTable, out operation);
121
122 if (TableOperation.Drop == operation)
123 {
124 Table droppedTable = transform.EnsureTable(targetTable.Definition);
125 droppedTable.Operation = TableOperation.Drop;
126 }
127 else if (TableOperation.None == operation)
128 {
129 Table modified = transform.EnsureTable(updatedTable.Definition);
130 rows.ForEach(r => modified.Rows.Add(r));
131 }
132 }
133
134 // added tables
135 foreach (Table updatedTable in updatedOutput.Tables)
136 {
137 if (null == targetOutput.Tables[updatedTable.Name])
138 {
139 Table addedTable = transform.EnsureTable(updatedTable.Definition);
140 addedTable.Operation = TableOperation.Add;
141
142 foreach (Row updatedRow in updatedTable.Rows)
143 {
144 updatedRow.Operation = RowOperation.Add;
145 updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId;
146 addedTable.Rows.Add(updatedRow);
147 }
148 }
149 }
150
151 // set summary information properties
152 if (!this.suppressKeepingSpecialRows)
153 {
154 Table summaryInfoTable = transform.Tables["_SummaryInformation"];
155 this.UpdateTransformSummaryInformationTable(summaryInfoTable, validationFlags);
156 }
157
158 // inspect the transform
159 InspectorCore inspectorCore = new InspectorCore();
160 foreach (InspectorExtension inspectorExtension in this.inspectorExtensions)
161 {
162 inspectorExtension.Core = inspectorCore;
163 inspectorExtension.InspectOutput(transform);
164
165 // reset
166 inspectorExtension.Core = null;
167 }
168
169 return transform;
170 }
171
172 /// <summary>
173 /// Sends a message to the message delegate if there is one.
174 /// </summary>
175 /// <param name="mea">Message event arguments.</param>
176 public void OnMessage(MessageEventArgs e)
177 {
178 Messaging.Instance.OnMessage(e);
179 }
180
181 /// <summary>
182 /// Add a row to the <paramref name="index"/> using the primary key.
183 /// </summary>
184 /// <param name="index">The indexed rows.</param>
185 /// <param name="row">The row to index.</param>
186 private void AddIndexedRow(IDictionary index, Row row)
187 {
188 string primaryKey = row.GetPrimaryKey('/');
189 if (null != primaryKey)
190 {
191 // Overriding WixActionRows have a primary key defined and take precedence in the index.
192 if (row is WixActionRow)
193 {
194 WixActionRow currentRow = (WixActionRow)row;
195 if (index.Contains(primaryKey))
196 {
197 // If the current row is not overridable, see if the indexed row is.
198 if (!currentRow.Overridable)
199 {
200 WixActionRow indexedRow = index[primaryKey] as WixActionRow;
201 if (null != indexedRow && indexedRow.Overridable)
202 {
203 // The indexed key is overridable and should be replaced
204 // (not removed and re-added which results in two Array.Copy
205 // operations for SortedList, or may be re-hashing in other
206 // implementations of IDictionary).
207 index[primaryKey] = currentRow;
208 }
209 }
210
211 // If we got this far, the row does not need to be indexed.
212 return;
213 }
214 }
215
216 // Nothing else should be added more than once.
217 if (!index.Contains(primaryKey))
218 {
219 index.Add(primaryKey, row);
220 }
221 else if (this.showPedanticMessages)
222 {
223 this.OnMessage(WixWarnings.DuplicatePrimaryKey(row.SourceLineNumbers, primaryKey, row.Table.Name));
224 }
225 }
226 else // use the string representation of the row as its primary key (it may not be unique)
227 {
228 // this is provided for compatibility with unreal tables with no primary key
229 // all real tables must specify at least one column as the primary key
230 primaryKey = row.ToString();
231 index[primaryKey] = row;
232 }
233 }
234
235 private Row CompareRows(Table targetTable, Row targetRow, Row updatedRow, out RowOperation operation, out bool keepRow)
236 {
237 Row comparedRow = null;
238 keepRow = false;
239 operation = RowOperation.None;
240
241 if (null == targetRow ^ null == updatedRow)
242 {
243 if (null == targetRow)
244 {
245 operation = updatedRow.Operation = RowOperation.Add;
246 comparedRow = updatedRow;
247 }
248 else if (null == updatedRow)
249 {
250 operation = targetRow.Operation = RowOperation.Delete;
251 targetRow.SectionId = targetRow.SectionId + sectionDelimiter;
252 comparedRow = targetRow;
253 keepRow = true;
254 }
255 }
256 else // possibly modified
257 {
258 updatedRow.Operation = RowOperation.None;
259 if (!this.suppressKeepingSpecialRows && "_SummaryInformation" == targetTable.Name)
260 {
261 // ignore rows that shouldn't be in a transform
262 if (Enum.IsDefined(typeof(SummaryInformation.Transform), (int)updatedRow[0]))
263 {
264 updatedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId;
265 comparedRow = updatedRow;
266 keepRow = true;
267 operation = RowOperation.Modify;
268 }
269 }
270 else
271 {
272 if (this.preserveUnchangedRows)
273 {
274 keepRow = true;
275 }
276
277 for (int i = 0; i < updatedRow.Fields.Length; i++)
278 {
279 ColumnDefinition columnDefinition = updatedRow.Fields[i].Column;
280
281 if (!columnDefinition.PrimaryKey)
282 {
283 bool modified = false;
284
285 if (i >= targetRow.Fields.Length)
286 {
287 columnDefinition.Added = true;
288 modified = true;
289 }
290 else if (ColumnType.Number == columnDefinition.Type && !columnDefinition.IsLocalizable)
291 {
292 if (null == targetRow[i] ^ null == updatedRow[i])
293 {
294 modified = true;
295 }
296 else if (null != targetRow[i] && null != updatedRow[i])
297 {
298 modified = ((int)targetRow[i] != (int)updatedRow[i]);
299 }
300 }
301 else if (ColumnType.Preserved == columnDefinition.Type)
302 {
303 updatedRow.Fields[i].PreviousData = (string)targetRow.Fields[i].Data;
304
305 // keep rows containing preserved fields so the historical data is available to the binder
306 keepRow = !this.suppressKeepingSpecialRows;
307 }
308 else if (ColumnType.Object == columnDefinition.Type)
309 {
310 ObjectField targetObjectField = (ObjectField)targetRow.Fields[i];
311 ObjectField updatedObjectField = (ObjectField)updatedRow.Fields[i];
312
313 updatedObjectField.PreviousEmbeddedFileIndex = targetObjectField.EmbeddedFileIndex;
314 updatedObjectField.PreviousBaseUri = targetObjectField.BaseUri;
315
316 // always keep a copy of the previous data even if they are identical
317 // This makes diff.wixmst clean and easier to control patch logic
318 updatedObjectField.PreviousData = (string)targetObjectField.Data;
319
320 // always remember the unresolved data for target build
321 updatedObjectField.UnresolvedPreviousData = (string)targetObjectField.UnresolvedData;
322
323 // keep rows containing object fields so the files can be compared in the binder
324 keepRow = !this.suppressKeepingSpecialRows;
325 }
326 else
327 {
328 modified = ((string)targetRow[i] != (string)updatedRow[i]);
329 }
330
331 if (modified)
332 {
333 if (null != updatedRow.Fields[i].PreviousData)
334 {
335 updatedRow.Fields[i].PreviousData = targetRow.Fields[i].Data.ToString();
336 }
337
338 updatedRow.Fields[i].Modified = true;
339 operation = updatedRow.Operation = RowOperation.Modify;
340 keepRow = true;
341 }
342 }
343 }
344
345 if (keepRow)
346 {
347 comparedRow = updatedRow;
348 comparedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId;
349 }
350 }
351 }
352
353 return comparedRow;
354 }
355
356 private List<Row> CompareTables(Output targetOutput, Table targetTable, Table updatedTable, out TableOperation operation)
357 {
358 List<Row> rows = new List<Row>();
359 operation = TableOperation.None;
360
361 // dropped tables
362 if (null == updatedTable ^ null == targetTable)
363 {
364 if (null == targetTable)
365 {
366 operation = TableOperation.Add;
367 rows.AddRange(updatedTable.Rows);
368 }
369 else if (null == updatedTable)
370 {
371 operation = TableOperation.Drop;
372 }
373 }
374 else // possibly modified tables
375 {
376 SortedList updatedPrimaryKeys = new SortedList();
377 SortedList targetPrimaryKeys = new SortedList();
378
379 // compare the table definitions
380 if (0 != targetTable.Definition.CompareTo(updatedTable.Definition))
381 {
382 // continue to the next table; may be more mismatches
383 this.OnMessage(WixErrors.DatabaseSchemaMismatch(targetOutput.SourceLineNumbers, targetTable.Name));
384 }
385 else
386 {
387 this.IndexPrimaryKeys(targetTable, targetPrimaryKeys, updatedTable, updatedPrimaryKeys);
388
389 // diff the target and updated rows
390 foreach (DictionaryEntry targetPrimaryKeyEntry in targetPrimaryKeys)
391 {
392 string targetPrimaryKey = (string)targetPrimaryKeyEntry.Key;
393 bool keepRow = false;
394 RowOperation rowOperation = RowOperation.None;
395
396 Row compared = this.CompareRows(targetTable, targetPrimaryKeyEntry.Value as Row, updatedPrimaryKeys[targetPrimaryKey] as Row, out rowOperation, out keepRow);
397
398 if (keepRow)
399 {
400 rows.Add(compared);
401 }
402 }
403
404 // find the inserted rows
405 foreach (DictionaryEntry updatedPrimaryKeyEntry in updatedPrimaryKeys)
406 {
407 string updatedPrimaryKey = (string)updatedPrimaryKeyEntry.Key;
408
409 if (!targetPrimaryKeys.Contains(updatedPrimaryKey))
410 {
411 Row updatedRow = (Row)updatedPrimaryKeyEntry.Value;
412
413 updatedRow.Operation = RowOperation.Add;
414 updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId;
415 rows.Add(updatedRow);
416 }
417 }
418 }
419 }
420
421 return rows;
422 }
423
424 private void IndexPrimaryKeys(Table targetTable, SortedList targetPrimaryKeys, Table updatedTable, SortedList updatedPrimaryKeys)
425 {
426 // index the target rows
427 foreach (Row row in targetTable.Rows)
428 {
429 this.AddIndexedRow(targetPrimaryKeys, row);
430
431 if ("Property" == targetTable.Name)
432 {
433 if ("ProductCode" == (string)row[0])
434 {
435 this.transformSummaryInfo.TargetProductCode = (string)row[1];
436 if ("*" == this.transformSummaryInfo.TargetProductCode)
437 {
438 this.OnMessage(WixErrors.ProductCodeInvalidForTransform(row.SourceLineNumbers));
439 }
440 }
441 else if ("ProductVersion" == (string)row[0])
442 {
443 this.transformSummaryInfo.TargetProductVersion = (string)row[1];
444 }
445 else if ("UpgradeCode" == (string)row[0])
446 {
447 this.transformSummaryInfo.TargetUpgradeCode = (string)row[1];
448 }
449 }
450 else if ("_SummaryInformation" == targetTable.Name)
451 {
452 if (1 == (int)row[0]) // PID_CODEPAGE
453 {
454 this.transformSummaryInfo.TargetSummaryInfoCodepage = (string)row[1];
455 }
456 else if (7 == (int)row[0]) // PID_TEMPLATE
457 {
458 this.transformSummaryInfo.TargetPlatformAndLanguage = (string)row[1];
459 }
460 else if (14 == (int)row[0]) // PID_PAGECOUNT
461 {
462 this.transformSummaryInfo.TargetMinimumVersion = (string)row[1];
463 }
464 }
465 }
466
467 // index the updated rows
468 foreach (Row row in updatedTable.Rows)
469 {
470 this.AddIndexedRow(updatedPrimaryKeys, row);
471
472 if ("Property" == updatedTable.Name)
473 {
474 if ("ProductCode" == (string)row[0])
475 {
476 this.transformSummaryInfo.UpdatedProductCode = (string)row[1];
477 if ("*" == this.transformSummaryInfo.UpdatedProductCode)
478 {
479 this.OnMessage(WixErrors.ProductCodeInvalidForTransform(row.SourceLineNumbers));
480 }
481 }
482 else if ("ProductVersion" == (string)row[0])
483 {
484 this.transformSummaryInfo.UpdatedProductVersion = (string)row[1];
485 }
486 }
487 else if ("_SummaryInformation" == updatedTable.Name)
488 {
489 if (1 == (int)row[0]) // PID_CODEPAGE
490 {
491 this.transformSummaryInfo.UpdatedSummaryInfoCodepage = (string)row[1];
492 }
493 else if (7 == (int)row[0]) // PID_TEMPLATE
494 {
495 this.transformSummaryInfo.UpdatedPlatformAndLanguage = (string)row[1];
496 }
497 else if (14 == (int)row[0]) // PID_PAGECOUNT
498 {
499 this.transformSummaryInfo.UpdatedMinimumVersion = (string)row[1];
500 }
501 }
502 }
503 }
504
505 private void UpdateTransformSummaryInformationTable(Table summaryInfoTable, TransformFlags validationFlags)
506 {
507 // calculate the minimum version of MSI required to process the transform
508 int targetMin;
509 int updatedMin;
510 int minimumVersion = 100;
511
512 if (Int32.TryParse(this.transformSummaryInfo.TargetMinimumVersion, out targetMin) && Int32.TryParse(this.transformSummaryInfo.UpdatedMinimumVersion, out updatedMin))
513 {
514 minimumVersion = Math.Max(targetMin, updatedMin);
515 }
516
517 Hashtable summaryRows = new Hashtable(summaryInfoTable.Rows.Count);
518 foreach (Row row in summaryInfoTable.Rows)
519 {
520 summaryRows[row[0]] = row;
521
522 if ((int)SummaryInformation.Transform.CodePage == (int)row[0])
523 {
524 row.Fields[1].Data = this.transformSummaryInfo.UpdatedSummaryInfoCodepage;
525 row.Fields[1].PreviousData = this.transformSummaryInfo.TargetSummaryInfoCodepage;
526 }
527 else if ((int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0])
528 {
529 row[1] = this.transformSummaryInfo.TargetPlatformAndLanguage;
530 }
531 else if ((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == (int)row[0])
532 {
533 row[1] = this.transformSummaryInfo.UpdatedPlatformAndLanguage;
534 }
535 else if ((int)SummaryInformation.Transform.ProductCodes == (int)row[0])
536 {
537 row[1] = String.Concat(this.transformSummaryInfo.TargetProductCode, this.transformSummaryInfo.TargetProductVersion, ';', this.transformSummaryInfo.UpdatedProductCode, this.transformSummaryInfo.UpdatedProductVersion, ';', this.transformSummaryInfo.TargetUpgradeCode);
538 }
539 else if ((int)SummaryInformation.Transform.InstallerRequirement == (int)row[0])
540 {
541 row[1] = minimumVersion.ToString(CultureInfo.InvariantCulture);
542 }
543 else if ((int)SummaryInformation.Transform.Security == (int)row[0])
544 {
545 row[1] = "4";
546 }
547 }
548
549 if (!summaryRows.Contains((int)SummaryInformation.Transform.TargetPlatformAndLanguage))
550 {
551 Row summaryRow = summaryInfoTable.CreateRow(null);
552 summaryRow[0] = (int)SummaryInformation.Transform.TargetPlatformAndLanguage;
553 summaryRow[1] = this.transformSummaryInfo.TargetPlatformAndLanguage;
554 }
555
556 if (!summaryRows.Contains((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage))
557 {
558 Row summaryRow = summaryInfoTable.CreateRow(null);
559 summaryRow[0] = (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage;
560 summaryRow[1] = this.transformSummaryInfo.UpdatedPlatformAndLanguage;
561 }
562
563 if (!summaryRows.Contains((int)SummaryInformation.Transform.ValidationFlags))
564 {
565 Row summaryRow = summaryInfoTable.CreateRow(null);
566 summaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags;
567 summaryRow[1] = ((int)validationFlags).ToString(CultureInfo.InvariantCulture);
568 }
569
570 if (!summaryRows.Contains((int)SummaryInformation.Transform.InstallerRequirement))
571 {
572 Row summaryRow = summaryInfoTable.CreateRow(null);
573 summaryRow[0] = (int)SummaryInformation.Transform.InstallerRequirement;
574 summaryRow[1] = minimumVersion.ToString(CultureInfo.InvariantCulture);
575 }
576
577 if (!summaryRows.Contains((int)SummaryInformation.Transform.Security))
578 {
579 Row summaryRow = summaryInfoTable.CreateRow(null);
580 summaryRow[0] = (int)SummaryInformation.Transform.Security;
581 summaryRow[1] = "4";
582 }
583 }
584
585 private class SummaryInformationStreams
586 {
587 public string TargetSummaryInfoCodepage
588 { get; set; }
589
590 public string TargetPlatformAndLanguage
591 { get; set; }
592
593 public string TargetProductCode
594 { get; set; }
595
596 public string TargetProductVersion
597 { get; set; }
598
599 public string TargetUpgradeCode
600 { get; set; }
601
602 public string TargetMinimumVersion
603 { get; set; }
604
605 public string UpdatedSummaryInfoCodepage
606 { get; set; }
607
608 public string UpdatedPlatformAndLanguage
609 { get; set; }
610
611 public string UpdatedProductCode
612 { get; set; }
613
614 public string UpdatedProductVersion
615 { get; set; }
616
617 public string UpdatedMinimumVersion
618 { get; set; }
619 }
620 }
621}
diff --git a/src/WixToolset.Core/Exceptions/WixFileNotFoundException.cs b/src/WixToolset.Core/Exceptions/WixFileNotFoundException.cs
new file mode 100644
index 00000000..14169c4c
--- /dev/null
+++ b/src/WixToolset.Core/Exceptions/WixFileNotFoundException.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
4{
5 using System;
6 using WixToolset.Data;
7
8 /// <summary>
9 /// WixException thrown when a file cannot be found.
10 /// </summary>
11 [Serializable]
12 public sealed class WixFileNotFoundException : WixException
13 {
14 /// <summary>
15 /// Instantiate a new WixFileNotFoundException.
16 /// </summary>
17 /// <param name="file">The file that could not be found.</param>
18 public WixFileNotFoundException(string file) : this(null, file, null)
19 {
20 }
21
22 /// <summary>
23 /// Instantiate a new WixFileNotFoundException.
24 /// </summary>
25 /// <param name="sourceLineNumbers">Source line information pertaining to the file that cannot be found.</param>
26 /// <param name="file">The file that could not be found.</param>
27 public WixFileNotFoundException(SourceLineNumber sourceLineNumbers, string file) :
28 base(WixErrors.FileNotFound(sourceLineNumbers, file))
29 {
30 }
31
32 /// <summary>
33 /// Instantiate a new WixFileNotFoundException.
34 /// </summary>
35 /// <param name="file">The file that could not be found.</param>
36 /// <param name="fileType">The type of file that cannot be found.</param>
37 public WixFileNotFoundException(string file, string fileType) : this(null, file, fileType)
38 {
39 }
40
41 /// <summary>
42 /// Instantiate a new WixFileNotFoundException.
43 /// </summary>
44 /// <param name="sourceLineNumbers">Source line information pertaining to the file that cannot be found.</param>
45 /// <param name="file">The file that could not be found.</param>
46 /// <param name="fileType">The type of file that cannot be found.</param>
47 public WixFileNotFoundException(SourceLineNumber sourceLineNumbers, string file, string fileType) :
48 base(WixErrors.FileNotFound(sourceLineNumbers, file, fileType))
49 {
50 }
51 }
52}
diff --git a/src/WixToolset.Core/Extensibility/AssemblyDefaultHeatExtensionAttribute.cs b/src/WixToolset.Core/Extensibility/AssemblyDefaultHeatExtensionAttribute.cs
new file mode 100644
index 00000000..bb53e30c
--- /dev/null
+++ b/src/WixToolset.Core/Extensibility/AssemblyDefaultHeatExtensionAttribute.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.Tools
4{
5 using System;
6
7 /// <summary>
8 /// Represents a custom attribute for declaring the type to use
9 /// as the default heat extension in an assembly.
10 /// </summary>
11 public class AssemblyDefaultHeatExtensionAttribute : Attribute
12 {
13 private readonly Type extensionType;
14
15 /// <summary>
16 /// Instantiate a new AssemblyDefaultHeatExtensionAttribute.
17 /// </summary>
18 /// <param name="extensionType">The type of the default heat extension in an assembly.</param>
19 public AssemblyDefaultHeatExtensionAttribute(Type extensionType)
20 {
21 this.extensionType = extensionType;
22 }
23
24 /// <summary>
25 /// Gets the type of the default heat extension in an assembly.
26 /// </summary>
27 /// <value>The type of the default heat extension in an assembly.</value>
28 public Type ExtensionType
29 {
30 get { return this.extensionType; }
31 }
32 }
33}
diff --git a/src/WixToolset.Core/Extensibility/HarvesterExtension.cs b/src/WixToolset.Core/Extensibility/HarvesterExtension.cs
new file mode 100644
index 00000000..d8d0ab34
--- /dev/null
+++ b/src/WixToolset.Core/Extensibility/HarvesterExtension.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
4{
5 using Wix = WixToolset.Data.Serialize;
6
7 /// <summary>
8 /// The base harvester extension. Any of these methods can be overridden to change
9 /// the behavior of the harvester.
10 /// </summary>
11 public abstract class HarvesterExtension
12 {
13 /// <summary>
14 /// Gets or sets the harvester core for the extension.
15 /// </summary>
16 /// <value>The harvester core for the extension.</value>
17 public IHarvesterCore Core { get; set; }
18
19 /// <summary>
20 /// Harvest a WiX document.
21 /// </summary>
22 /// <param name="argument">The argument for harvesting.</param>
23 /// <returns>The harvested Fragments.</returns>
24 public abstract Wix.Fragment[] Harvest(string argument);
25 }
26}
diff --git a/src/WixToolset.Core/Extensibility/HeatExtension.cs b/src/WixToolset.Core/Extensibility/HeatExtension.cs
new file mode 100644
index 00000000..5e292220
--- /dev/null
+++ b/src/WixToolset.Core/Extensibility/HeatExtension.cs
@@ -0,0 +1,204 @@
1// Copyright (c) .NET Foundation and 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.Extensibility
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Reflection;
9 using WixToolset;
10 using WixToolset.Data;
11 using WixToolset.Extensibilty;
12 using WixToolset.Tools;
13 using Wix = WixToolset.Data.Serialize;
14
15 /// <summary>
16 /// A command line option.
17 /// </summary>
18 public struct HeatCommandLineOption
19 {
20 public string Option;
21
22 public string Description;
23
24 /// <summary>
25 /// Instantiates a new CommandLineOption.
26 /// </summary>
27 /// <param name="option">The option name.</param>
28 /// <param name="description">The description of the option.</param>
29 public HeatCommandLineOption(string option, string description)
30 {
31 this.Option = option;
32 this.Description = description;
33 }
34 }
35
36 /// <summary>
37 /// An extension for the WiX Toolset Harvester application.
38 /// </summary>
39 public abstract class HeatExtension
40 {
41 /// <summary>
42 /// Gets or sets the heat core for the extension.
43 /// </summary>
44 /// <value>The heat core for the extension.</value>
45 public IHeatCore Core { get; set; }
46
47 /// <summary>
48 /// Gets the supported command line types for this extension.
49 /// </summary>
50 /// <value>The supported command line types for this extension.</value>
51 public virtual HeatCommandLineOption[] CommandLineTypes
52 {
53 get { return null; }
54 }
55
56 /// <summary>
57 /// Loads a HeatExtension from a type description string.
58 /// </summary>
59 /// <param name="extension">The extension type description string.</param>
60 /// <returns>The loaded HeatExtension.</returns>
61 /// <remarks>
62 /// <paramref name="extension"/> can be in several different forms:
63 /// <list type="number">
64 /// <item><term>AssemblyQualifiedName (TopNamespace.SubNameSpace.ContainingClass+NestedClass, MyAssembly, Version=1.3.0.0, Culture=neutral, PublicKeyToken=b17a5c561934e089)</term></item>
65 /// <item><term>AssemblyName (MyAssembly, Version=1.3.0.0, Culture=neutral, PublicKeyToken=b17a5c561934e089)</term></item>
66 /// <item><term>Absolute path to an assembly (C:\MyExtensions\ExtensionAssembly.dll)</term></item>
67 /// <item><term>Filename of an assembly in the application directory (ExtensionAssembly.dll)</term></item>
68 /// <item><term>Relative path to an assembly (..\..\MyExtensions\ExtensionAssembly.dll)</term></item>
69 /// </list>
70 /// To specify a particular class to use, prefix the fully qualified class name to the assembly and separate them with a comma.
71 /// For example: "TopNamespace.SubNameSpace.ContainingClass+NestedClass, C:\MyExtensions\ExtensionAssembly.dll"
72 /// </remarks>
73 public static HeatExtension Load(string extension)
74 {
75 Type extensionType = null;
76 int commaIndex = extension.IndexOf(',');
77 string className = String.Empty;
78 string assemblyName = extension;
79
80 if (0 <= commaIndex)
81 {
82 className = extension.Substring(0, commaIndex);
83 assemblyName = (extension.Length <= commaIndex + 1 ? String.Empty : extension.Substring(commaIndex + 1));
84 }
85
86 className = className.Trim();
87 assemblyName = assemblyName.Trim();
88
89 if (null == extensionType && 0 < assemblyName.Length)
90 {
91
92 Assembly extensionAssembly;
93
94 // case 3: Absolute path to an assembly
95 if (Path.IsPathRooted(assemblyName))
96 {
97 extensionAssembly = ExtensionLoadFrom(assemblyName);
98 }
99 else
100 {
101 try
102 {
103 // case 2: AssemblyName
104 extensionAssembly = Assembly.Load(assemblyName);
105 }
106 catch (IOException e)
107 {
108 if (e is FileLoadException || e is FileNotFoundException)
109 {
110 try
111 {
112 // case 4: Filename of an assembly in the application directory
113 extensionAssembly = Assembly.Load(Path.GetFileNameWithoutExtension(assemblyName));
114 }
115 catch (IOException innerE)
116 {
117 if (innerE is FileLoadException || innerE is FileNotFoundException)
118 {
119 // case 5: Relative path to an assembly
120
121 // we want to use Assembly.Load when we can because it has some benefits over Assembly.LoadFrom
122 // (see the documentation for Assembly.LoadFrom). However, it may fail when the path is a relative
123 // path, so we should try Assembly.LoadFrom one last time. We could have detected a directory
124 // separator character and used Assembly.LoadFrom directly, but dealing with path canonicalization
125 // issues is something we don't want to deal with if we don't have to.
126 extensionAssembly = ExtensionLoadFrom(assemblyName);
127 }
128 else
129 {
130 throw new WixException(WixErrors.InvalidExtension(assemblyName, innerE.Message));
131 }
132 }
133 }
134 else
135 {
136 throw new WixException(WixErrors.InvalidExtension(assemblyName, e.Message));
137 }
138 }
139 }
140
141 if (0 < className.Length)
142 {
143 try
144 {
145 // case 1: AssemblyQualifiedName
146 extensionType = extensionAssembly.GetType(className, true /* throwOnError */, true /* ignoreCase */);
147 }
148 catch (Exception e)
149 {
150 throw new WixException(WixErrors.InvalidExtensionType(assemblyName, className, e.GetType().ToString(), e.Message));
151 }
152 }
153 else
154 {
155 // if no class name was specified, then let's hope the assembly defined a default WixExtension
156 AssemblyDefaultHeatExtensionAttribute extensionAttribute = (AssemblyDefaultHeatExtensionAttribute)Attribute.GetCustomAttribute(extensionAssembly, typeof(AssemblyDefaultHeatExtensionAttribute));
157
158 if (null != extensionAttribute)
159 {
160 extensionType = extensionAttribute.ExtensionType;
161 }
162 else
163 {
164 throw new WixException(WixErrors.InvalidExtensionType(assemblyName, typeof(AssemblyDefaultHeatExtensionAttribute).ToString()));
165 }
166 }
167 }
168
169 if (extensionType.IsSubclassOf(typeof(HeatExtension)))
170 {
171 return Activator.CreateInstance(extensionType) as HeatExtension;
172 }
173 else
174 {
175 throw new WixException(WixErrors.InvalidExtensionType(extension, extensionType.ToString(), typeof(HeatExtension).ToString()));
176 }
177 }
178
179 /// <summary>
180 /// Parse the command line options for this extension.
181 /// </summary>
182 /// <param name="type">The active harvester type.</param>
183 /// <param name="args">The option arguments.</param>
184 public virtual void ParseOptions(string type, string[] args)
185 {
186 }
187
188 private static Assembly ExtensionLoadFrom(string assemblyName)
189 {
190 Assembly extensionAssembly = null;
191
192 try
193 {
194 extensionAssembly = Assembly.LoadFrom(assemblyName);
195 }
196 catch (Exception e)
197 {
198 throw new WixException(WixErrors.InvalidExtension(assemblyName, e.Message));
199 }
200
201 return extensionAssembly;
202 }
203 }
204}
diff --git a/src/WixToolset.Core/Extensibility/IHarvesterCore.cs b/src/WixToolset.Core/Extensibility/IHarvesterCore.cs
new file mode 100644
index 00000000..9a6fd10c
--- /dev/null
+++ b/src/WixToolset.Core/Extensibility/IHarvesterCore.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
4{
5 using System;
6 using System.Diagnostics.CodeAnalysis;
7 using System.IO;
8 using WixToolset.Data;
9 using Wix = WixToolset.Data.Serialize;
10
11 /// <summary>
12 /// The WiX Toolset harvester core.
13 /// </summary>
14 public interface IHarvesterCore
15 {
16 /// <summary>
17 /// Gets whether the harvester core encountered an error while processing.
18 /// </summary>
19 /// <value>Flag if core encountered an error during processing.</value>
20 bool EncounteredError { get; }
21
22 /// <summary>
23 /// Gets or sets the value of the extension argument passed to heat.
24 /// </summary>
25 /// <value>The extension argument.</value>
26 string ExtensionArgument { get; set; }
27
28 /// <summary>
29 /// Gets or sets the value of the root directory that is being harvested.
30 /// </summary>
31 /// <value>The root directory being harvested.</value>
32 string RootDirectory { get; set; }
33
34 /// <summary>
35 /// Create an identifier based on passed file name
36 /// </summary>
37 /// <param name="name">File name to generate identifer from</param>
38 /// <returns></returns>
39 string CreateIdentifierFromFilename(string filename);
40
41 /// <summary>
42 /// Generate an identifier by hashing data from the row.
43 /// </summary>
44 /// <param name="prefix">Three letter or less prefix for generated row identifier.</param>
45 /// <param name="args">Information to hash.</param>
46 /// <returns>The generated identifier.</returns>
47 string GenerateIdentifier(string prefix, params string[] args);
48
49 /// <summary>
50 /// Sends a message to the message delegate if there is one.
51 /// </summary>
52 /// <param name="mea">Message event arguments.</param>
53 void OnMessage(MessageEventArgs mea);
54
55 /// <summary>
56 /// Resolves a file's path if the Wix.File.Source value starts with "SourceDir\".
57 /// </summary>
58 /// <param name="fileSource">The Wix.File.Source value with "SourceDir\".</param>
59 /// <returns>The full path of the file.</returns>
60 string ResolveFilePath(string fileSource);
61 }
62}
diff --git a/src/WixToolset.Core/Extensibility/IHeatCore.cs b/src/WixToolset.Core/Extensibility/IHeatCore.cs
new file mode 100644
index 00000000..bc853b24
--- /dev/null
+++ b/src/WixToolset.Core/Extensibility/IHeatCore.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.Extensibilty
4{
5 using WixToolset.Data;
6
7 /// <summary>
8 /// The WiX Toolset Harvester application core.
9 /// </summary>
10 public interface IHeatCore
11 {
12 /// <summary>
13 /// Gets whether the mutator core encountered an error while processing.
14 /// </summary>
15 /// <value>Flag if core encountered an error during processing.</value>
16 bool EncounteredError { get; }
17
18 /// <summary>
19 /// Gets the harvester.
20 /// </summary>
21 /// <value>The harvester.</value>
22 Harvester Harvester { get; }
23
24 /// <summary>
25 /// Gets the mutator.
26 /// </summary>
27 /// <value>The mutator.</value>
28 Mutator Mutator { get; }
29
30 /// <summary>
31 /// Sends a message to the message delegate if there is one.
32 /// </summary>
33 /// <param name="mea">Message event arguments.</param>
34 void OnMessage(MessageEventArgs mea);
35 }
36}
diff --git a/src/WixToolset.Core/Extensibility/MutatorExtension.cs b/src/WixToolset.Core/Extensibility/MutatorExtension.cs
new file mode 100644
index 00000000..9de64180
--- /dev/null
+++ b/src/WixToolset.Core/Extensibility/MutatorExtension.cs
@@ -0,0 +1,198 @@
1// Copyright (c) .NET Foundation and 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.Extensibility
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics.CodeAnalysis;
8 using System.Text;
9
10 using Wix = WixToolset.Data.Serialize;
11
12 /// <summary>
13 /// The base mutator extension. Any of these methods can be overridden to change
14 /// the behavior of the mutator.
15 /// </summary>
16 public abstract class MutatorExtension
17 {
18 /// <summary>
19 /// Gets or sets the mutator core for the extension.
20 /// </summary>
21 /// <value>The mutator core for the extension.</value>
22 public IHarvesterCore Core { get; set; }
23
24 /// <summary>
25 /// Gets the sequence of the extension.
26 /// </summary>
27 /// <value>The sequence of the extension.</value>
28 public abstract int Sequence
29 {
30 get;
31 }
32
33 /// <summary>
34 /// Mutate a WiX document.
35 /// </summary>
36 /// <param name="wix">The Wix document element.</param>
37 public virtual void Mutate(Wix.Wix wix)
38 {
39 }
40
41 /// <summary>
42 /// Mutate a WiX document as a string.
43 /// </summary>
44 /// <param name="wix">The Wix document element as a string.</param>
45 /// <returns>The mutated Wix document as a string.</returns>
46 public virtual string Mutate(string wixString)
47 {
48 return wixString;
49 }
50
51 /// <summary>
52 /// Generate unique MSI identifiers.
53 /// </summary>
54 protected class IdentifierGenerator
55 {
56 public const int MaxProductIdentifierLength = 72;
57 public const int MaxModuleIdentifierLength = 35;
58
59 private string baseName;
60 private int maxLength;
61 private Dictionary<string, object> existingIdentifiers;
62 private Dictionary<string, object> possibleIdentifiers;
63
64 /// <summary>
65 /// Instantiate a new IdentifierGenerator.
66 /// </summary>
67 /// <param name="baseName">The base resource name to use if a resource name contains no usable characters.</param>
68 public IdentifierGenerator(string baseName)
69 {
70 this.baseName = baseName;
71 this.maxLength = IdentifierGenerator.MaxProductIdentifierLength;
72 this.existingIdentifiers = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
73 this.possibleIdentifiers = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
74 }
75
76 /// <summary>
77 /// Gets or sets the maximum length for generated identifiers.
78 /// </summary>
79 /// <value>Maximum length for generated identifiers. (Default is 72.)</value>
80 public int MaxIdentifierLength
81 {
82 get { return this.maxLength; }
83 set { this.maxLength = value; }
84 }
85
86 /// <summary>
87 /// Index an existing identifier for collision detection.
88 /// </summary>
89 /// <param name="identifier">The identifier.</param>
90 public void IndexExistingIdentifier(string identifier)
91 {
92 if (null == identifier)
93 {
94 throw new ArgumentNullException("identifier");
95 }
96
97 this.existingIdentifiers[identifier] = null;
98 }
99
100 /// <summary>
101 /// Index a resource name for collision detection.
102 /// </summary>
103 /// <param name="name">The resource name.</param>
104 public void IndexName(string name)
105 {
106 if (null == name)
107 {
108 throw new ArgumentNullException("name");
109 }
110
111 string identifier = this.CreateIdentifier(name, 0);
112
113 if (this.possibleIdentifiers.ContainsKey(identifier))
114 {
115 this.possibleIdentifiers[identifier] = String.Empty;
116 }
117 else
118 {
119 this.possibleIdentifiers.Add(identifier, null);
120 }
121 }
122
123 /// <summary>
124 /// Get the identifier for the given resource name.
125 /// </summary>
126 /// <param name="name">The resource name.</param>
127 /// <returns>A legal MSI identifier.</returns>
128 [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "System.InvalidOperationException.#ctor(System.String)")]
129 public string GetIdentifier(string name)
130 {
131 if (null == name)
132 {
133 throw new ArgumentNullException("name");
134 }
135
136 for (int i = 0; i <= Int32.MaxValue; i++)
137 {
138 string identifier = this.CreateIdentifier(name, i);
139
140 if (this.existingIdentifiers.ContainsKey(identifier) || // already used
141 (0 == i && 0 != this.possibleIdentifiers.Count && null != this.possibleIdentifiers[identifier]) || // needs an index because its duplicated
142 (0 != i && this.possibleIdentifiers.ContainsKey(identifier))) // collides with another possible identifier
143 {
144 continue;
145 }
146 else // use this identifier
147 {
148 this.existingIdentifiers.Add(identifier, null);
149
150 return identifier;
151 }
152 }
153
154 throw new InvalidOperationException(WixStrings.EXP_CouldnotFileUniqueIDForResourceName);
155 }
156
157 /// <summary>
158 /// Create a legal MSI identifier from a resource name and an index.
159 /// </summary>
160 /// <param name="name">The name of the resource for which an identifier should be created.</param>
161 /// <param name="index">An index to append to the end of the identifier to make it unique.</param>
162 /// <returns>A legal MSI identifier.</returns>
163 public string CreateIdentifier(string name, int index)
164 {
165 if (null == name)
166 {
167 throw new ArgumentNullException("name");
168 }
169
170 StringBuilder identifier = new StringBuilder();
171
172 // Convert the name to a standard MSI identifier
173 identifier.Append(Common.GetIdentifierFromName(name));
174
175 // no legal identifier characters were found, use the base id instead
176 if (0 == identifier.Length)
177 {
178 identifier.Append(this.baseName);
179 }
180
181 // truncate the identifier if it's too long (reserve 3 characters for up to 99 collisions)
182 int adjustedMaxLength = this.MaxIdentifierLength - (index != 0 ? 3 : 0);
183 if (adjustedMaxLength < identifier.Length)
184 {
185 identifier.Length = adjustedMaxLength;
186 }
187
188 // if the index is not zero, then append it to the identifier name
189 if (0 != index)
190 {
191 identifier.AppendFormat("_{0}", index);
192 }
193
194 return identifier.ToString();
195 }
196 }
197 }
198}
diff --git a/src/WixToolset.Core/Extensibility/ValidatorExtension.cs b/src/WixToolset.Core/Extensibility/ValidatorExtension.cs
new file mode 100644
index 00000000..44ec3106
--- /dev/null
+++ b/src/WixToolset.Core/Extensibility/ValidatorExtension.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.Extensibility
4{
5 using System;
6 using System.Collections;
7 using WixToolset.Data;
8
9 /// <summary>
10 /// Base class for creating a validator extension. This default implementation
11 /// will fire and event with the ICE name and description.
12 /// </summary>
13 public class ValidatorExtension : IMessageHandler
14 {
15 private string databaseFile;
16 private Hashtable indexedSourceLineNumbers;
17 private Output output;
18 private SourceLineNumber sourceLineNumbers;
19
20 /// <summary>
21 /// Instantiate a new <see cref="ValidatorExtension"/>.
22 /// </summary>
23 public ValidatorExtension()
24 {
25 }
26
27 /// <summary>
28 /// Gets or sets the path to the database to validate.
29 /// </summary>
30 /// <value>The path to the database to validate.</value>
31 public string DatabaseFile
32 {
33 get { return this.databaseFile; }
34 set { this.databaseFile = value; }
35 }
36
37 /// <summary>
38 /// Gets or sets the <see cref="Output"/> for finding source line information.
39 /// </summary>
40 /// <value>The <see cref="Output"/> for finding source line information.</value>
41 public Output Output
42 {
43 get { return this.output; }
44 set { this.output = value; }
45 }
46
47 /// <summary>
48 /// Called at the beginning of the validation of a database file.
49 /// </summary>
50 /// <remarks>
51 /// <para>The <see cref="Validator"/> will set
52 /// <see cref="DatabaseFile"/> before calling InitializeValidator.</para>
53 /// <para><b>Notes to Inheritors:</b> When overriding
54 /// <b>InitializeValidator</b> in a derived class, be sure to call
55 /// the base class's <b>InitializeValidator</b> to thoroughly
56 /// initialize the extension.</para>
57 /// </remarks>
58 public virtual void InitializeValidator()
59 {
60 if (this.databaseFile != null)
61 {
62 this.sourceLineNumbers = new SourceLineNumber(databaseFile);
63 }
64 }
65
66 /// <summary>
67 /// Called at the end of the validation of a database file.
68 /// </summary>
69 /// <remarks>
70 /// <para>The default implementation will nullify source lines.</para>
71 /// <para><b>Notes to Inheritors:</b> When overriding
72 /// <b>FinalizeValidator</b> in a derived class, be sure to call
73 /// the base class's <b>FinalizeValidator</b> to thoroughly
74 /// finalize the extension.</para>
75 /// </remarks>
76 public virtual void FinalizeValidator()
77 {
78 this.sourceLineNumbers = null;
79 }
80
81 /// <summary>
82 /// Logs a message from the <see cref="Validator"/>.
83 /// </summary>
84 /// <param name="message">A <see cref="String"/> of tab-delmited tokens
85 /// in the validation message.</param>
86 public virtual void Log(string message)
87 {
88 this.Log(message, null);
89 }
90
91 /// <summary>
92 /// Logs a message from the <see cref="Validator"/>.
93 /// </summary>
94 /// <param name="message">A <see cref="String"/> of tab-delmited tokens
95 /// in the validation message.</param>
96 /// <param name="action">The name of the action to which the message
97 /// belongs.</param>
98 /// <exception cref="ArgumentNullException">The message cannot be null.
99 /// </exception>
100 /// <exception cref="WixException">The message does not contain four (4)
101 /// or more tab-delimited tokens.</exception>
102 /// <remarks>
103 /// <para><paramref name="message"/> a tab-delimited set of tokens,
104 /// formatted according to Windows Installer guidelines for ICE
105 /// message. The following table lists what each token by index
106 /// should mean.</para>
107 /// <para><paramref name="action"/> a name that represents the ICE
108 /// action that was executed (e.g. 'ICE08').</para>
109 /// <list type="table">
110 /// <listheader>
111 /// <term>Index</term>
112 /// <description>Description</description>
113 /// </listheader>
114 /// <item>
115 /// <term>0</term>
116 /// <description>Name of the ICE.</description>
117 /// </item>
118 /// <item>
119 /// <term>1</term>
120 /// <description>Message type. See the following list.</description>
121 /// </item>
122 /// <item>
123 /// <term>2</term>
124 /// <description>Detailed description.</description>
125 /// </item>
126 /// <item>
127 /// <term>3</term>
128 /// <description>Help URL or location.</description>
129 /// </item>
130 /// <item>
131 /// <term>4</term>
132 /// <description>Table name.</description>
133 /// </item>
134 /// <item>
135 /// <term>5</term>
136 /// <description>Column name.</description>
137 /// </item>
138 /// <item>
139 /// <term>6</term>
140 /// <description>This and remaining fields are primary keys
141 /// to identify a row.</description>
142 /// </item>
143 /// </list>
144 /// <para>The message types are one of the following value.</para>
145 /// <list type="table">
146 /// <listheader>
147 /// <term>Value</term>
148 /// <description>Message Type</description>
149 /// </listheader>
150 /// <item>
151 /// <term>0</term>
152 /// <description>Failure message reporting the failure of the
153 /// ICE custom action.</description>
154 /// </item>
155 /// <item>
156 /// <term>1</term>
157 /// <description>Error message reporting database authoring that
158 /// case incorrect behavior.</description>
159 /// </item>
160 /// <item>
161 /// <term>2</term>
162 /// <description>Warning message reporting database authoring that
163 /// causes incorrect behavior in certain cases. Warnings can also
164 /// report unexpected side-effects of database authoring.
165 /// </description>
166 /// </item>
167 /// <item>
168 /// <term>3</term>
169 /// <description>Informational message.</description>
170 /// </item>
171 /// </list>
172 /// </remarks>
173 public virtual void Log(string message, string action)
174 {
175 if (message == null)
176 {
177 throw new ArgumentNullException("message");
178 }
179
180 string[] messageParts = message.Split('\t');
181 if (3 > messageParts.Length)
182 {
183 if (null == action)
184 {
185 throw new WixException(WixErrors.UnexpectedExternalUIMessage(message));
186 }
187 else
188 {
189 throw new WixException(WixErrors.UnexpectedExternalUIMessage(message, action));
190 }
191 }
192
193 SourceLineNumber messageSourceLineNumbers = null;
194 if (6 < messageParts.Length)
195 {
196 string[] primaryKeys = new string[messageParts.Length - 6];
197
198 Array.Copy(messageParts, 6, primaryKeys, 0, primaryKeys.Length);
199
200 messageSourceLineNumbers = this.GetSourceLineNumbers(messageParts[4], primaryKeys);
201 }
202 else // use the file name as the source line information
203 {
204 messageSourceLineNumbers = this.sourceLineNumbers;
205 }
206
207 switch (messageParts[1])
208 {
209 case "0":
210 case "1":
211 this.OnMessage(WixErrors.ValidationError(messageSourceLineNumbers, messageParts[0], messageParts[2]));
212 break;
213 case "2":
214 this.OnMessage(WixWarnings.ValidationWarning(messageSourceLineNumbers, messageParts[0], messageParts[2]));
215 break;
216 case "3":
217 this.OnMessage(WixVerboses.ValidationInfo(messageParts[0], messageParts[2]));
218 break;
219 default:
220 throw new WixException(WixErrors.InvalidValidatorMessageType(messageParts[1]));
221 }
222 }
223
224 /// <summary>
225 /// Gets the source line information (if available) for a row by its table name and primary key.
226 /// </summary>
227 /// <param name="tableName">The table name of the row.</param>
228 /// <param name="primaryKeys">The primary keys of the row.</param>
229 /// <returns>The source line number information if found; null otherwise.</returns>
230 protected SourceLineNumber GetSourceLineNumbers(string tableName, string[] primaryKeys)
231 {
232 // source line information only exists if an output file was supplied
233 if (null != this.output)
234 {
235 // index the source line information if it hasn't been indexed already
236 if (null == this.indexedSourceLineNumbers)
237 {
238 this.indexedSourceLineNumbers = new Hashtable();
239
240 // index each real table
241 foreach (Table table in this.output.Tables)
242 {
243 // skip unreal tables
244 if (table.Definition.Unreal)
245 {
246 continue;
247 }
248
249 // index each row
250 foreach (Row row in table.Rows)
251 {
252 // skip rows that don't contain source line information
253 if (null == row.SourceLineNumbers)
254 {
255 continue;
256 }
257
258 // index the row using its table name and primary key
259 string primaryKey = row.GetPrimaryKey(';');
260 if (null != primaryKey)
261 {
262 string key = String.Concat(table.Name, ":", primaryKey);
263
264 if (this.indexedSourceLineNumbers.ContainsKey(key))
265 {
266 this.OnMessage(WixWarnings.DuplicatePrimaryKey(row.SourceLineNumbers, primaryKey, table.Name));
267 }
268 else
269 {
270 this.indexedSourceLineNumbers.Add(key, row.SourceLineNumbers);
271 }
272 }
273 }
274 }
275 }
276
277 return (SourceLineNumber)this.indexedSourceLineNumbers[String.Concat(tableName, ":", String.Join(";", primaryKeys))];
278 }
279
280 // use the file name as the source line information
281 return this.sourceLineNumbers;
282 }
283
284 /// <summary>
285 /// Sends a message to the <see cref="Message"/> delegate if there is one.
286 /// </summary>
287 /// <param name="e">Message event arguments.</param>
288 /// <remarks>
289 /// <para><b>Notes to Inheritors:</b> When overriding <b>OnMessage</b>
290 /// in a derived class, be sure to call the base class's
291 /// <b>OnMessage</b> method so that registered delegates recieve
292 /// the event.</para>
293 /// </remarks>
294 public virtual void OnMessage(MessageEventArgs e)
295 {
296 Messaging.Instance.OnMessage(e);
297 }
298 }
299}
diff --git a/src/WixToolset.Core/ExtensionManager.cs b/src/WixToolset.Core/ExtensionManager.cs
new file mode 100644
index 00000000..45cb65ec
--- /dev/null
+++ b/src/WixToolset.Core/ExtensionManager.cs
@@ -0,0 +1,110 @@
1// Copyright (c) .NET Foundation and 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.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 using System.Reflection;
10 using WixToolset.Data;
11
12 public class ExtensionManager
13 {
14 private List<Assembly> extensionAssemblies = new List<Assembly>();
15
16 /// <summary>
17 /// Loads an assembly from a type description string.
18 /// </summary>
19 /// <param name="extension">The assembly type description string.</param>
20 /// <returns>The loaded assembly. This assembly can be ignored since the extension manager maintains the list of loaded assemblies internally.</returns>
21 /// <remarks>
22 /// <paramref name="extension"/> can be in several different forms:
23 /// <list type="number">
24 /// <item><term>AssemblyName (MyAssembly, Version=1.3.0.0, Culture=neutral, PublicKeyToken=b17a5c561934e089)</term></item>
25 /// <item><term>Absolute path to an assembly (C:\MyExtensions\ExtensionAssembly.dll)</term></item>
26 /// <item><term>Filename of an assembly in the application directory (ExtensionAssembly.dll)</term></item>
27 /// <item><term>Relative path to an assembly (..\..\MyExtensions\ExtensionAssembly.dll)</term></item>
28 /// </list>
29 /// </remarks>
30 public Assembly Load(string extension)
31 {
32 string assemblyName = extension;
33 Assembly assembly;
34
35 // Absolute path to an assembly which means only "load from" will work even though we'd prefer to
36 // use Assembly.Load (see the documentation for Assembly.LoadFrom why).
37 if (Path.IsPathRooted(assemblyName))
38 {
39 assembly = ExtensionManager.ExtensionLoadFrom(assemblyName);
40 }
41 else if (ExtensionManager.TryExtensionLoad(assemblyName, out assembly))
42 {
43 // Loaded the assembly by name from the probing path.
44 }
45 else if (ExtensionManager.TryExtensionLoad(Path.GetFileNameWithoutExtension(assemblyName), out assembly))
46 {
47 // Loaded the assembly by filename alone along the probing path.
48 }
49 else // relative path to an assembly
50 {
51 // We want to use Assembly.Load when we can because it has some benefits over Assembly.LoadFrom
52 // (see the documentation for Assembly.LoadFrom). However, it may fail when the path is a relative
53 // path, so we should try Assembly.LoadFrom one last time. We could have detected a directory
54 // separator character and used Assembly.LoadFrom directly, but dealing with path canonicalization
55 // issues is something we don't want to deal with if we don't have to.
56 assembly = ExtensionManager.ExtensionLoadFrom(assemblyName);
57 }
58
59 this.extensionAssemblies.Add(assembly);
60 return assembly;
61 }
62
63 /// <summary>
64 /// Creates extension of specified type from assemblies loaded into the extension manager.
65 /// </summary>
66 /// <typeparam name="T">Type of extension to create.</typeparam>
67 /// <returns>Extensions created of the specified type.</returns>
68 public IEnumerable<T> Create<T>() where T : class
69 {
70 var extensionType = typeof(T);
71 var types = this.extensionAssemblies.SelectMany(a => a.GetTypes().Where(t => !t.IsAbstract && !t.IsInterface && extensionType.IsAssignableFrom(t)));
72 return types.Select(t => (T)Activator.CreateInstance(t)).ToList();
73 }
74
75 private static Assembly ExtensionLoadFrom(string assemblyName)
76 {
77 try
78 {
79 return Assembly.LoadFrom(assemblyName);
80 }
81 catch (Exception e)
82 {
83 throw new WixException(WixErrors.InvalidExtension(assemblyName, e.Message), e);
84 }
85 }
86
87 private static bool TryExtensionLoad(string assemblyName, out Assembly assembly)
88 {
89 try
90 {
91 assembly = Assembly.Load(assemblyName);
92 return true;
93 }
94 catch (IOException innerE)
95 {
96 if (innerE is FileLoadException || innerE is FileNotFoundException)
97 {
98 assembly = null;
99 return false;
100 }
101
102 throw new WixException(WixErrors.InvalidExtension(assemblyName, innerE.Message), innerE);
103 }
104 catch (Exception e)
105 {
106 throw new WixException(WixErrors.InvalidExtension(assemblyName, e.Message), e);
107 }
108 }
109 }
110}
diff --git a/src/WixToolset.Core/GlobalSuppressions.cs b/src/WixToolset.Core/GlobalSuppressions.cs
new file mode 100644
index 00000000..4b283c52
--- /dev/null
+++ b/src/WixToolset.Core/GlobalSuppressions.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
3[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "WixToolset.Cab")]
4[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue", Scope = "type", Target = "WixToolset.Msi.InstallMessage")]
5[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue", Scope = "type", Target = "WixToolset.Msi.InstallUILevels")]
6[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1027:MarkEnumsWithFlags", Scope = "type", Target = "WixToolset.Msi.OpenDatabase")]
7[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors")]
8[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1036:OverrideMethodsOnComparableTypes")]
9
10[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
11
12// .NET 2.0 requirement
13[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Scope = "member", Target = "WixToolset.Intermediate.Load(System.String,WixToolset.TableDefinitionCollection,System.Boolean,System.Boolean):WixToolset.Intermediate")]
14[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Scope = "member", Target = "WixToolset.Library.Load(System.IO.Stream,System.Uri,WixToolset.TableDefinitionCollection,System.Boolean,System.Boolean):WixToolset.Library")]
15[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Scope = "member", Target = "WixToolset.Localization.Load(System.IO.Stream,System.Uri,WixToolset.TableDefinitionCollection,System.Boolean):WixToolset.Localization")]
16[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Scope = "member", Target = "WixToolset.Output.Load(System.IO.Stream,System.Uri,System.Boolean,System.Boolean):WixToolset.Output")]
17
18// .NET 2.0 requirement
19[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources", Scope = "member", Target = "WixToolset.Cab.WixCreateCab.handle")]
20
21[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Msi", Scope = "namespace", Target = "WixToolset.Msi")]
22[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "wix")]
23[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "wix")]
24[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "wx")]
diff --git a/src/WixToolset.Core/Harvester.cs b/src/WixToolset.Core/Harvester.cs
new file mode 100644
index 00000000..0f79a2d6
--- /dev/null
+++ b/src/WixToolset.Core/Harvester.cs
@@ -0,0 +1,80 @@
1// Copyright (c) .NET Foundation and 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.Collections;
7 using System.Diagnostics.CodeAnalysis;
8 using WixToolset.Data;
9 using Wix = WixToolset.Data.Serialize;
10
11 /// <summary>
12 /// The WiX Toolset harvester.
13 /// </summary>
14 public sealed class Harvester
15 {
16 private HarvesterExtension harvesterExtension;
17
18 /// <summary>
19 /// Gets or sets the harvester core for the extension.
20 /// </summary>
21 /// <value>The harvester core for the extension.</value>
22 public IHarvesterCore Core { get; set; }
23
24 /// <summary>
25 /// Gets or sets the extension.
26 /// </summary>
27 /// <value>The extension.</value>
28 [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "System.InvalidOperationException.#ctor(System.String)")]
29 public HarvesterExtension Extension
30 {
31 get
32 {
33 return this.harvesterExtension;
34 }
35 set
36 {
37 if (null != this.harvesterExtension)
38 {
39 throw new InvalidOperationException(WixStrings.EXP_MultipleHarvesterExtensionsSpecified);
40 }
41
42 this.harvesterExtension = value;
43 }
44 }
45
46 /// <summary>
47 /// Harvest wix authoring.
48 /// </summary>
49 /// <param name="argument">The argument for harvesting.</param>
50 /// <returns>The harvested wix authoring.</returns>
51 public Wix.Wix Harvest(string argument)
52 {
53 if (null == argument)
54 {
55 throw new ArgumentNullException("argument");
56 }
57
58 if (null == this.harvesterExtension)
59 {
60 throw new WixException(WixErrors.HarvestTypeNotFound());
61 }
62
63 this.harvesterExtension.Core = this.Core;
64
65 Wix.Fragment[] fragments = this.harvesterExtension.Harvest(argument);
66 if (null == fragments || 0 == fragments.Length)
67 {
68 return null;
69 }
70
71 Wix.Wix wix = new Wix.Wix();
72 foreach (Wix.Fragment fragment in fragments)
73 {
74 wix.AddChild(fragment);
75 }
76
77 return wix;
78 }
79 }
80}
diff --git a/src/WixToolset.Core/HarvesterCore.cs b/src/WixToolset.Core/HarvesterCore.cs
new file mode 100644
index 00000000..66a693f2
--- /dev/null
+++ b/src/WixToolset.Core/HarvesterCore.cs
@@ -0,0 +1,103 @@
1// Copyright (c) .NET Foundation and 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.Diagnostics.CodeAnalysis;
7 using System.IO;
8 using WixToolset.Data;
9 using Wix = WixToolset.Data.Serialize;
10
11 /// <summary>
12 /// The WiX Toolset harvester core.
13 /// </summary>
14 public sealed class HarvesterCore : IHarvesterCore
15 {
16 private string extensionArgument;
17 private string rootDirectory;
18
19 /// <summary>
20 /// Gets whether the harvester core encountered an error while processing.
21 /// </summary>
22 /// <value>Flag if core encountered an error during processing.</value>
23 public bool EncounteredError
24 {
25 get { return Messaging.Instance.EncounteredError; }
26 }
27
28 /// <summary>
29 /// Gets or sets the value of the extension argument passed to heat.
30 /// </summary>
31 /// <value>The extension argument.</value>
32 public string ExtensionArgument
33 {
34 get { return this.extensionArgument; }
35 set { this.extensionArgument = value; }
36 }
37
38 /// <summary>
39 /// Gets or sets the value of the root directory that is being harvested.
40 /// </summary>
41 /// <value>The root directory being harvested.</value>
42 public string RootDirectory
43 {
44 get { return this.rootDirectory; }
45 set { this.rootDirectory = value; }
46 }
47
48 /// <summary>
49 /// Create an identifier based on passed file name
50 /// </summary>
51 /// <param name="name">File name to generate identifer from</param>
52 /// <returns></returns>
53 public string CreateIdentifierFromFilename(string filename)
54 {
55 return Common.GetIdentifierFromName(filename);
56 }
57
58 /// <summary>
59 /// Generate an identifier by hashing data from the row.
60 /// </summary>
61 /// <param name="prefix">Three letter or less prefix for generated row identifier.</param>
62 /// <param name="args">Information to hash.</param>
63 /// <returns>The generated identifier.</returns>
64 [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "System.InvalidOperationException.#ctor(System.String)")]
65 public string GenerateIdentifier(string prefix, params string[] args)
66 {
67 return Common.GenerateIdentifier(prefix, args);
68 }
69
70 /// <summary>
71 /// Sends a message to the message delegate if there is one.
72 /// </summary>
73 /// <param name="mea">Message event arguments.</param>
74 public void OnMessage(MessageEventArgs mea)
75 {
76 Messaging.Instance.OnMessage(mea);
77 }
78
79 /// <summary>
80 /// Resolves a file's path if the Wix.File.Source value starts with "SourceDir\".
81 /// </summary>
82 /// <param name="fileSource">The Wix.File.Source value with "SourceDir\".</param>
83 /// <returns>The full path of the file.</returns>
84 public string ResolveFilePath(string fileSource)
85 {
86 if (fileSource.StartsWith("SourceDir\\", StringComparison.Ordinal))
87 {
88 string file = Path.GetFullPath(this.rootDirectory);
89 if (File.Exists(file))
90 {
91 return file;
92 }
93 else
94 {
95 fileSource = fileSource.Substring(10);
96 fileSource = Path.Combine(Path.GetFullPath(this.rootDirectory), fileSource);
97 }
98 }
99
100 return fileSource;
101 }
102 }
103}
diff --git a/src/WixToolset.Core/HeatCore.cs b/src/WixToolset.Core/HeatCore.cs
new file mode 100644
index 00000000..5c5defe8
--- /dev/null
+++ b/src/WixToolset.Core/HeatCore.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.Tools
4{
5 using System;
6 using System.Reflection;
7 using WixToolset.Data;
8 using WixToolset.Extensibilty;
9 using Wix = WixToolset.Data.Serialize;
10
11 /// <summary>
12 /// The WiX Toolset Harvester application core.
13 /// </summary>
14 public sealed class HeatCore : IHeatCore, IMessageHandler
15 {
16 private Harvester harvester;
17 private Mutator mutator;
18
19 /// <summary>
20 /// Instantiates a new HeatCore.
21 /// </summary>
22 /// <param name="messageHandler">The message handler for the core.</param>
23 public HeatCore()
24 {
25 this.harvester = new Harvester();
26 this.mutator = new Mutator();
27 }
28
29 /// <summary>
30 /// Gets whether the mutator core encountered an error while processing.
31 /// </summary>
32 /// <value>Flag if core encountered an error during processing.</value>
33 public bool EncounteredError
34 {
35 get { return Messaging.Instance.EncounteredError; }
36 }
37
38 /// <summary>
39 /// Gets the harvester.
40 /// </summary>
41 /// <value>The harvester.</value>
42 public Harvester Harvester
43 {
44 get { return this.harvester; }
45 }
46
47 /// <summary>
48 /// Gets the mutator.
49 /// </summary>
50 /// <value>The mutator.</value>
51 public Mutator Mutator
52 {
53 get { return this.mutator; }
54 }
55
56 /// <summary>
57 /// Sends a message to the message delegate if there is one.
58 /// </summary>
59 /// <param name="mea">Message event arguments.</param>
60 public void OnMessage(MessageEventArgs mea)
61 {
62 Messaging.Instance.OnMessage(mea);
63 }
64 }
65}
diff --git a/src/WixToolset.Core/ICommand.cs b/src/WixToolset.Core/ICommand.cs
new file mode 100644
index 00000000..957f735b
--- /dev/null
+++ b/src/WixToolset.Core/ICommand.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
3namespace WixToolset
4{
5 internal interface ICommand
6 {
7 void Execute();
8 }
9}
diff --git a/src/WixToolset.Core/IfDefEventHandler.cs b/src/WixToolset.Core/IfDefEventHandler.cs
new file mode 100644
index 00000000..37a7206e
--- /dev/null
+++ b/src/WixToolset.Core/IfDefEventHandler.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 WixToolset
4{
5 using System;
6 using System.Text;
7 using WixToolset.Data;
8
9 public delegate void IfDefEventHandler(object sender, IfDefEventArgs e);
10
11 public class IfDefEventArgs : EventArgs
12 {
13 private SourceLineNumber sourceLineNumbers;
14 private bool isIfDef;
15 private bool isDefined;
16 private string variableName;
17
18 public IfDefEventArgs(SourceLineNumber sourceLineNumbers, bool isIfDef, bool isDefined, string variableName)
19 {
20 this.sourceLineNumbers = sourceLineNumbers;
21 this.isIfDef = isIfDef;
22 this.isDefined = isDefined;
23 this.variableName = variableName;
24 }
25
26 public SourceLineNumber SourceLineNumbers
27 {
28 get { return this.sourceLineNumbers; }
29 }
30
31 public bool IsDefined
32 {
33 get { return this.isDefined; }
34 }
35
36 public bool IsIfDef
37 {
38 get { return this.isIfDef; }
39 }
40
41 public string VariableName
42 {
43 get { return this.variableName; }
44 }
45 }
46}
diff --git a/src/WixToolset.Core/IncludedFileEventHandler.cs b/src/WixToolset.Core/IncludedFileEventHandler.cs
new file mode 100644
index 00000000..57527e5c
--- /dev/null
+++ b/src/WixToolset.Core/IncludedFileEventHandler.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
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="ea">Arguments for the included file event.</param>
13 public delegate void IncludedFileEventHandler(object sender, IncludedFileEventArgs e);
14
15 /// <summary>
16 /// Event args for included file event.
17 /// </summary>
18 public class IncludedFileEventArgs : EventArgs
19 {
20 private SourceLineNumber sourceLineNumbers;
21 private string fullName;
22
23 /// <summary>
24 /// Creates a new IncludedFileEventArgs.
25 /// </summary>
26 /// <param name="sourceLineNumbers">Source line numbers for the included file.</param>
27 /// <param name="fullName">The full path of the included file.</param>
28 public IncludedFileEventArgs(SourceLineNumber sourceLineNumbers, string fullName)
29 {
30 this.sourceLineNumbers = sourceLineNumbers;
31 this.fullName = fullName;
32 }
33
34 /// <summary>
35 /// Gets the full path of the included file.
36 /// </summary>
37 /// <value>The full path of the included file.</value>
38 public string FullName
39 {
40 get { return this.fullName; }
41 }
42
43 /// <summary>
44 /// Gets the source line numbers.
45 /// </summary>
46 /// <value>The source line numbers.</value>
47 public SourceLineNumber SourceLineNumbers
48 {
49 get { return this.sourceLineNumbers; }
50 }
51 }
52}
diff --git a/src/WixToolset.Core/Inscriber.cs b/src/WixToolset.Core/Inscriber.cs
new file mode 100644
index 00000000..5b467ec1
--- /dev/null
+++ b/src/WixToolset.Core/Inscriber.cs
@@ -0,0 +1,457 @@
1// Copyright (c) .NET Foundation and 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.Generic;
8 using System.Globalization;
9 using System.IO;
10 using System.Runtime.InteropServices;
11 using System.Security.Cryptography.X509Certificates;
12 using WixToolset.Bind.Bundles;
13 using WixToolset.Data;
14 using WixToolset.Msi;
15 using WixToolset.Core.Native;
16
17 /// <summary>
18 /// Converts a wixout representation of an MSM database into a ComponentGroup the form of WiX source.
19 /// </summary>
20 public sealed class Inscriber : IMessageHandler
21 {
22 // private TempFileCollection tempFiles;
23 private TableDefinitionCollection tableDefinitions;
24
25 public Inscriber()
26 {
27 this.tableDefinitions = new TableDefinitionCollection(WindowsInstallerStandard.GetTableDefinitions());
28 }
29
30 /// <summary>
31 /// Gets or sets the temp files collection.
32 /// </summary>
33 /// <value>The temp files collection.</value>
34 // public TempFileCollection TempFiles
35 // {
36 // get { return this.tempFiles; }
37 // set { this.tempFiles = value; }
38 // }
39
40 /// <summary>
41 /// Gets or sets the path to the temp files location.
42 /// </summary>
43 /// <value>The path to the temp files location.</value>
44 public string TempFilesLocation
45 {
46 get
47 {
48 // if (null == this.tempFiles)
49 // {
50 // return null;
51 // }
52 // else
53 // {
54 // return this.tempFiles.BasePath;
55 // }
56 return Path.GetTempPath();
57 }
58 // set
59 // {
60 // this.DeleteTempFiles();
61
62 // if (null == value)
63 // {
64 // this.tempFiles = new TempFileCollection();
65 // }
66 // else
67 // {
68 // this.tempFiles = new TempFileCollection(value);
69 // }
70
71 // // ensure the base path exists
72 // Directory.CreateDirectory(this.tempFiles.BasePath);
73 // }
74 }
75
76 /// <summary>
77 /// Extracts engine from attached container and updates engine with detached container signatures.
78 /// </summary>
79 /// <param name="bundleFile">Bundle with attached container.</param>
80 /// <param name="outputFile">Bundle engine only.</param>
81 /// <returns>True if bundle was updated.</returns>
82 public bool InscribeBundleEngine(string bundleFile, string outputFile)
83 {
84 string tempFile = Path.Combine(this.TempFilesLocation, "bundle_engine_unsigned.exe");
85
86 using (BurnReader reader = BurnReader.Open(bundleFile))
87 using (FileStream writer = File.Open(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read | FileShare.Delete))
88 {
89 reader.Stream.Seek(0, SeekOrigin.Begin);
90
91 byte[] buffer = new byte[4 * 1024];
92 int total = 0;
93 int read = 0;
94 do
95 {
96 read = Math.Min(buffer.Length, (int)reader.EngineSize - total);
97
98 read = reader.Stream.Read(buffer, 0, read);
99 writer.Write(buffer, 0, read);
100
101 total += read;
102 } while (total < reader.EngineSize && 0 < read);
103
104 if (total != reader.EngineSize)
105 {
106 throw new InvalidOperationException("Failed to copy engine out of bundle.");
107 }
108
109 // TODO: update writer with detached container signatures.
110 }
111
112 Directory.CreateDirectory(Path.GetDirectoryName(outputFile));
113 if (File.Exists(outputFile))
114 {
115 File.Delete(outputFile);
116 }
117 File.Move(tempFile, outputFile);
118 WixToolset.Core.Native.NativeMethods.ResetAcls(new string[] { outputFile }, 1);
119
120 return true;
121 }
122
123 /// <summary>
124 /// Updates engine with attached container information and adds attached container again.
125 /// </summary>
126 /// <param name="bundleFile">Bundle with attached container.</param>
127 /// <param name="signedEngineFile">Signed bundle engine.</param>
128 /// <param name="outputFile">Signed engine with attached container.</param>
129 /// <returns>True if bundle was updated.</returns>
130 public bool InscribeBundle(string bundleFile, string signedEngineFile, string outputFile)
131 {
132 bool inscribed = false;
133 string tempFile = Path.Combine(this.TempFilesLocation, "bundle_engine_signed.exe");
134
135 using (BurnReader reader = BurnReader.Open(bundleFile))
136 {
137 File.Copy(signedEngineFile, tempFile, true);
138
139 // If there was an attached container on the original (unsigned) bundle, put it back.
140 if (reader.AttachedContainerSize > 0)
141 {
142 reader.Stream.Seek(reader.AttachedContainerAddress, SeekOrigin.Begin);
143
144 using (BurnWriter writer = BurnWriter.Open(tempFile))
145 {
146 writer.RememberThenResetSignature();
147 writer.AppendContainer(reader.Stream, reader.AttachedContainerSize, BurnCommon.Container.Attached);
148 inscribed = true;
149 }
150 }
151 }
152
153 Directory.CreateDirectory(Path.GetDirectoryName(outputFile));
154 if (File.Exists(outputFile))
155 {
156 File.Delete(outputFile);
157 }
158 File.Move(tempFile, outputFile);
159 WixToolset.Core.Native.NativeMethods.ResetAcls(new string[] { outputFile }, 1);
160
161 return inscribed;
162 }
163
164 /// <summary>
165 /// Updates database with signatures from external cabinets.
166 /// </summary>
167 /// <param name="databaseFile">Path to MSI database.</param>
168 /// <param name="outputFile">Ouput for updated MSI database.</param>
169 /// <param name="tidy">Clean up files.</param>
170 /// <returns>True if database is updated.</returns>
171 public bool InscribeDatabase(string databaseFile, string outputFile, bool tidy)
172 {
173 // 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
174 bool foundUnsignedExternals = false;
175 bool shouldCommit = false;
176
177 FileAttributes attributes = File.GetAttributes(databaseFile);
178 if (FileAttributes.ReadOnly == (attributes & FileAttributes.ReadOnly))
179 {
180 this.OnMessage(WixErrors.ReadOnlyOutputFile(databaseFile));
181 return shouldCommit;
182 }
183
184 using (Database database = new Database(databaseFile, OpenDatabase.Transact))
185 {
186 // Just use the English codepage, because the tables we're importing only have binary streams / MSI identifiers / other non-localizable content
187 int codepage = 1252;
188
189 // list of certificates for this database (hash/identifier)
190 Dictionary<string, string> certificates = new Dictionary<string, string>();
191
192 // Reset the in-memory tables for this new database
193 Table digitalSignatureTable = new Table(null, this.tableDefinitions["MsiDigitalSignature"]);
194 Table digitalCertificateTable = new Table(null, this.tableDefinitions["MsiDigitalCertificate"]);
195
196 // If any digital signature records exist that are not of the media type, preserve them
197 if (database.TableExists("MsiDigitalSignature"))
198 {
199 using (View digitalSignatureView = database.OpenExecuteView("SELECT `Table`, `SignObject`, `DigitalCertificate_`, `Hash` FROM `MsiDigitalSignature` WHERE `Table` <> 'Media'"))
200 {
201 while (true)
202 {
203 using (Record digitalSignatureRecord = digitalSignatureView.Fetch())
204 {
205 if (null == digitalSignatureRecord)
206 {
207 break;
208 }
209
210 Row digitalSignatureRow = null;
211 digitalSignatureRow = digitalSignatureTable.CreateRow(null);
212
213 string table = digitalSignatureRecord.GetString(0);
214 string signObject = digitalSignatureRecord.GetString(1);
215
216 digitalSignatureRow[0] = table;
217 digitalSignatureRow[1] = signObject;
218 digitalSignatureRow[2] = digitalSignatureRecord.GetString(2);
219
220 if (false == digitalSignatureRecord.IsNull(3))
221 {
222 // Export to a file, because the MSI API's require us to provide a file path on disk
223 string hashPath = Path.Combine(this.TempFilesLocation, "MsiDigitalSignature");
224 string hashFileName = string.Concat(table, ".", signObject, ".bin");
225
226 Directory.CreateDirectory(hashPath);
227 hashPath = Path.Combine(hashPath, hashFileName);
228
229 using (FileStream fs = File.Create(hashPath))
230 {
231 int bytesRead;
232 byte[] buffer = new byte[1024 * 4];
233
234 while (0 != (bytesRead = digitalSignatureRecord.GetStream(3, buffer, buffer.Length)))
235 {
236 fs.Write(buffer, 0, bytesRead);
237 }
238 }
239
240 digitalSignatureRow[3] = hashFileName;
241 }
242 }
243 }
244 }
245 }
246
247 // If any digital certificates exist, extract and preserve them
248 if (database.TableExists("MsiDigitalCertificate"))
249 {
250 using (View digitalCertificateView = database.OpenExecuteView("SELECT * FROM `MsiDigitalCertificate`"))
251 {
252 while (true)
253 {
254 using (Record digitalCertificateRecord = digitalCertificateView.Fetch())
255 {
256 if (null == digitalCertificateRecord)
257 {
258 break;
259 }
260
261 string certificateId = digitalCertificateRecord.GetString(1); // get the identifier of the certificate
262
263 // Export to a file, because the MSI API's require us to provide a file path on disk
264 string certPath = Path.Combine(this.TempFilesLocation, "MsiDigitalCertificate");
265 Directory.CreateDirectory(certPath);
266 certPath = Path.Combine(certPath, string.Concat(certificateId, ".cer"));
267
268 using (FileStream fs = File.Create(certPath))
269 {
270 int bytesRead;
271 byte[] buffer = new byte[1024 * 4];
272
273 while (0 != (bytesRead = digitalCertificateRecord.GetStream(2, buffer, buffer.Length)))
274 {
275 fs.Write(buffer, 0, bytesRead);
276 }
277 }
278
279 // Add it to our "add to MsiDigitalCertificate" table dictionary
280 Row digitalCertificateRow = digitalCertificateTable.CreateRow(null);
281 digitalCertificateRow[0] = certificateId;
282
283 // Now set the file path on disk where this binary stream will be picked up at import time
284 digitalCertificateRow[1] = string.Concat(certificateId, ".cer");
285
286 // Load the cert to get it's thumbprint
287 X509Certificate cert = X509Certificate.CreateFromCertFile(certPath);
288 X509Certificate2 cert2 = new X509Certificate2(cert);
289
290 certificates.Add(cert2.Thumbprint, certificateId);
291 }
292 }
293 }
294 }
295
296 using (View mediaView = database.OpenExecuteView("SELECT * FROM `Media`"))
297 {
298 while (true)
299 {
300 using (Record mediaRecord = mediaView.Fetch())
301 {
302 if (null == mediaRecord)
303 {
304 break;
305 }
306
307 X509Certificate2 cert2 = null;
308 Row digitalSignatureRow = null;
309
310 string cabName = mediaRecord.GetString(4); // get the name of the cab
311 // If there is no cabinet or it's an internal cab, skip it.
312 if (String.IsNullOrEmpty(cabName) || cabName.StartsWith("#", StringComparison.Ordinal))
313 {
314 continue;
315 }
316
317 string cabId = mediaRecord.GetString(1); // get the ID of the cab
318 string cabPath = Path.Combine(Path.GetDirectoryName(databaseFile), cabName);
319
320 // If the cabs aren't there, throw an error but continue to catch the other errors
321 if (!File.Exists(cabPath))
322 {
323 this.OnMessage(WixErrors.WixFileNotFound(cabPath));
324 continue;
325 }
326
327 try
328 {
329 // Get the certificate from the cab
330 X509Certificate signedFileCert = X509Certificate.CreateFromSignedFile(cabPath);
331 cert2 = new X509Certificate2(signedFileCert);
332 }
333 catch (System.Security.Cryptography.CryptographicException e)
334 {
335 uint HResult = unchecked((uint)Marshal.GetHRForException(e));
336
337 // If the file has no cert, continue, but flag that we found at least one so we can later give a warning
338 if (0x80092009 == HResult) // CRYPT_E_NO_MATCH
339 {
340 foundUnsignedExternals = true;
341 continue;
342 }
343
344 // todo: exactly which HRESULT corresponds to this issue?
345 // If it's one of these exact platforms, warn the user that it may be due to their OS.
346 if ((5 == Environment.OSVersion.Version.Major && 2 == Environment.OSVersion.Version.Minor) || // W2K3
347 (5 == Environment.OSVersion.Version.Major && 1 == Environment.OSVersion.Version.Minor)) // XP
348 {
349 this.OnMessage(WixErrors.UnableToGetAuthenticodeCertOfFileDownlevelOS(cabPath, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", HResult)));
350 }
351 else // otherwise, generic error
352 {
353 this.OnMessage(WixErrors.UnableToGetAuthenticodeCertOfFile(cabPath, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", HResult)));
354 }
355 }
356
357 // If we haven't added this cert to the MsiDigitalCertificate table, set it up to be added
358 if (!certificates.ContainsKey(cert2.Thumbprint))
359 {
360 // generate a stable identifier
361 string certificateGeneratedId = Common.GenerateIdentifier("cer", cert2.Thumbprint);
362
363 // Add it to our "add to MsiDigitalCertificate" table dictionary
364 Row digitalCertificateRow = digitalCertificateTable.CreateRow(null);
365 digitalCertificateRow[0] = certificateGeneratedId;
366
367 // Export to a file, because the MSI API's require us to provide a file path on disk
368 string certPath = Path.Combine(this.TempFilesLocation, "MsiDigitalCertificate");
369 Directory.CreateDirectory(certPath);
370 certPath = Path.Combine(certPath, string.Concat(cert2.Thumbprint, ".cer"));
371 File.Delete(certPath);
372
373 using (BinaryWriter writer = new BinaryWriter(File.Open(certPath, FileMode.Create)))
374 {
375 writer.Write(cert2.RawData);
376 writer.Close();
377 }
378
379 // Now set the file path on disk where this binary stream will be picked up at import time
380 digitalCertificateRow[1] = string.Concat(cert2.Thumbprint, ".cer");
381
382 certificates.Add(cert2.Thumbprint, certificateGeneratedId);
383 }
384
385 digitalSignatureRow = digitalSignatureTable.CreateRow(null);
386
387 digitalSignatureRow[0] = "Media";
388 digitalSignatureRow[1] = cabId;
389 digitalSignatureRow[2] = certificates[cert2.Thumbprint];
390 }
391 }
392 }
393
394 if (digitalCertificateTable.Rows.Count > 0)
395 {
396 database.ImportTable(codepage, digitalCertificateTable, this.TempFilesLocation, true);
397 shouldCommit = true;
398 }
399
400 if (digitalSignatureTable.Rows.Count > 0)
401 {
402 database.ImportTable(codepage, digitalSignatureTable, this.TempFilesLocation, true);
403 shouldCommit = true;
404 }
405
406 // TODO: if we created the table(s), then we should add the _Validation records for them.
407
408 certificates = null;
409
410 // If we did find external cabs but none of them were signed, give a warning
411 if (foundUnsignedExternals)
412 {
413 this.OnMessage(WixWarnings.ExternalCabsAreNotSigned(databaseFile));
414 }
415
416 if (shouldCommit)
417 {
418 database.Commit();
419 }
420 }
421
422 return shouldCommit;
423 }
424
425 /// <summary>
426 /// Cleans up the temp files used by the Inscriber.
427 /// </summary>
428 /// <returns>True if all files were deleted, false otherwise.</returns>
429 public bool DeleteTempFiles()
430 {
431#if REDO_IN_NETCORE
432 if (null == this.tempFiles)
433 {
434 return true; // no work to do
435 }
436 else
437 {
438 bool deleted = Common.DeleteTempFiles(this.TempFilesLocation, this);
439
440 if (deleted)
441 {
442 ((IDisposable)this.tempFiles).Dispose();
443 this.tempFiles = null; // temp files have been deleted, no need to remember this now
444 }
445
446 return deleted;
447 }
448#endif
449 return true;
450 }
451
452 public void OnMessage(MessageEventArgs e)
453 {
454 Messaging.Instance.OnMessage(e);
455 }
456 }
457}
diff --git a/src/WixToolset.Core/InspectorCore.cs b/src/WixToolset.Core/InspectorCore.cs
new file mode 100644
index 00000000..63b6af6e
--- /dev/null
+++ b/src/WixToolset.Core/InspectorCore.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 System;
6 using WixToolset.Data;
7 using WixToolset.Extensibility;
8
9 /// <summary>
10 /// Core facilities for inspector extensions.
11 /// </summary>
12 internal sealed class InspectorCore : IInspectorCore
13 {
14 /// <summary>
15 /// Gets whether an error occured.
16 /// </summary>
17 /// <value>Whether an error occured.</value>
18 public bool EncounteredError
19 {
20 get { return Messaging.Instance.EncounteredError; }
21 }
22
23 /// <summary>
24 /// Logs a message to the log handler.
25 /// </summary>
26 /// <param name="e">The <see cref="MessageEventArgs"/> that contains information to log.</param>
27 public void OnMessage(MessageEventArgs e)
28 {
29 Messaging.Instance.OnMessage(e);
30 }
31 }
32}
diff --git a/src/WixToolset.Core/Librarian.cs b/src/WixToolset.Core/Librarian.cs
new file mode 100644
index 00000000..daf53478
--- /dev/null
+++ b/src/WixToolset.Core/Librarian.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
4{
5 using System;
6 using System.Collections.Generic;
7 using WixToolset.Data;
8 using WixToolset.Extensibility;
9 using WixToolset.Link;
10
11 /// <summary>
12 /// Core librarian tool.
13 /// </summary>
14 public sealed class Librarian
15 {
16 /// <summary>
17 /// Instantiate a new Librarian class.
18 /// </summary>
19 public Librarian()
20 {
21 this.TableDefinitions = new TableDefinitionCollection(WindowsInstallerStandard.GetTableDefinitions());
22 }
23
24 /// <summary>
25 /// Gets table definitions used by this librarian.
26 /// </summary>
27 /// <value>Table definitions.</value>
28 public TableDefinitionCollection TableDefinitions { get; private set; }
29
30 /// <summary>
31 /// Adds an extension's data.
32 /// </summary>
33 /// <param name="extension">The extension data to add.</param>
34 public void AddExtensionData(IExtensionData extension)
35 {
36 if (null != extension.TableDefinitions)
37 {
38 foreach (TableDefinition tableDefinition in extension.TableDefinitions)
39 {
40 try
41 {
42 this.TableDefinitions.Add(tableDefinition);
43 }
44 catch (ArgumentException)
45 {
46 Messaging.Instance.OnMessage(WixErrors.DuplicateExtensionTable(extension.GetType().ToString(), tableDefinition.Name));
47 }
48 }
49 }
50 }
51
52 /// <summary>
53 /// Create a library by combining several intermediates (objects).
54 /// </summary>
55 /// <param name="sections">The sections to combine into a library.</param>
56 /// <returns>Returns the new library.</returns>
57 public Library Combine(IEnumerable<Section> sections)
58 {
59 Library library = new Library(sections);
60
61 this.Validate(library);
62
63 return (Messaging.Instance.EncounteredError ? null : library);
64 }
65
66 /// <summary>
67 /// Sends a message to the message delegate if there is one.
68 /// </summary>
69 /// <param name="mea">Message event arguments.</param>
70 public void OnMessage(MessageEventArgs e)
71 {
72 Messaging.Instance.OnMessage(e);
73 }
74
75 /// <summary>
76 /// Validate that a library contains one entry section and no duplicate symbols.
77 /// </summary>
78 /// <param name="library">Library to validate.</param>
79 private void Validate(Library library)
80 {
81 FindEntrySectionAndLoadSymbolsCommand find = new FindEntrySectionAndLoadSymbolsCommand(library.Sections);
82 find.Execute();
83
84 // TODO: Consider bringing this sort of verification back.
85 // foreach (Section section in library.Sections)
86 // {
87 // ResolveReferencesCommand resolve = new ResolveReferencesCommand(find.EntrySection, find.Symbols);
88 // resolve.Execute();
89 //
90 // ReportDuplicateResolvedSymbolErrorsCommand reportDupes = new ReportDuplicateResolvedSymbolErrorsCommand(find.SymbolsWithDuplicates, resolve.ResolvedSections);
91 // reportDupes.Execute();
92 // }
93 }
94 }
95}
diff --git a/src/WixToolset.Core/Link/ConnectToFeature.cs b/src/WixToolset.Core/Link/ConnectToFeature.cs
new file mode 100644
index 00000000..6e046b89
--- /dev/null
+++ b/src/WixToolset.Core/Link/ConnectToFeature.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.Link
4{
5 using System.Collections.Specialized;
6 using WixToolset.Data;
7
8 /// <summary>
9 /// Object that connects things (components/modules) to features.
10 /// </summary>
11 public sealed class ConnectToFeature
12 {
13 private Section section;
14 private string childId;
15
16 private string primaryFeature;
17 private bool explicitPrimaryFeature;
18 private StringCollection connectFeatures;
19
20 /// <summary>
21 /// Creates a new connect to feature.
22 /// </summary>
23 /// <param name="section">Section this connect belongs to.</param>
24 /// <param name="childId">Id of the child.</param>
25 public ConnectToFeature(Section section, string childId) :
26 this(section, childId, null, false)
27 {
28 }
29
30 /// <summary>
31 /// Creates a new connect to feature.
32 /// </summary>
33 /// <param name="section">Section this connect belongs to.</param>
34 /// <param name="childId">Id of the child.</param>
35 /// <param name="primaryFeature">Sets the primary feature for the connection.</param>
36 /// <param name="explicitPrimaryFeature">Sets if this is explicit primary.</param>
37 public ConnectToFeature(Section section, string childId, string primaryFeature, bool explicitPrimaryFeature)
38 {
39 this.section = section;
40 this.childId = childId;
41
42 this.primaryFeature = primaryFeature;
43 this.explicitPrimaryFeature = explicitPrimaryFeature;
44
45 this.connectFeatures = new StringCollection();
46 }
47
48 /// <summary>
49 /// Gets the section.
50 /// </summary>
51 /// <value>Section.</value>
52 public Section Section
53 {
54 get { return this.section; }
55 }
56
57 /// <summary>
58 /// Gets the child identifier.
59 /// </summary>
60 /// <value>The child identifier.</value>
61 public string ChildId
62 {
63 get { return this.childId; }
64 }
65
66 /// <summary>
67 /// Gets or sets if the flag for if the primary feature was set explicitly.
68 /// </summary>
69 /// <value>The flag for if the primary feature was set explicitly.</value>
70 public bool IsExplicitPrimaryFeature
71 {
72 get { return this.explicitPrimaryFeature; }
73 set { this.explicitPrimaryFeature = value; }
74 }
75
76 /// <summary>
77 /// Gets or sets the primary feature.
78 /// </summary>
79 /// <value>The primary feature.</value>
80 public string PrimaryFeature
81 {
82 get { return this.primaryFeature; }
83 set { this.primaryFeature = value; }
84 }
85
86 /// <summary>
87 /// Gets the features connected to.
88 /// </summary>
89 /// <value>Features connected to.</value>
90 public StringCollection ConnectFeatures
91 {
92 get { return this.connectFeatures; }
93 }
94 }
95}
diff --git a/src/WixToolset.Core/Link/ConnectToFeatureCollection.cs b/src/WixToolset.Core/Link/ConnectToFeatureCollection.cs
new file mode 100644
index 00000000..8dd0d22c
--- /dev/null
+++ b/src/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.Link
4{
5 using System;
6 using System.Collections;
7
8 /// <summary>
9 /// Hash collection of connect to feature objects.
10 /// </summary>
11 public sealed 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/WixToolset.Core/Link/ConnectToModule.cs b/src/WixToolset.Core/Link/ConnectToModule.cs
new file mode 100644
index 00000000..d6a8338e
--- /dev/null
+++ b/src/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.Link
4{
5 /// <summary>
6 /// Object that connects things to modules.
7 /// </summary>
8 public sealed 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/WixToolset.Core/Link/ConnectToModuleCollection.cs b/src/WixToolset.Core/Link/ConnectToModuleCollection.cs
new file mode 100644
index 00000000..6595487f
--- /dev/null
+++ b/src/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.Link
4{
5 using System;
6 using System.Collections;
7
8 /// <summary>
9 /// Hash collection of connect to module objects.
10 /// </summary>
11 public sealed 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/WixToolset.Core/Link/FindEntrySectionAndLoadSymbolsCommand.cs b/src/WixToolset.Core/Link/FindEntrySectionAndLoadSymbolsCommand.cs
new file mode 100644
index 00000000..effb06e4
--- /dev/null
+++ b/src/WixToolset.Core/Link/FindEntrySectionAndLoadSymbolsCommand.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.Link
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using WixToolset.Data;
9
10 internal class FindEntrySectionAndLoadSymbolsCommand : ICommand
11 {
12 private IEnumerable<Section> sections;
13
14 public FindEntrySectionAndLoadSymbolsCommand(IEnumerable<Section> sections)
15 {
16 this.sections = sections;
17 }
18
19 /// <summary>
20 /// Sets the expected entry output type, based on output file extension provided to the linker.
21 /// </summary>
22 public OutputType ExpectedOutputType { private get; set; }
23
24 /// <summary>
25 /// Gets the located entry section after the command is executed.
26 /// </summary>
27 public Section EntrySection { get; private set; }
28
29 /// <summary>
30 /// Gets the collection of loaded symbols.
31 /// </summary>
32 public IDictionary<string, Symbol> Symbols { get; private set; }
33
34 public IEnumerable<Symbol> PossiblyConflictingSymbols { get; private set; }
35
36 public void Execute()
37 {
38 Dictionary<string, Symbol> symbols = new Dictionary<string, Symbol>();
39 HashSet<Symbol> possibleConflicts = new HashSet<Symbol>();
40
41 SectionType expectedEntrySectionType;
42 if (!Enum.TryParse<SectionType>(this.ExpectedOutputType.ToString(), out expectedEntrySectionType))
43 {
44 expectedEntrySectionType = SectionType.Unknown;
45 }
46
47 foreach (Section section in this.sections)
48 {
49 // Try to find the one and only entry section.
50 if (SectionType.Product == section.Type || SectionType.Module == section.Type || SectionType.PatchCreation == section.Type || SectionType.Patch == section.Type || SectionType.Bundle == section.Type)
51 {
52 if (SectionType.Unknown != expectedEntrySectionType && section.Type != expectedEntrySectionType)
53 {
54 string outputExtension = Output.GetExtension(this.ExpectedOutputType);
55 Messaging.Instance.OnMessage(WixWarnings.UnexpectedEntrySection(section.SourceLineNumbers, section.Type.ToString(), expectedEntrySectionType.ToString(), outputExtension));
56 }
57
58 if (null == this.EntrySection)
59 {
60 this.EntrySection = section;
61 }
62 else
63 {
64 Messaging.Instance.OnMessage(WixErrors.MultipleEntrySections(this.EntrySection.SourceLineNumbers, this.EntrySection.Id, section.Id));
65 Messaging.Instance.OnMessage(WixErrors.MultipleEntrySections2(section.SourceLineNumbers));
66 }
67 }
68
69 // Load all the symbols from the section's tables that create symbols.
70 foreach (Table table in section.Tables.Where(t => t.Definition.CreateSymbols))
71 {
72 foreach (Row row in table.Rows)
73 {
74 Symbol symbol = new Symbol(row);
75
76 Symbol existingSymbol;
77 if (!symbols.TryGetValue(symbol.Name, out existingSymbol))
78 {
79 symbols.Add(symbol.Name, symbol);
80 }
81 else // uh-oh, duplicate symbols.
82 {
83 // If the duplicate symbols are both private directories, there is a chance that they
84 // point to identical rows. Identical directory rows are redundant and will not cause
85 // conflicts.
86 if (AccessModifier.Private == existingSymbol.Access && AccessModifier.Private == symbol.Access &&
87 "Directory" == existingSymbol.Row.Table.Name && existingSymbol.Row.IsIdentical(symbol.Row))
88 {
89 // Ensure identical symbol's row is marked redundant to ensure (should the row be
90 // referenced into the final output) it will not add duplicate primary keys during
91 // the .IDT importing.
92 symbol.Row.Redundant = true;
93 existingSymbol.AddRedundant(symbol);
94 }
95 else
96 {
97 existingSymbol.AddPossibleConflict(symbol);
98 possibleConflicts.Add(existingSymbol);
99 }
100 }
101 }
102 }
103 }
104
105 this.Symbols = symbols;
106 this.PossiblyConflictingSymbols = possibleConflicts;
107 }
108 }
109}
diff --git a/src/WixToolset.Core/Link/ReportConflictingSymbolsCommand.cs b/src/WixToolset.Core/Link/ReportConflictingSymbolsCommand.cs
new file mode 100644
index 00000000..39c3a5c2
--- /dev/null
+++ b/src/WixToolset.Core/Link/ReportConflictingSymbolsCommand.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.Link
4{
5 using System.Collections.Generic;
6 using System.Linq;
7 using WixToolset.Data;
8
9 public class ReportConflictingSymbolsCommand : ICommand
10 {
11 private IEnumerable<Symbol> possibleConflicts;
12 private IEnumerable<Section> resolvedSections;
13
14 public ReportConflictingSymbolsCommand(IEnumerable<Symbol> possibleConflicts, IEnumerable<Section> resolvedSections)
15 {
16 this.possibleConflicts = possibleConflicts;
17 this.resolvedSections = resolvedSections;
18 }
19
20 public void Execute()
21 {
22 // Do a quick check if there are any possibly conflicting symbols that don't come from tables that allow
23 // overriding. Hopefully the symbols with possible conflicts list is usually very short list (empty should
24 // be the most common). If we find any matches, we'll do a more costly check to see if the possible conflicting
25 // symbols are in sections we actually referenced. From the resulting set, show an error for each duplicate
26 // (aka: conflicting) symbol. This should catch any rows with colliding primary keys (since symbols are based
27 // on the primary keys of rows).
28 List<Symbol> illegalDuplicates = possibleConflicts.Where(s => "WixAction" != s.Row.Table.Name && "WixVariable" != s.Row.Table.Name).ToList();
29 if (0 < illegalDuplicates.Count)
30 {
31 HashSet<Section> referencedSections = new HashSet<Section>(resolvedSections);
32 foreach (Symbol referencedDuplicateSymbol in illegalDuplicates.Where(s => referencedSections.Contains(s.Section)))
33 {
34 List<Symbol> actuallyReferencedDuplicateSymbols = referencedDuplicateSymbol.PossiblyConflictingSymbols.Where(s => referencedSections.Contains(s.Section)).ToList();
35
36 if (actuallyReferencedDuplicateSymbols.Any())
37 {
38 Messaging.Instance.OnMessage(WixErrors.DuplicateSymbol(referencedDuplicateSymbol.Row.SourceLineNumbers, referencedDuplicateSymbol.Name));
39
40 foreach (Symbol duplicate in actuallyReferencedDuplicateSymbols)
41 {
42 Messaging.Instance.OnMessage(WixErrors.DuplicateSymbol2(duplicate.Row.SourceLineNumbers));
43 }
44 }
45 }
46 }
47 }
48 }
49}
diff --git a/src/WixToolset.Core/Link/ResolveReferencesCommand.cs b/src/WixToolset.Core/Link/ResolveReferencesCommand.cs
new file mode 100644
index 00000000..5a985f3f
--- /dev/null
+++ b/src/WixToolset.Core/Link/ResolveReferencesCommand.cs
@@ -0,0 +1,178 @@
1// Copyright (c) .NET Foundation and 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.Link
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.Rows;
11
12 /// <summary>
13 /// Resolves all the simple references in a section.
14 /// </summary>
15 internal class ResolveReferencesCommand : ICommand
16 {
17 private Section entrySection;
18 private IDictionary<string, Symbol> symbols;
19 private HashSet<Symbol> referencedSymbols;
20 private HashSet<Section> resolvedSections;
21
22 public ResolveReferencesCommand(Section entrySection, IDictionary<string, Symbol> symbols)
23 {
24 this.entrySection = entrySection;
25 this.symbols = symbols;
26 }
27
28 public bool BuildingMergeModule { private get; set; }
29
30 public IEnumerable<Symbol> ReferencedSymbols { get { return this.referencedSymbols; } }
31
32 public IEnumerable<Section> ResolvedSections { get { return this.resolvedSections; } }
33
34 /// <summary>
35 /// Resolves all the simple references in a section.
36 /// </summary>
37 public void Execute()
38 {
39 this.resolvedSections = new HashSet<Section>();
40 this.referencedSymbols = new HashSet<Symbol>();
41
42 this.RecursivelyResolveReferences(this.entrySection);
43 }
44
45 /// <summary>
46 /// Recursive helper function to resolve all references of passed in section.
47 /// </summary>
48 /// <param name="section">Section with references to resolve.</param>
49 /// <remarks>Note: recursive function.</remarks>
50 private void RecursivelyResolveReferences(Section section)
51 {
52 // If we already resolved this section, move on to the next.
53 if (!this.resolvedSections.Add(section))
54 {
55 return;
56 }
57
58 // Process all of the references contained in this section using the collection of
59 // symbols provided. Then recursively call this method to process the
60 // located symbol's section. All in all this is a very simple depth-first
61 // search of the references per-section.
62 Table wixSimpleReferenceTable;
63 if (section.Tables.TryGetTable("WixSimpleReference", out wixSimpleReferenceTable))
64 {
65 foreach (WixSimpleReferenceRow wixSimpleReferenceRow in wixSimpleReferenceTable.Rows)
66 {
67 Debug.Assert(wixSimpleReferenceRow.Section == section);
68
69 // If we're building a Merge Module, ignore all references to the Media table
70 // because Merge Modules don't have Media tables.
71 if (this.BuildingMergeModule && "Media" == wixSimpleReferenceRow.TableName)
72 {
73 continue;
74 }
75
76 Symbol symbol;
77 if (!this.symbols.TryGetValue(wixSimpleReferenceRow.SymbolicName, out symbol))
78 {
79 Messaging.Instance.OnMessage(WixErrors.UnresolvedReference(wixSimpleReferenceRow.SourceLineNumbers, wixSimpleReferenceRow.SymbolicName));
80 }
81 else // see if the symbol (and any of its duplicates) are appropriately accessible.
82 {
83 IList<Symbol> accessible = DetermineAccessibleSymbols(section, symbol);
84 if (!accessible.Any())
85 {
86 Messaging.Instance.OnMessage(WixErrors.UnresolvedReference(wixSimpleReferenceRow.SourceLineNumbers, wixSimpleReferenceRow.SymbolicName, symbol.Access));
87 }
88 else if (1 == accessible.Count)
89 {
90 Symbol accessibleSymbol = accessible[0];
91 this.referencedSymbols.Add(accessibleSymbol);
92
93 if (null != accessibleSymbol.Section)
94 {
95 RecursivelyResolveReferences(accessibleSymbol.Section);
96 }
97 }
98 else // display errors for the duplicate symbols.
99 {
100 Symbol accessibleSymbol = accessible[0];
101 string referencingSourceLineNumber = wixSimpleReferenceRow.SourceLineNumbers.ToString();
102 if (String.IsNullOrEmpty(referencingSourceLineNumber))
103 {
104 Messaging.Instance.OnMessage(WixErrors.DuplicateSymbol(accessibleSymbol.Row.SourceLineNumbers, accessibleSymbol.Name));
105 }
106 else
107 {
108 Messaging.Instance.OnMessage(WixErrors.DuplicateSymbol(accessibleSymbol.Row.SourceLineNumbers, accessibleSymbol.Name, referencingSourceLineNumber));
109 }
110
111 foreach (Symbol accessibleDuplicate in accessible.Skip(1))
112 {
113 Messaging.Instance.OnMessage(WixErrors.DuplicateSymbol2(accessibleDuplicate.Row.SourceLineNumbers));
114 }
115 }
116 }
117 }
118 }
119 }
120
121 /// <summary>
122 /// Determine if the symbol and any of its duplicates are accessbile by referencing section.
123 /// </summary>
124 /// <param name="referencingSection">Section referencing the symbol.</param>
125 /// <param name="symbol">Symbol being referenced.</param>
126 /// <returns>List of symbols accessible by referencing section.</returns>
127 private IList<Symbol> DetermineAccessibleSymbols(Section referencingSection, Symbol symbol)
128 {
129 List<Symbol> symbols = new List<Symbol>();
130
131 if (AccessibleSymbol(referencingSection, symbol))
132 {
133 symbols.Add(symbol);
134 }
135
136 foreach (Symbol dupe in symbol.PossiblyConflictingSymbols)
137 {
138 if (AccessibleSymbol(referencingSection, dupe))
139 {
140 symbols.Add(dupe);
141 }
142 }
143
144 foreach (Symbol dupe in symbol.RedundantSymbols)
145 {
146 if (AccessibleSymbol(referencingSection, dupe))
147 {
148 symbols.Add(dupe);
149 }
150 }
151
152 return symbols;
153 }
154
155 /// <summary>
156 /// Determine if a single symbol is accessible by the referencing section.
157 /// </summary>
158 /// <param name="referencingSection">Section referencing the symbol.</param>
159 /// <param name="symbol">Symbol being referenced.</param>
160 /// <returns>True if symbol is accessible.</returns>
161 private bool AccessibleSymbol(Section referencingSection, Symbol symbol)
162 {
163 switch (symbol.Access)
164 {
165 case AccessModifier.Public:
166 return true;
167 case AccessModifier.Internal:
168 return symbol.Row.Section.IntermediateId.Equals(referencingSection.IntermediateId) || (null != symbol.Row.Section.LibraryId && symbol.Row.Section.LibraryId.Equals(referencingSection.LibraryId));
169 case AccessModifier.Protected:
170 return symbol.Row.Section.IntermediateId.Equals(referencingSection.IntermediateId);
171 case AccessModifier.Private:
172 return referencingSection == symbol.Section;
173 default:
174 throw new InvalidOperationException();
175 }
176 }
177 }
178}
diff --git a/src/WixToolset.Core/Link/WixGroupingOrdering.cs b/src/WixToolset.Core/Link/WixGroupingOrdering.cs
new file mode 100644
index 00000000..fc0ce43b
--- /dev/null
+++ b/src/WixToolset.Core/Link/WixGroupingOrdering.cs
@@ -0,0 +1,726 @@
1// Copyright (c) .NET Foundation and 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.Link
4{
5 using System;
6 using System.Collections;
7 using System.Collections.ObjectModel;
8 using System.Collections.Generic;
9 using System.Diagnostics;
10 using System.Globalization;
11 using System.Linq;
12 using System.Text;
13 using WixToolset.Extensibility;
14 using WixToolset.Data;
15
16 /// <summary>
17 /// Grouping and Ordering class of the WiX toolset.
18 /// </summary>
19 internal sealed class WixGroupingOrdering : IMessageHandler
20 {
21 private Output output;
22 private IMessageHandler messageHandler;
23 private List<string> groupTypes;
24 private List<string> itemTypes;
25 private ItemCollection items;
26 private List<int> rowsUsed;
27 private bool loaded;
28 private bool encounteredError;
29
30 /// <summary>
31 /// Creates a WixGroupingOrdering object.
32 /// </summary>
33 /// <param name="output">Output from which to read the group and order information.</param>
34 /// <param name="messageHandler">Handler for any error messages.</param>
35 /// <param name="groupTypes">Group types to include.</param>
36 /// <param name="itemTypes">Item types to include.</param>
37 public WixGroupingOrdering(Output output, IMessageHandler messageHandler)
38 {
39 this.output = output;
40 this.messageHandler = messageHandler;
41
42 this.rowsUsed = new List<int>();
43 this.loaded = false;
44 this.encounteredError = false;
45 }
46
47 /// <summary>
48 /// Switches a WixGroupingOrdering object to operate on a new set of groups/items.
49 /// </summary>
50 /// <param name="groupTypes">Group types to include.</param>
51 /// <param name="itemTypes">Item types to include.</param>
52 public void UseTypes(IEnumerable<string> groupTypes, IEnumerable<string> itemTypes)
53 {
54 this.groupTypes = new List<string>(groupTypes);
55 this.itemTypes = new List<string>(itemTypes);
56
57 this.items = new ItemCollection();
58 this.loaded = false;
59 }
60
61 /// <summary>
62 /// Sends a message to the message handler if there is one.
63 /// </summary>
64 /// <param name="mea">Message event arguments.</param>
65 public void OnMessage(MessageEventArgs e)
66 {
67 WixErrorEventArgs errorEventArgs = e as WixErrorEventArgs;
68
69 if (null != errorEventArgs || MessageLevel.Error == e.Level)
70 {
71 this.encounteredError = true;
72 }
73
74 if (null != this.messageHandler)
75 {
76 this.messageHandler.OnMessage(e);
77 }
78 else if (null != errorEventArgs)
79 {
80 throw new WixException(errorEventArgs);
81 }
82 }
83
84 /// <summary>
85 /// Finds all nested items under a parent group and creates new WixGroup data for them.
86 /// </summary>
87 /// <param name="parentType">The group type for the parent group to flatten.</param>
88 /// <param name="parentId">The identifier of the parent group to flatten.</param>
89 /// <param name="removeUsedRows">Whether to remove used group rows before returning.</param>
90 public void FlattenAndRewriteRows(string parentType, string parentId, bool removeUsedRows)
91 {
92 Debug.Assert(this.groupTypes.Contains(parentType));
93
94 List<Item> orderedItems;
95 this.CreateOrderedList(parentType, parentId, out orderedItems);
96 if (this.encounteredError)
97 {
98 return;
99 }
100
101 this.CreateNewGroupRows(parentType, parentId, orderedItems);
102
103 if (removeUsedRows)
104 {
105 this.RemoveUsedGroupRows();
106 }
107 }
108
109 /// <summary>
110 /// Finds all items under a parent group type and creates new WixGroup data for them.
111 /// </summary>
112 /// <param name="parentType">The type of the parent group to flatten.</param>
113 /// <param name="removeUsedRows">Whether to remove used group rows before returning.</param>
114 public void FlattenAndRewriteGroups(string parentType, bool removeUsedRows)
115 {
116 Debug.Assert(this.groupTypes.Contains(parentType));
117
118 this.LoadFlattenOrderGroups();
119 if (this.encounteredError)
120 {
121 return;
122 }
123
124 foreach (Item item in this.items)
125 {
126 if (parentType == item.Type)
127 {
128 List<Item> orderedItems;
129 this.CreateOrderedList(item.Type, item.Id, out orderedItems);
130 this.CreateNewGroupRows(item.Type, item.Id, orderedItems);
131 }
132 }
133
134 if (removeUsedRows)
135 {
136 this.RemoveUsedGroupRows();
137 }
138 }
139
140
141 /// <summary>
142 /// Creates a flattened and ordered list of items for the given parent group.
143 /// </summary>
144 /// <param name="parentType">The group type for the parent group to flatten.</param>
145 /// <param name="parentId">The identifier of the parent group to flatten.</param>
146 /// <param name="orderedItems">The returned list of ordered items.</param>
147 private void CreateOrderedList(string parentType, string parentId, out List<Item> orderedItems)
148 {
149 orderedItems = null;
150
151 this.LoadFlattenOrderGroups();
152 if (this.encounteredError)
153 {
154 return;
155 }
156
157 Item parentItem;
158 if (!this.items.TryGetValue(parentType, parentId, out parentItem))
159 {
160 this.OnMessage(WixErrors.IdentifierNotFound(parentType, parentId));
161 return;
162 }
163
164 orderedItems = new List<Item>(parentItem.ChildItems);
165 orderedItems.Sort(new Item.AfterItemComparer());
166 }
167
168 /// <summary>
169 /// Removes rows from WixGroup that have been used by this object.
170 /// </summary>
171 public void RemoveUsedGroupRows()
172 {
173 List<int> sortedIndexes = this.rowsUsed.Distinct().OrderByDescending(i => i).ToList();
174
175 Table wixGroupTable = this.output.Tables["WixGroup"];
176 Debug.Assert(null != wixGroupTable);
177 Debug.Assert(sortedIndexes[0] < wixGroupTable.Rows.Count);
178
179 foreach (int rowIndex in sortedIndexes)
180 {
181 wixGroupTable.Rows.RemoveAt(rowIndex);
182 }
183 }
184
185 /// <summary>
186 /// Creates new WixGroup rows for a list of items.
187 /// </summary>
188 /// <param name="parentType">The group type for the parent group in the new rows.</param>
189 /// <param name="parentId">The identifier of the parent group in the new rows.</param>
190 /// <param name="orderedItems">The list of new items.</param>
191 private void CreateNewGroupRows(string parentType, string parentId, List<Item> orderedItems)
192 {
193 // TODO: MSIs don't guarantee that rows stay in the same order, and technically, neither
194 // does WiX (although they do, currently). We probably want to "upgrade" this to a new
195 // table that includes a sequence number, and then change the code that uses ordered
196 // groups to read from that table instead.
197 Table wixGroupTable = this.output.Tables["WixGroup"];
198 Debug.Assert(null != wixGroupTable);
199
200 foreach (Item item in orderedItems)
201 {
202 Row row = wixGroupTable.CreateRow(item.Row.SourceLineNumbers);
203 row[0] = parentId;
204 row[1] = parentType;
205 row[2] = item.Id;
206 row[3] = item.Type;
207 }
208 }
209
210 // Group/Ordering Flattening Logic
211 //
212 // What follows is potentially convoluted logic. Two somewhat orthogonal concepts are in
213 // play: grouping (parent/child relationships) and ordering (before/after relationships).
214 // Dealing with just one or the other is straghtforward. Groups can be flattened
215 // recursively. Ordering can be propagated in either direction. When the ordering also
216 // participates in the grouping constructions, however, things get trickier. For the
217 // purposes of this discussion, we're dealing with "items" and "groups", and an instance
218 // of either of them can be marked as coming "after" some other instance.
219 //
220 // For simple item-to-item ordering, the "after" values simply propagate: if A is after B,
221 // and B is after C, then we can say that A is after *both* B and C. If a group is involved,
222 // it acts as a proxy for all of its included items and any sub-groups.
223
224 /// <summary>
225 /// Internal workhorse for ensuring that group and ordering information has
226 /// been loaded and applied.
227 /// </summary>
228 private void LoadFlattenOrderGroups()
229 {
230 if (!this.loaded)
231 {
232 this.LoadGroups();
233 this.LoadOrdering();
234
235 // It would be really nice to have a "find circular after dependencies"
236 // function, but it gets much more complicated because of the way that
237 // the dependencies are propagated across group boundaries. For now, we
238 // just live with the dependency loop detection as we flatten the
239 // dependencies. Group references, however, we can check directly.
240 this.FindCircularGroupReferences();
241
242 if (!this.encounteredError)
243 {
244 this.FlattenGroups();
245 this.FlattenOrdering();
246 }
247
248 this.loaded = true;
249 }
250 }
251
252 /// <summary>
253 /// Loads data from the WixGroup table.
254 /// </summary>
255 private void LoadGroups()
256 {
257 Table wixGroupTable = this.output.Tables["WixGroup"];
258 if (null == wixGroupTable || 0 == wixGroupTable.Rows.Count)
259 {
260 // TODO: Change message name to make it *not* Bundle specific?
261 this.OnMessage(WixErrors.MissingBundleInformation("WixGroup"));
262 }
263
264 // Collect all of the groups
265 for (int rowIndex = 0; rowIndex < wixGroupTable.Rows.Count; ++rowIndex)
266 {
267 Row row = wixGroupTable.Rows[rowIndex];
268 string rowParentName = (string)row[0];
269 string rowParentType = (string)row[1];
270 string rowChildName = (string)row[2];
271 string rowChildType = (string)row[3];
272
273 // If this row specifies a parent or child type that's not in our
274 // lists, we assume it's not a row that we're concerned about.
275 if (!this.groupTypes.Contains(rowParentType) ||
276 !this.itemTypes.Contains(rowChildType))
277 {
278 continue;
279 }
280
281 this.rowsUsed.Add(rowIndex);
282
283 Item parentItem;
284 if (!this.items.TryGetValue(rowParentType, rowParentName, out parentItem))
285 {
286 parentItem = new Item(row, rowParentType, rowParentName);
287 this.items.Add(parentItem);
288 }
289
290 Item childItem;
291 if (!this.items.TryGetValue(rowChildType, rowChildName, out childItem))
292 {
293 childItem = new Item(row, rowChildType, rowChildName);
294 this.items.Add(childItem);
295 }
296
297 parentItem.ChildItems.Add(childItem);
298 }
299 }
300
301 /// <summary>
302 /// Flattens group/item information.
303 /// </summary>
304 private void FlattenGroups()
305 {
306 foreach (Item item in this.items)
307 {
308 item.FlattenChildItems();
309 }
310 }
311
312 /// <summary>
313 /// Finds and reports circular references in the group/item data.
314 /// </summary>
315 private void FindCircularGroupReferences()
316 {
317 ItemCollection itemsInKnownLoops = new ItemCollection();
318 foreach (Item item in this.items)
319 {
320 if (itemsInKnownLoops.Contains(item))
321 {
322 continue;
323 }
324
325 ItemCollection itemsSeen = new ItemCollection();
326 string circularReference;
327 if (this.FindCircularGroupReference(item, item, itemsSeen, out circularReference))
328 {
329 itemsInKnownLoops.Add(itemsSeen);
330 this.OnMessage(WixErrors.ReferenceLoopDetected(item.Row.SourceLineNumbers, circularReference));
331 }
332 }
333 }
334
335 /// <summary>
336 /// Recursive worker to find and report circular references in group/item data.
337 /// </summary>
338 /// <param name="checkItem">The sentinal item being checked.</param>
339 /// <param name="currentItem">The current item in the recursion.</param>
340 /// <param name="itemsSeen">A list of all items already visited (for performance).</param>
341 /// <param name="circularReference">A list of items in the current circular reference, if one was found; null otherwise.</param>
342 /// <returns>True if a circular reference was found; false otherwise.</returns>
343 private bool FindCircularGroupReference(Item checkItem, Item currentItem, ItemCollection itemsSeen, out string circularReference)
344 {
345 circularReference = null;
346 foreach (Item subitem in currentItem.ChildItems)
347 {
348 if (checkItem == subitem)
349 {
350 // TODO: Even better would be to include the source lines for each reference!
351 circularReference = String.Format(CultureInfo.InvariantCulture, "{0}:{1} -> {2}:{3}",
352 currentItem.Type, currentItem.Id, subitem.Type, subitem.Id);
353 return true;
354 }
355
356 if (!itemsSeen.Contains(subitem))
357 {
358 itemsSeen.Add(subitem);
359 if (this.FindCircularGroupReference(checkItem, subitem, itemsSeen, out circularReference))
360 {
361 // TODO: Even better would be to include the source lines for each reference!
362 circularReference = String.Format(CultureInfo.InvariantCulture, "{0}:{1} -> {2}",
363 currentItem.Type, currentItem.Id, circularReference);
364 return true;
365 }
366 }
367 }
368
369 return false;
370 }
371
372 /// <summary>
373 /// Loads ordering dependency data from the WixOrdering table.
374 /// </summary>
375 private void LoadOrdering()
376 {
377 Table wixOrderingTable = output.Tables["WixOrdering"];
378 if (null == wixOrderingTable || 0 == wixOrderingTable.Rows.Count)
379 {
380 // TODO: Do we need a message here?
381 return;
382 }
383
384 foreach (Row row in wixOrderingTable.Rows)
385 {
386 string rowItemType = (string)row[0];
387 string rowItemName = (string)row[1];
388 string rowDependsOnType = (string)row[2];
389 string rowDependsOnName = (string)row[3];
390
391 // If this row specifies some other (unknown) type in either
392 // position, we assume it's not a row that we're concerned about.
393 // For ordering, we allow group and item in either position.
394 if (!(this.groupTypes.Contains(rowItemType) || this.itemTypes.Contains(rowItemType)) ||
395 !(this.groupTypes.Contains(rowDependsOnType) || this.itemTypes.Contains(rowDependsOnType)))
396 {
397 continue;
398 }
399
400 Item item = null;
401 Item dependsOn = null;
402
403 if (!this.items.TryGetValue(rowItemType, rowItemName, out item))
404 {
405 this.OnMessage(WixErrors.IdentifierNotFound(rowItemType, rowItemName));
406 }
407
408 if (!this.items.TryGetValue(rowDependsOnType, rowDependsOnName, out dependsOn))
409 {
410 this.OnMessage(WixErrors.IdentifierNotFound(rowDependsOnType, rowDependsOnName));
411 }
412
413 if (null == item || null == dependsOn)
414 {
415 continue;
416 }
417
418 item.AddAfter(dependsOn, this);
419 }
420 }
421
422 /// <summary>
423 /// Flattens the ordering dependencies in the groups/items.
424 /// </summary>
425 private void FlattenOrdering()
426 {
427 // Because items don't know about their parent groups (and can, in fact, be
428 // in more than one group at a time), we need to pre-propagate the 'afters'
429 // from each parent item to its children before we attempt to flatten the
430 // ordering.
431 foreach (Item item in this.items)
432 {
433 item.PropagateAfterToChildItems(this);
434 }
435
436 foreach (Item item in this.items)
437 {
438 item.FlattenAfters(this);
439 }
440 }
441
442 /// <summary>
443 /// A variant of KeyedCollection that doesn't throw when an item is re-added.
444 /// </summary>
445 /// <typeparam name="TKey">Key type for the collection.</typeparam>
446 /// <typeparam name="TItem">Item type for the colelction.</typeparam>
447 internal abstract class EnhancedKeyCollection<TKey, TItem> : KeyedCollection<TKey, TItem>
448 {
449 new public void Add(TItem item)
450 {
451 if (!this.Contains(item))
452 {
453 base.Add(item);
454 }
455 }
456
457 public void Add(Collection<TItem> list)
458 {
459 foreach (TItem item in list)
460 {
461 this.Add(item);
462 }
463 }
464
465 public void Remove(Collection<TItem> list)
466 {
467 foreach (TItem item in list)
468 {
469 this.Remove(item);
470 }
471 }
472
473 public bool TryGetValue(TKey key, out TItem item)
474 {
475 // KeyedCollection doesn't implement the TryGetValue() method, but it's
476 // a useful concept. We can't just always pass this to the enclosed
477 // Dictionary, however, because it doesn't always exist! If it does, we
478 // can delegate to it as one would expect. If it doesn't, we have to
479 // implement everything ourselves in terms of Contains().
480
481 if (null != this.Dictionary)
482 {
483 return this.Dictionary.TryGetValue(key, out item);
484 }
485
486 if (this.Contains(key))
487 {
488 item = this[key];
489 return true;
490 }
491
492 item = default(TItem);
493 return false;
494 }
495
496#if DEBUG
497 // This just makes debugging easier...
498 public override string ToString()
499 {
500 StringBuilder sb = new StringBuilder();
501 foreach (TItem item in this)
502 {
503 sb.AppendFormat("{0}, ", item);
504 }
505 sb.Length -= 2;
506 return sb.ToString();
507 }
508#endif // DEBUG
509 }
510
511 /// <summary>
512 /// A specialized EnhancedKeyCollection, typed to Items.
513 /// </summary>
514 internal class ItemCollection : EnhancedKeyCollection<string, Item>
515 {
516 protected override string GetKeyForItem(Item item)
517 {
518 return item.Key;
519 }
520
521 public bool TryGetValue(string type, string id, out Item item)
522 {
523 return this.TryGetValue(CreateKeyFromTypeId(type, id), out item);
524 }
525
526 public static string CreateKeyFromTypeId(string type, string id)
527 {
528 return String.Format(CultureInfo.InvariantCulture, "{0}_{1}", type, id);
529 }
530 }
531
532 /// <summary>
533 /// An item (or group) in the grouping/ordering engine.
534 /// </summary>
535 /// <remarks>Encapsulates nested group membership and also before/after
536 /// ordering dependencies.</remarks>
537 internal class Item
538 {
539 private ItemCollection afterItems;
540 private ItemCollection beforeItems; // for checking for circular references
541 private bool flattenedAfterItems;
542
543 public Item(Row row, string type, string id)
544 {
545 this.Row = row;
546 this.Type = type;
547 this.Id = id;
548
549 this.Key = ItemCollection.CreateKeyFromTypeId(type, id);
550
551 afterItems = new ItemCollection();
552 beforeItems = new ItemCollection();
553 flattenedAfterItems = false;
554 }
555
556 public Row Row { get; private set; }
557 public string Type { get; private set; }
558 public string Id { get; private set; }
559 public string Key { get; private set; }
560
561#if DEBUG
562 // Makes debugging easier...
563 public override string ToString()
564 {
565 return this.Key;
566 }
567#endif // DEBUG
568
569 private ItemCollection childItems = new ItemCollection();
570 public ItemCollection ChildItems { get { return childItems; } }
571
572 /// <summary>
573 /// Removes any nested groups under this item and replaces
574 /// them with their child items.
575 /// </summary>
576 public void FlattenChildItems()
577 {
578 ItemCollection flattenedChildItems = new ItemCollection();
579
580 foreach (Item childItem in this.ChildItems)
581 {
582 if (0 == childItem.ChildItems.Count)
583 {
584 flattenedChildItems.Add(childItem);
585 }
586 else
587 {
588 childItem.FlattenChildItems();
589 flattenedChildItems.Add(childItem.ChildItems);
590 }
591 }
592
593 this.ChildItems.Clear();
594 this.ChildItems.Add(flattenedChildItems);
595 }
596
597 /// <summary>
598 /// Adds a list of items to the 'after' ordering collection.
599 /// </summary>
600 /// <param name="items">List of items to add.</param>
601 /// <param name="messageHandler">Message handler in case a circular ordering reference is found.</param>
602 public void AddAfter(ItemCollection items, IMessageHandler messageHandler)
603 {
604 foreach (Item item in items)
605 {
606 this.AddAfter(item, messageHandler);
607 }
608 }
609
610 /// <summary>
611 /// Adds an item to the 'after' ordering collection.
612 /// </summary>
613 /// <param name="item">Items to add.</param>
614 /// <param name="messageHandler">Message handler in case a circular ordering reference is found.</param>
615 public void AddAfter(Item after, IMessageHandler messageHandler)
616 {
617 if (this.beforeItems.Contains(after))
618 {
619 // We could try to chain this up (the way that group circular dependencies
620 // are reported), but since we're in the process of flattening, we may already
621 // have lost some distinction between authored and propagated ordering.
622 string circularReference = String.Format(CultureInfo.InvariantCulture, "{0}:{1} -> {2}:{3} -> {0}:{1}",
623 this.Type, this.Id, after.Type, after.Id);
624 messageHandler.OnMessage(WixErrors.OrderingReferenceLoopDetected(after.Row.SourceLineNumbers, circularReference));
625 return;
626 }
627
628 this.afterItems.Add(after);
629 after.beforeItems.Add(this);
630 }
631
632 /// <summary>
633 /// Propagates 'after' dependencies from an item to its child items.
634 /// </summary>
635 /// <param name="messageHandler">Message handler in case a circular ordering reference is found.</param>
636 /// <remarks>Because items don't know about their parent groups (and can, in fact, be in more
637 /// than one group at a time), we need to propagate the 'afters' from each parent item to its children
638 /// before we attempt to flatten the ordering.</remarks>
639 public void PropagateAfterToChildItems(IMessageHandler messageHandler)
640 {
641 if (this.ShouldItemPropagateChildOrdering())
642 {
643 foreach (Item childItem in this.childItems)
644 {
645 childItem.AddAfter(this.afterItems, messageHandler);
646 }
647 }
648 }
649
650 /// <summary>
651 /// Flattens the ordering dependency for this item.
652 /// </summary>
653 /// <param name="messageHandler">Message handler in case a circular ordering reference is found.</param>
654 public void FlattenAfters(IMessageHandler messageHandler)
655 {
656 if (this.flattenedAfterItems)
657 {
658 return;
659 }
660
661 this.flattenedAfterItems = true;
662
663 // Ensure that if we're after something (A), and *it's* after something (B),
664 // that we list ourselved as after both (A) *and* (B).
665 ItemCollection nestedAfterItems = new ItemCollection();
666
667 foreach (Item afterItem in this.afterItems)
668 {
669 afterItem.FlattenAfters(messageHandler);
670 nestedAfterItems.Add(afterItem.afterItems);
671
672 if (afterItem.ShouldItemPropagateChildOrdering())
673 {
674 // If we are after a group, it really means
675 // we are after all of the group's children.
676 foreach (Item childItem in afterItem.ChildItems)
677 {
678 childItem.FlattenAfters(messageHandler);
679 nestedAfterItems.Add(childItem.afterItems);
680 nestedAfterItems.Add(childItem);
681 }
682 }
683 }
684
685 this.AddAfter(nestedAfterItems, messageHandler);
686 }
687
688 // We *don't* propagate ordering information from Packages or
689 // Containers to their children, because ordering doesn't matter
690 // for them, and a Payload in two Packages (or Containers) can
691 // cause a circular reference to occur. We do, however, need to
692 // track the ordering in the UX Container, because we need the
693 // first payload to be the entrypoint.
694 private bool ShouldItemPropagateChildOrdering()
695 {
696 if (String.Equals("Package", this.Type, StringComparison.Ordinal) ||
697 (String.Equals("Container", this.Type, StringComparison.Ordinal) &&
698 !String.Equals(Compiler.BurnUXContainerId, this.Id, StringComparison.Ordinal)))
699 {
700 return false;
701 }
702 return true;
703 }
704
705 /// <summary>
706 /// Helper IComparer class to make ordering easier.
707 /// </summary>
708 internal sealed class AfterItemComparer : IComparer<Item>
709 {
710 public int Compare(Item x, Item y)
711 {
712 if (x.afterItems.Contains(y))
713 {
714 return 1;
715 }
716 else if (y.afterItems.Contains(x))
717 {
718 return -1;
719 }
720
721 return string.CompareOrdinal(x.Id, y.Id);
722 }
723 }
724 }
725 }
726}
diff --git a/src/WixToolset.Core/Linker.cs b/src/WixToolset.Core/Linker.cs
new file mode 100644
index 00000000..166894b9
--- /dev/null
+++ b/src/WixToolset.Core/Linker.cs
@@ -0,0 +1,2434 @@
1// Copyright (c) .NET Foundation and 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.Collections;
7 using System.Collections.Generic;
8 using System.Collections.Specialized;
9 using System.Diagnostics;
10 using System.Diagnostics.CodeAnalysis;
11 using System.Globalization;
12 using System.Linq;
13 using System.Text;
14 using WixToolset.Data;
15 using WixToolset.Data.Rows;
16 using WixToolset.Extensibility;
17 using WixToolset.Link;
18 using WixToolset.Core.Native;
19
20 /// <summary>
21 /// Linker core of the WiX toolset.
22 /// </summary>
23 public sealed class Linker : IMessageHandler
24 {
25 private static readonly char[] colonCharacter = ":".ToCharArray();
26 private static readonly string emptyGuid = Guid.Empty.ToString("B");
27
28 private List<IExtensionData> extensionData;
29
30 private List<InspectorExtension> inspectorExtensions;
31 private bool sectionIdOnRows;
32 private WixActionRowCollection standardActions;
33 private Localizer localizer;
34 private Output activeOutput;
35 private TableDefinitionCollection tableDefinitions;
36
37 /// <summary>
38 /// Creates a linker.
39 /// </summary>
40 public Linker()
41 {
42 this.sectionIdOnRows = true; // TODO: what is the correct value for this?
43
44 this.standardActions = WindowsInstallerStandard.GetStandardActions();
45 this.tableDefinitions = new TableDefinitionCollection(WindowsInstallerStandard.GetTableDefinitions());
46
47 this.extensionData = new List<IExtensionData>();
48 this.inspectorExtensions = new List<InspectorExtension>();
49 }
50
51 /// <summary>
52 /// Gets or sets the localizer.
53 /// </summary>
54 /// <value>The localizer.</value>
55 public Localizer Localizer
56 {
57 get { return this.localizer; }
58 set { this.localizer = value; }
59 }
60
61 /// <summary>
62 /// Gets or sets the path to output unreferenced symbols to. If null or empty, there is no output.
63 /// </summary>
64 /// <value>The path to output the xml file.</value>
65 public string UnreferencedSymbolsFile { get; set; }
66
67 /// <summary>
68 /// Gets or sets the option to show pedantic messages.
69 /// </summary>
70 /// <value>The option to show pedantic messages.</value>
71 public bool ShowPedanticMessages { get; set; }
72
73 /// <summary>
74 /// Gets the table definitions used by the linker.
75 /// </summary>
76 /// <value>Table definitions used by the linker.</value>
77 public TableDefinitionCollection TableDefinitions
78 {
79 get { return this.tableDefinitions; }
80 }
81
82 /// <summary>
83 /// Gets or sets the Wix variable resolver.
84 /// </summary>
85 /// <value>The Wix variable resolver.</value>
86 public WixVariableResolver WixVariableResolver { get; set; }
87
88 /// <summary>
89 /// Adds an extension.
90 /// </summary>
91 /// <param name="extension">The extension to add.</param>
92 public void AddExtensionData(IExtensionData extension)
93 {
94 if (null != extension.TableDefinitions)
95 {
96 foreach (TableDefinition tableDefinition in extension.TableDefinitions)
97 {
98 if (!this.tableDefinitions.Contains(tableDefinition.Name))
99 {
100 this.tableDefinitions.Add(tableDefinition);
101 }
102 else
103 {
104 throw new WixException(WixErrors.DuplicateExtensionTable(extension.GetType().ToString(), tableDefinition.Name));
105 }
106 }
107 }
108
109 // keep track of extension data so the libraries can be loaded from these later once all the table definitions
110 // are loaded; this will allow extensions to have cross table definition dependencies
111 this.extensionData.Add(extension);
112 }
113
114 /// <summary>
115 /// Links a collection of sections into an output.
116 /// </summary>
117 /// <param name="inputs">The collection of sections to link together.</param>
118 /// <param name="expectedOutputType">Expected output type, based on output file extension provided to the linker.</param>
119 /// <returns>Output object from the linking.</returns>
120 public Output Link(IEnumerable<Section> inputs, OutputType expectedOutputType)
121 {
122 Output output = null;
123 List<Section> sections = new List<Section>(inputs);
124
125 try
126 {
127 bool containsModuleSubstitution = false;
128 bool containsModuleConfiguration = false;
129
130 this.activeOutput = null;
131
132 List<Row> actionRows = new List<Row>();
133 List<Row> suppressActionRows = new List<Row>();
134
135 TableDefinitionCollection customTableDefinitions = new TableDefinitionCollection();
136 List<Row> customRows = new List<Row>();
137
138 StringCollection generatedShortFileNameIdentifiers = new StringCollection();
139 Hashtable generatedShortFileNames = new Hashtable();
140
141 Hashtable multipleFeatureComponents = new Hashtable();
142
143 Hashtable wixVariables = new Hashtable();
144
145 // verify that modularization types match for foreign key relationships
146 foreach (TableDefinition tableDefinition in this.tableDefinitions)
147 {
148 foreach (ColumnDefinition columnDefinition in tableDefinition.Columns)
149 {
150 if (null != columnDefinition.KeyTable && 0 > columnDefinition.KeyTable.IndexOf(';') && columnDefinition.IsKeyColumnSet)
151 {
152 try
153 {
154 TableDefinition keyTableDefinition = this.tableDefinitions[columnDefinition.KeyTable];
155
156 if (0 >= columnDefinition.KeyColumn || keyTableDefinition.Columns.Count < columnDefinition.KeyColumn)
157 {
158 this.OnMessage(WixErrors.InvalidKeyColumn(tableDefinition.Name, columnDefinition.Name, columnDefinition.KeyTable, columnDefinition.KeyColumn));
159 }
160 else if (keyTableDefinition.Columns[columnDefinition.KeyColumn - 1].ModularizeType != columnDefinition.ModularizeType && ColumnModularizeType.CompanionFile != columnDefinition.ModularizeType)
161 {
162 this.OnMessage(WixErrors.CollidingModularizationTypes(tableDefinition.Name, columnDefinition.Name, columnDefinition.KeyTable, columnDefinition.KeyColumn, columnDefinition.ModularizeType.ToString(), keyTableDefinition.Columns[columnDefinition.KeyColumn - 1].ModularizeType.ToString()));
163 }
164 }
165 catch (WixMissingTableDefinitionException)
166 {
167 // ignore missing table definitions - this error is caught in other places
168 }
169 }
170 }
171 }
172
173 // Add sections from the extensions with data.
174 foreach (IExtensionData data in this.extensionData)
175 {
176 Library library = data.GetLibrary(this.tableDefinitions);
177
178 if (null != library)
179 {
180 sections.AddRange(library.Sections);
181 }
182 }
183
184 // First find the entry section and while processing all sections load all the symbols from all of the sections.
185 // sections.FindEntrySectionAndLoadSymbols(false, this, expectedOutputType, out entrySection, out allSymbols);
186 FindEntrySectionAndLoadSymbolsCommand find = new FindEntrySectionAndLoadSymbolsCommand(sections);
187 find.ExpectedOutputType = expectedOutputType;
188
189 find.Execute();
190
191 // Must have found the entry section by now.
192 if (null == find.EntrySection)
193 {
194 throw new WixException(WixErrors.MissingEntrySection(expectedOutputType.ToString()));
195 }
196
197 IDictionary<string, Symbol> allSymbols = find.Symbols;
198
199 // Add the missing standard action symbols.
200 this.LoadStandardActionSymbols(allSymbols);
201
202 // now that we know where we're starting from, create the output object
203 output = new Output(null);
204 output.EntrySection = find.EntrySection; // Note: this entry section will get added to the Output.Sections collection later
205 if (null != this.localizer && -1 != this.localizer.Codepage)
206 {
207 output.Codepage = this.localizer.Codepage;
208 }
209 this.activeOutput = output;
210
211 // Resolve the symbol references to find the set of sections we care about for linking.
212 // Of course, we start with the entry section (that's how it got its name after all).
213 ResolveReferencesCommand resolve = new ResolveReferencesCommand(output.EntrySection, allSymbols);
214 resolve.BuildingMergeModule = (OutputType.Module == output.Type);
215
216 resolve.Execute();
217
218 if (Messaging.Instance.EncounteredError)
219 {
220 return null;
221 }
222
223 // Add the resolved sections to the output then flatten the complex
224 // references that particpate in groups.
225 foreach (Section section in resolve.ResolvedSections)
226 {
227 output.Sections.Add(section);
228 }
229
230 this.FlattenSectionsComplexReferences(output.Sections);
231
232 if (Messaging.Instance.EncounteredError)
233 {
234 return null;
235 }
236
237 // The hard part in linking is processing the complex references.
238 HashSet<string> referencedComponents = new HashSet<string>();
239 ConnectToFeatureCollection componentsToFeatures = new ConnectToFeatureCollection();
240 ConnectToFeatureCollection featuresToFeatures = new ConnectToFeatureCollection();
241 ConnectToFeatureCollection modulesToFeatures = new ConnectToFeatureCollection();
242 this.ProcessComplexReferences(output, output.Sections, referencedComponents, componentsToFeatures, featuresToFeatures, modulesToFeatures);
243
244 if (Messaging.Instance.EncounteredError)
245 {
246 return null;
247 }
248
249 // Display an error message for Components that were not referenced by a Feature.
250 foreach (Symbol symbol in resolve.ReferencedSymbols.Where(s => "Component".Equals(s.Row.TableDefinition.Name, StringComparison.Ordinal)))
251 {
252 if (!referencedComponents.Contains(symbol.Name))
253 {
254 this.OnMessage(WixErrors.OrphanedComponent(symbol.Row.SourceLineNumbers, (string)symbol.Row[0]));
255 }
256 }
257
258 // Report duplicates that would ultimately end up being primary key collisions.
259 ReportConflictingSymbolsCommand reportDupes = new ReportConflictingSymbolsCommand(find.PossiblyConflictingSymbols, resolve.ResolvedSections);
260 reportDupes.Execute();
261
262 if (Messaging.Instance.EncounteredError)
263 {
264 return null;
265 }
266
267 // resolve the feature to feature connects
268 this.ResolveFeatureToFeatureConnects(featuresToFeatures, allSymbols);
269
270 // start generating OutputTables and OutputRows for all the sections in the output
271 List<Row> ensureTableRows = new List<Row>();
272 int sectionCount = 0;
273 foreach (Section section in output.Sections)
274 {
275 sectionCount++;
276 string sectionId = section.Id;
277 if (null == sectionId && this.sectionIdOnRows)
278 {
279 sectionId = "wix.section." + sectionCount.ToString(CultureInfo.InvariantCulture);
280 }
281
282 foreach (Table table in section.Tables)
283 {
284 bool copyRows = true; // by default, copy rows.
285
286 // handle special tables
287 switch (table.Name)
288 {
289 case "AppSearch":
290 this.activeOutput.EnsureTable(this.tableDefinitions["Signature"]);
291 break;
292
293 case "Class":
294 if (OutputType.Product == output.Type)
295 {
296 this.ResolveFeatures(table.Rows, 2, 11, componentsToFeatures, multipleFeatureComponents);
297 }
298 break;
299
300 case "CustomAction":
301 if (OutputType.Module == this.activeOutput.Type)
302 {
303 this.activeOutput.EnsureTable(this.tableDefinitions["AdminExecuteSequence"]);
304 this.activeOutput.EnsureTable(this.tableDefinitions["AdminUISequence"]);
305 this.activeOutput.EnsureTable(this.tableDefinitions["AdvtExecuteSequence"]);
306 this.activeOutput.EnsureTable(this.tableDefinitions["InstallExecuteSequence"]);
307 this.activeOutput.EnsureTable(this.tableDefinitions["InstallUISequence"]);
308 }
309 break;
310
311 case "Dialog":
312 this.activeOutput.EnsureTable(this.tableDefinitions["ListBox"]);
313 break;
314
315 case "Directory":
316 foreach (Row row in table.Rows)
317 {
318 if (OutputType.Module == this.activeOutput.Type)
319 {
320 string directory = row[0].ToString();
321 if (WindowsInstallerStandard.IsStandardDirectory(directory))
322 {
323 // if the directory table contains references to standard windows folders
324 // mergemod.dll will add customactions to set the MSM directory to
325 // the same directory as the standard windows folder and will add references to
326 // custom action to all the standard sequence tables. A problem will occur
327 // if the MSI does not have these tables as mergemod.dll does not add these
328 // tables to the MSI if absent. This code adds the tables in case mergemod.dll
329 // needs them.
330 this.activeOutput.EnsureTable(this.tableDefinitions["CustomAction"]);
331 this.activeOutput.EnsureTable(this.tableDefinitions["AdminExecuteSequence"]);
332 this.activeOutput.EnsureTable(this.tableDefinitions["AdminUISequence"]);
333 this.activeOutput.EnsureTable(this.tableDefinitions["AdvtExecuteSequence"]);
334 this.activeOutput.EnsureTable(this.tableDefinitions["InstallExecuteSequence"]);
335 this.activeOutput.EnsureTable(this.tableDefinitions["InstallUISequence"]);
336 }
337 else
338 {
339 foreach (string standardDirectory in WindowsInstallerStandard.GetStandardDirectories())
340 {
341 if (directory.StartsWith(standardDirectory, StringComparison.Ordinal))
342 {
343 this.OnMessage(WixWarnings.StandardDirectoryConflictInMergeModule(row.SourceLineNumbers, directory, standardDirectory));
344 }
345 }
346 }
347 }
348 }
349 break;
350
351 case "Extension":
352 if (OutputType.Product == output.Type)
353 {
354 this.ResolveFeatures(table.Rows, 1, 4, componentsToFeatures, multipleFeatureComponents);
355 }
356 break;
357
358 case "ModuleSubstitution":
359 containsModuleSubstitution = true;
360 break;
361
362 case "ModuleConfiguration":
363 containsModuleConfiguration = true;
364 break;
365
366 case "MsiAssembly":
367 if (OutputType.Product == output.Type)
368 {
369 this.ResolveFeatures(table.Rows, 0, 1, componentsToFeatures, multipleFeatureComponents);
370 }
371 break;
372
373 case "ProgId":
374 // the Extension table is required with a ProgId table
375 this.activeOutput.EnsureTable(this.tableDefinitions["Extension"]);
376 break;
377
378 case "Property":
379 // Remove property rows with no value. These are properties associated with
380 // AppSearch but without a default value.
381 for (int i = 0; i < table.Rows.Count; i++)
382 {
383 if (null == table.Rows[i][1])
384 {
385 table.Rows.RemoveAt(i);
386 i--;
387 }
388 }
389 break;
390
391 case "PublishComponent":
392 if (OutputType.Product == output.Type)
393 {
394 this.ResolveFeatures(table.Rows, 2, 4, componentsToFeatures, multipleFeatureComponents);
395 }
396 break;
397
398 case "Shortcut":
399 if (OutputType.Product == output.Type)
400 {
401 this.ResolveFeatures(table.Rows, 3, 4, componentsToFeatures, multipleFeatureComponents);
402 }
403 break;
404
405 case "TypeLib":
406 if (OutputType.Product == output.Type)
407 {
408 this.ResolveFeatures(table.Rows, 2, 6, componentsToFeatures, multipleFeatureComponents);
409 }
410 break;
411
412 case "WixAction":
413 if (this.sectionIdOnRows)
414 {
415 foreach (Row row in table.Rows)
416 {
417 row.SectionId = sectionId;
418 }
419 }
420 actionRows.AddRange(table.Rows);
421 break;
422
423 case "WixCustomTable":
424 this.LinkCustomTable(table, customTableDefinitions);
425 copyRows = false; // we've created table definitions from these rows, no need to process them any longer
426 break;
427
428 case "WixCustomRow":
429 foreach (Row row in table.Rows)
430 {
431 row.SectionId = (this.sectionIdOnRows ? sectionId : null);
432 customRows.Add(row);
433 }
434 copyRows = false;
435 break;
436
437 case "WixEnsureTable":
438 ensureTableRows.AddRange(table.Rows);
439 break;
440
441 case "WixFile":
442 foreach (Row row in table.Rows)
443 {
444 // DiskId is not valid when creating a module, so set it to
445 // 0 for all files to ensure proper sorting in the binder
446 if (OutputType.Module == this.activeOutput.Type)
447 {
448 row[5] = 0;
449 }
450
451 // if the short file name was generated, check for collisions
452 if (0x1 == (int)row[9])
453 {
454 generatedShortFileNameIdentifiers.Add((string)row[0]);
455 }
456 }
457 break;
458
459 case "WixMerge":
460 if (OutputType.Product == output.Type)
461 {
462 this.ResolveFeatures(table.Rows, 0, 7, modulesToFeatures, null);
463 }
464 break;
465
466 case "WixSuppressAction":
467 suppressActionRows.AddRange(table.Rows);
468 break;
469
470 case "WixVariable":
471 // check for colliding values and collect the wix variable rows
472 foreach (WixVariableRow row in table.Rows)
473 {
474 WixVariableRow collidingRow = (WixVariableRow)wixVariables[row.Id];
475
476 if (null == collidingRow || (collidingRow.Overridable && !row.Overridable))
477 {
478 wixVariables[row.Id] = row;
479 }
480 else if (!row.Overridable || (collidingRow.Overridable && row.Overridable))
481 {
482 this.OnMessage(WixErrors.WixVariableCollision(row.SourceLineNumbers, row.Id));
483 }
484 }
485 copyRows = false;
486 break;
487 }
488
489 if (copyRows)
490 {
491 Table outputTable = this.activeOutput.EnsureTable(this.tableDefinitions[table.Name]);
492 this.CopyTableRowsToOutputTable(table, outputTable, sectionId);
493 }
494 }
495 }
496
497 // copy the module to feature connections into the output
498 if (0 < modulesToFeatures.Count)
499 {
500 Table wixFeatureModulesTable = this.activeOutput.EnsureTable(this.tableDefinitions["WixFeatureModules"]);
501
502 foreach (ConnectToFeature connectToFeature in modulesToFeatures)
503 {
504 foreach (string feature in connectToFeature.ConnectFeatures)
505 {
506 Row row = wixFeatureModulesTable.CreateRow(null);
507 row[0] = feature;
508 row[1] = connectToFeature.ChildId;
509 }
510 }
511 }
512
513 // ensure the creation of tables that need to exist
514 if (0 < ensureTableRows.Count)
515 {
516 foreach (Row row in ensureTableRows)
517 {
518 string tableId = (string)row[0];
519 TableDefinition tableDef = null;
520
521 try
522 {
523 tableDef = this.tableDefinitions[tableId];
524 }
525 catch (WixMissingTableDefinitionException)
526 {
527 tableDef = customTableDefinitions[tableId];
528 }
529
530 this.activeOutput.EnsureTable(tableDef);
531 }
532 }
533
534 // copy all the suppress action rows to the output to suppress actions from merge modules
535 if (0 < suppressActionRows.Count)
536 {
537 Table suppressActionTable = this.activeOutput.EnsureTable(this.tableDefinitions["WixSuppressAction"]);
538 suppressActionRows.ForEach(r => suppressActionTable.Rows.Add(r));
539 }
540
541 // sequence all the actions
542 this.SequenceActions(actionRows, suppressActionRows);
543
544 // check for missing table and add them or display an error as appropriate
545 switch (this.activeOutput.Type)
546 {
547 case OutputType.Module:
548 this.activeOutput.EnsureTable(this.tableDefinitions["Component"]);
549 this.activeOutput.EnsureTable(this.tableDefinitions["Directory"]);
550 this.activeOutput.EnsureTable(this.tableDefinitions["FeatureComponents"]);
551 this.activeOutput.EnsureTable(this.tableDefinitions["File"]);
552 this.activeOutput.EnsureTable(this.tableDefinitions["ModuleComponents"]);
553 this.activeOutput.EnsureTable(this.tableDefinitions["ModuleSignature"]);
554 break;
555 case OutputType.PatchCreation:
556 Table imageFamiliesTable = this.activeOutput.Tables["ImageFamilies"];
557 Table targetImagesTable = this.activeOutput.Tables["TargetImages"];
558 Table upgradedImagesTable = this.activeOutput.Tables["UpgradedImages"];
559
560 if (null == imageFamiliesTable || 1 > imageFamiliesTable.Rows.Count)
561 {
562 this.OnMessage(WixErrors.ExpectedRowInPatchCreationPackage("ImageFamilies"));
563 }
564
565 if (null == targetImagesTable || 1 > targetImagesTable.Rows.Count)
566 {
567 this.OnMessage(WixErrors.ExpectedRowInPatchCreationPackage("TargetImages"));
568 }
569
570 if (null == upgradedImagesTable || 1 > upgradedImagesTable.Rows.Count)
571 {
572 this.OnMessage(WixErrors.ExpectedRowInPatchCreationPackage("UpgradedImages"));
573 }
574
575 this.activeOutput.EnsureTable(this.tableDefinitions["Properties"]);
576 break;
577 case OutputType.Product:
578 this.activeOutput.EnsureTable(this.tableDefinitions["File"]);
579 this.activeOutput.EnsureTable(this.tableDefinitions["Media"]);
580 break;
581 }
582
583 this.CheckForIllegalTables(this.activeOutput);
584
585 // add the custom row data
586 foreach (Row row in customRows)
587 {
588 TableDefinition customTableDefinition = (TableDefinition)customTableDefinitions[row[0].ToString()];
589 Table customTable = this.activeOutput.EnsureTable(customTableDefinition);
590 Row customRow = customTable.CreateRow(row.SourceLineNumbers);
591
592 customRow.SectionId = row.SectionId;
593
594 string[] data = row[1].ToString().Split(Common.CustomRowFieldSeparator);
595
596 for (int i = 0; i < data.Length; ++i)
597 {
598 bool foundColumn = false;
599 string[] item = data[i].Split(colonCharacter, 2);
600
601 for (int j = 0; j < customRow.Fields.Length; ++j)
602 {
603 if (customRow.Fields[j].Column.Name == item[0])
604 {
605 if (0 < item[1].Length)
606 {
607 if (ColumnType.Number == customRow.Fields[j].Column.Type)
608 {
609 try
610 {
611 customRow.Fields[j].Data = Convert.ToInt32(item[1], CultureInfo.InvariantCulture);
612 }
613 catch (FormatException)
614 {
615 this.OnMessage(WixErrors.IllegalIntegerValue(row.SourceLineNumbers, customTableDefinition.Columns[i].Name, customTableDefinition.Name, item[1]));
616 }
617 catch (OverflowException)
618 {
619 this.OnMessage(WixErrors.IllegalIntegerValue(row.SourceLineNumbers, customTableDefinition.Columns[i].Name, customTableDefinition.Name, item[1]));
620 }
621 }
622 else if (ColumnCategory.Identifier == customRow.Fields[j].Column.Category)
623 {
624 if (Common.IsIdentifier(item[1]) || Common.IsValidBinderVariable(item[1]) || ColumnCategory.Formatted == customRow.Fields[j].Column.Category)
625 {
626 customRow.Fields[j].Data = item[1];
627 }
628 else
629 {
630 this.OnMessage(WixErrors.IllegalIdentifier(row.SourceLineNumbers, "Data", item[1]));
631 }
632 }
633 else
634 {
635 customRow.Fields[j].Data = item[1];
636 }
637 }
638 foundColumn = true;
639 break;
640 }
641 }
642
643 if (!foundColumn)
644 {
645 this.OnMessage(WixErrors.UnexpectedCustomTableColumn(row.SourceLineNumbers, item[0]));
646 }
647 }
648
649 for (int i = 0; i < customTableDefinition.Columns.Count; ++i)
650 {
651 if (!customTableDefinition.Columns[i].Nullable && (null == customRow.Fields[i].Data || 0 == customRow.Fields[i].Data.ToString().Length))
652 {
653 this.OnMessage(WixErrors.NoDataForColumn(row.SourceLineNumbers, customTableDefinition.Columns[i].Name, customTableDefinition.Name));
654 }
655 }
656 }
657
658 //correct the section Id in FeatureComponents table
659 if (this.sectionIdOnRows)
660 {
661 Hashtable componentSectionIds = new Hashtable();
662 Table componentTable = output.Tables["Component"];
663
664 if (null != componentTable)
665 {
666 foreach (Row componentRow in componentTable.Rows)
667 {
668 componentSectionIds.Add(componentRow.Fields[0].Data.ToString(), componentRow.SectionId);
669 }
670 }
671
672 Table featureComponentsTable = output.Tables["FeatureComponents"];
673
674 if (null != featureComponentsTable)
675 {
676 foreach (Row featureComponentsRow in featureComponentsTable.Rows)
677 {
678 if (componentSectionIds.Contains(featureComponentsRow.Fields[1].Data.ToString()))
679 {
680 featureComponentsRow.SectionId = (string)componentSectionIds[featureComponentsRow.Fields[1].Data.ToString()];
681 }
682 }
683 }
684 }
685
686 // add the ModuleSubstitution table to the ModuleIgnoreTable
687 if (containsModuleSubstitution)
688 {
689 Table moduleIgnoreTableTable = this.activeOutput.EnsureTable(this.tableDefinitions["ModuleIgnoreTable"]);
690
691 Row moduleIgnoreTableRow = moduleIgnoreTableTable.CreateRow(null);
692 moduleIgnoreTableRow[0] = "ModuleSubstitution";
693 }
694
695 // add the ModuleConfiguration table to the ModuleIgnoreTable
696 if (containsModuleConfiguration)
697 {
698 Table moduleIgnoreTableTable = this.activeOutput.EnsureTable(this.tableDefinitions["ModuleIgnoreTable"]);
699
700 Row moduleIgnoreTableRow = moduleIgnoreTableTable.CreateRow(null);
701 moduleIgnoreTableRow[0] = "ModuleConfiguration";
702 }
703
704 // index all the file rows
705 Table fileTable = this.activeOutput.Tables["File"];
706 RowDictionary<FileRow> indexedFileRows = (null == fileTable) ? new RowDictionary<FileRow>() : new RowDictionary<FileRow>(fileTable);
707
708 // flag all the generated short file name collisions
709 foreach (string fileId in generatedShortFileNameIdentifiers)
710 {
711 FileRow fileRow = indexedFileRows[fileId];
712
713 string[] names = fileRow.FileName.Split('|');
714 string shortFileName = names[0];
715
716 // create lists of conflicting generated short file names
717 if (!generatedShortFileNames.Contains(shortFileName))
718 {
719 generatedShortFileNames.Add(shortFileName, new ArrayList());
720 }
721 ((ArrayList)generatedShortFileNames[shortFileName]).Add(fileRow);
722 }
723
724 // check for generated short file name collisions
725 foreach (DictionaryEntry entry in generatedShortFileNames)
726 {
727 string shortFileName = (string)entry.Key;
728 ArrayList fileRows = (ArrayList)entry.Value;
729
730 if (1 < fileRows.Count)
731 {
732 // sort the rows by DiskId
733 fileRows.Sort();
734
735 this.OnMessage(WixWarnings.GeneratedShortFileNameConflict(((FileRow)fileRows[0]).SourceLineNumbers, shortFileName));
736
737 for (int i = 1; i < fileRows.Count; i++)
738 {
739 FileRow fileRow = (FileRow)fileRows[i];
740
741 if (null != fileRow.SourceLineNumbers)
742 {
743 this.OnMessage(WixWarnings.GeneratedShortFileNameConflict2(fileRow.SourceLineNumbers));
744 }
745 }
746 }
747 }
748
749 // copy the wix variable rows to the output after all overriding has been accounted for.
750 if (0 < wixVariables.Count)
751 {
752 Table wixVariableTable = output.EnsureTable(this.tableDefinitions["WixVariable"]);
753
754 foreach (WixVariableRow row in wixVariables.Values)
755 {
756 wixVariableTable.Rows.Add(row);
757 }
758 }
759
760 // Bundles have groups of data that must be flattened in a way different from other types.
761 this.FlattenBundleTables(output);
762
763 if (Messaging.Instance.EncounteredError)
764 {
765 return null;
766 }
767
768 this.CheckOutputConsistency(output);
769
770 // inspect the output
771 InspectorCore inspectorCore = new InspectorCore();
772 foreach (InspectorExtension inspectorExtension in this.inspectorExtensions)
773 {
774 inspectorExtension.Core = inspectorCore;
775 inspectorExtension.InspectOutput(output);
776
777 // reset
778 inspectorExtension.Core = null;
779 }
780 }
781 finally
782 {
783 this.activeOutput = null;
784 }
785
786 return Messaging.Instance.EncounteredError ? null : output;
787 }
788
789 /// <summary>
790 /// Links the definition of a custom table.
791 /// </summary>
792 /// <param name="table">The table to link.</param>
793 /// <param name="customTableDefinitions">Receives the linked definition of the custom table.</param>
794 private void LinkCustomTable(Table table, TableDefinitionCollection customTableDefinitions)
795 {
796 foreach (Row row in table.Rows)
797 {
798 bool bootstrapperApplicationData = (null != row[13] && 1 == (int)row[13]);
799
800 if (null == row[4])
801 {
802 this.OnMessage(WixErrors.ExpectedAttribute(row.SourceLineNumbers, "CustomTable/Column", "PrimaryKey"));
803 }
804
805 string[] columnNames = row[2].ToString().Split('\t');
806 string[] columnTypes = row[3].ToString().Split('\t');
807 string[] primaryKeys = row[4].ToString().Split('\t');
808 string[] minValues = row[5] == null ? null : row[5].ToString().Split('\t');
809 string[] maxValues = row[6] == null ? null : row[6].ToString().Split('\t');
810 string[] keyTables = row[7] == null ? null : row[7].ToString().Split('\t');
811 string[] keyColumns = row[8] == null ? null : row[8].ToString().Split('\t');
812 string[] categories = row[9] == null ? null : row[9].ToString().Split('\t');
813 string[] sets = row[10] == null ? null : row[10].ToString().Split('\t');
814 string[] descriptions = row[11] == null ? null : row[11].ToString().Split('\t');
815 string[] modularizations = row[12] == null ? null : row[12].ToString().Split('\t');
816
817 int currentPrimaryKey = 0;
818
819 List<ColumnDefinition> columns = new List<ColumnDefinition>(columnNames.Length);
820 for (int i = 0; i < columnNames.Length; ++i)
821 {
822 string name = columnNames[i];
823 ColumnType type = ColumnType.Unknown;
824
825 if (columnTypes[i].StartsWith("s", StringComparison.OrdinalIgnoreCase))
826 {
827 type = ColumnType.String;
828 }
829 else if (columnTypes[i].StartsWith("l", StringComparison.OrdinalIgnoreCase))
830 {
831 type = ColumnType.Localized;
832 }
833 else if (columnTypes[i].StartsWith("i", StringComparison.OrdinalIgnoreCase))
834 {
835 type = ColumnType.Number;
836 }
837 else if (columnTypes[i].StartsWith("v", StringComparison.OrdinalIgnoreCase))
838 {
839 type = ColumnType.Object;
840 }
841 else
842 {
843 throw new WixException(WixErrors.UnknownCustomTableColumnType(row.SourceLineNumbers, columnTypes[i]));
844 }
845
846 bool nullable = columnTypes[i].Substring(0, 1) == columnTypes[i].Substring(0, 1).ToUpper(CultureInfo.InvariantCulture);
847 int length = Convert.ToInt32(columnTypes[i].Substring(1), CultureInfo.InvariantCulture);
848
849 bool primaryKey = false;
850 if (currentPrimaryKey < primaryKeys.Length && primaryKeys[currentPrimaryKey] == columnNames[i])
851 {
852 primaryKey = true;
853 currentPrimaryKey++;
854 }
855
856 bool minValSet = null != minValues && null != minValues[i] && 0 < minValues[i].Length;
857 int minValue = 0;
858 if (minValSet)
859 {
860 minValue = Convert.ToInt32(minValues[i], CultureInfo.InvariantCulture);
861 }
862
863 bool maxValSet = null != maxValues && null != maxValues[i] && 0 < maxValues[i].Length;
864 int maxValue = 0;
865 if (maxValSet)
866 {
867 maxValue = Convert.ToInt32(maxValues[i], CultureInfo.InvariantCulture);
868 }
869
870 bool keyColumnSet = null != keyColumns && null != keyColumns[i] && 0 < keyColumns[i].Length;
871 int keyColumn = 0;
872 if (keyColumnSet)
873 {
874 keyColumn = Convert.ToInt32(keyColumns[i], CultureInfo.InvariantCulture);
875 }
876
877 ColumnCategory category = ColumnCategory.Unknown;
878 if (null != categories && null != categories[i] && 0 < categories[i].Length)
879 {
880 switch (categories[i])
881 {
882 case "Text":
883 category = ColumnCategory.Text;
884 break;
885 case "UpperCase":
886 category = ColumnCategory.UpperCase;
887 break;
888 case "LowerCase":
889 category = ColumnCategory.LowerCase;
890 break;
891 case "Integer":
892 category = ColumnCategory.Integer;
893 break;
894 case "DoubleInteger":
895 category = ColumnCategory.DoubleInteger;
896 break;
897 case "TimeDate":
898 category = ColumnCategory.TimeDate;
899 break;
900 case "Identifier":
901 category = ColumnCategory.Identifier;
902 break;
903 case "Property":
904 category = ColumnCategory.Property;
905 break;
906 case "Filename":
907 category = ColumnCategory.Filename;
908 break;
909 case "WildCardFilename":
910 category = ColumnCategory.WildCardFilename;
911 break;
912 case "Path":
913 category = ColumnCategory.Path;
914 break;
915 case "Paths":
916 category = ColumnCategory.Paths;
917 break;
918 case "AnyPath":
919 category = ColumnCategory.AnyPath;
920 break;
921 case "DefaultDir":
922 category = ColumnCategory.DefaultDir;
923 break;
924 case "RegPath":
925 category = ColumnCategory.RegPath;
926 break;
927 case "Formatted":
928 category = ColumnCategory.Formatted;
929 break;
930 case "FormattedSddl":
931 category = ColumnCategory.FormattedSDDLText;
932 break;
933 case "Template":
934 category = ColumnCategory.Template;
935 break;
936 case "Condition":
937 category = ColumnCategory.Condition;
938 break;
939 case "Guid":
940 category = ColumnCategory.Guid;
941 break;
942 case "Version":
943 category = ColumnCategory.Version;
944 break;
945 case "Language":
946 category = ColumnCategory.Language;
947 break;
948 case "Binary":
949 category = ColumnCategory.Binary;
950 break;
951 case "CustomSource":
952 category = ColumnCategory.CustomSource;
953 break;
954 case "Cabinet":
955 category = ColumnCategory.Cabinet;
956 break;
957 case "Shortcut":
958 category = ColumnCategory.Shortcut;
959 break;
960 default:
961 break;
962 }
963 }
964
965 string keyTable = keyTables != null ? keyTables[i] : null;
966 string setValue = sets != null ? sets[i] : null;
967 string description = descriptions != null ? descriptions[i] : null;
968 string modString = modularizations != null ? modularizations[i] : null;
969 ColumnModularizeType modularization = ColumnModularizeType.None;
970 if (modString != null)
971 {
972 switch (modString)
973 {
974 case "None":
975 modularization = ColumnModularizeType.None;
976 break;
977 case "Column":
978 modularization = ColumnModularizeType.Column;
979 break;
980 case "Property":
981 modularization = ColumnModularizeType.Property;
982 break;
983 case "Condition":
984 modularization = ColumnModularizeType.Condition;
985 break;
986 case "CompanionFile":
987 modularization = ColumnModularizeType.CompanionFile;
988 break;
989 case "SemicolonDelimited":
990 modularization = ColumnModularizeType.SemicolonDelimited;
991 break;
992 }
993 }
994
995 ColumnDefinition columnDefinition = new ColumnDefinition(name, type, length, primaryKey, nullable, modularization, ColumnType.Localized == type, minValSet, minValue, maxValSet, maxValue, keyTable, keyColumnSet, keyColumn, category, setValue, description, true, true);
996 columns.Add(columnDefinition);
997 }
998
999 TableDefinition customTable = new TableDefinition((string)row[0], columns, false, bootstrapperApplicationData, bootstrapperApplicationData);
1000 customTableDefinitions.Add(customTable);
1001 }
1002 }
1003
1004 /// <summary>
1005 /// Checks for any tables in the output which are not allowed in the output type.
1006 /// </summary>
1007 /// <param name="output">The output to check.</param>
1008 private void CheckForIllegalTables(Output output)
1009 {
1010 foreach (Table table in output.Tables)
1011 {
1012 switch (output.Type)
1013 {
1014 case OutputType.Module:
1015 if ("BBControl" == table.Name ||
1016 "Billboard" == table.Name ||
1017 "CCPSearch" == table.Name ||
1018 "Feature" == table.Name ||
1019 "LaunchCondition" == table.Name ||
1020 "Media" == table.Name ||
1021 "Patch" == table.Name ||
1022 "Upgrade" == table.Name ||
1023 "WixMerge" == table.Name)
1024 {
1025 foreach (Row row in table.Rows)
1026 {
1027 this.OnMessage(WixErrors.UnexpectedTableInMergeModule(row.SourceLineNumbers, table.Name));
1028 }
1029 }
1030 else if ("Error" == table.Name)
1031 {
1032 foreach (Row row in table.Rows)
1033 {
1034 this.OnMessage(WixWarnings.DangerousTableInMergeModule(row.SourceLineNumbers, table.Name));
1035 }
1036 }
1037 break;
1038 case OutputType.PatchCreation:
1039 if (!table.Definition.Unreal &&
1040 "_SummaryInformation" != table.Name &&
1041 "ExternalFiles" != table.Name &&
1042 "FamilyFileRanges" != table.Name &&
1043 "ImageFamilies" != table.Name &&
1044 "PatchMetadata" != table.Name &&
1045 "PatchSequence" != table.Name &&
1046 "Properties" != table.Name &&
1047 "TargetFiles_OptionalData" != table.Name &&
1048 "TargetImages" != table.Name &&
1049 "UpgradedFiles_OptionalData" != table.Name &&
1050 "UpgradedFilesToIgnore" != table.Name &&
1051 "UpgradedImages" != table.Name)
1052 {
1053 foreach (Row row in table.Rows)
1054 {
1055 this.OnMessage(WixErrors.UnexpectedTableInPatchCreationPackage(row.SourceLineNumbers, table.Name));
1056 }
1057 }
1058 break;
1059 case OutputType.Patch:
1060 if (!table.Definition.Unreal &&
1061 "_SummaryInformation" != table.Name &&
1062 "Media" != table.Name &&
1063 "MsiPatchMetadata" != table.Name &&
1064 "MsiPatchSequence" != table.Name)
1065 {
1066 foreach (Row row in table.Rows)
1067 {
1068 this.OnMessage(WixErrors.UnexpectedTableInPatch(row.SourceLineNumbers, table.Name));
1069 }
1070 }
1071 break;
1072 case OutputType.Product:
1073 if ("ModuleAdminExecuteSequence" == table.Name ||
1074 "ModuleAdminUISequence" == table.Name ||
1075 "ModuleAdvtExecuteSequence" == table.Name ||
1076 "ModuleAdvtUISequence" == table.Name ||
1077 "ModuleComponents" == table.Name ||
1078 "ModuleConfiguration" == table.Name ||
1079 "ModuleDependency" == table.Name ||
1080 "ModuleExclusion" == table.Name ||
1081 "ModuleIgnoreTable" == table.Name ||
1082 "ModuleInstallExecuteSequence" == table.Name ||
1083 "ModuleInstallUISequence" == table.Name ||
1084 "ModuleSignature" == table.Name ||
1085 "ModuleSubstitution" == table.Name)
1086 {
1087 foreach (Row row in table.Rows)
1088 {
1089 this.OnMessage(WixWarnings.UnexpectedTableInProduct(row.SourceLineNumbers, table.Name));
1090 }
1091 }
1092 break;
1093 }
1094 }
1095 }
1096
1097 /// <summary>
1098 /// Performs various consistency checks on the output.
1099 /// </summary>
1100 /// <param name="output">Output containing instance transform definitions.</param>
1101 private void CheckOutputConsistency(Output output)
1102 {
1103 // Get the output's minimum installer version
1104 int outputInstallerVersion = int.MinValue;
1105 Table summaryInformationTable = output.Tables["_SummaryInformation"];
1106 if (null != summaryInformationTable)
1107 {
1108 foreach (Row row in summaryInformationTable.Rows)
1109 {
1110 if (14 == (int)row[0])
1111 {
1112 outputInstallerVersion = Convert.ToInt32(row[1], CultureInfo.InvariantCulture);
1113 break;
1114 }
1115 }
1116 }
1117
1118 // ensure the Error table exists if output is marked for MSI 1.0 or below (see ICE40)
1119 if (100 >= outputInstallerVersion && OutputType.Product == output.Type)
1120 {
1121 output.EnsureTable(this.tableDefinitions["Error"]);
1122 }
1123
1124 // check for the presence of tables/rows/columns that require MSI 1.1 or later
1125 if (110 > outputInstallerVersion)
1126 {
1127 Table isolatedComponentTable = output.Tables["IsolatedComponent"];
1128 if (null != isolatedComponentTable)
1129 {
1130 foreach (Row row in isolatedComponentTable.Rows)
1131 {
1132 this.OnMessage(WixWarnings.TableIncompatibleWithInstallerVersion(row.SourceLineNumbers, "IsolatedComponent", outputInstallerVersion));
1133 }
1134 }
1135 }
1136
1137 // check for the presence of tables/rows/columns that require MSI 4.0 or later
1138 if (400 > outputInstallerVersion)
1139 {
1140 Table shortcutTable = output.Tables["Shortcut"];
1141 if (null != shortcutTable)
1142 {
1143 foreach (Row row in shortcutTable.Rows)
1144 {
1145 if (null != row[12] || null != row[13] || null != row[14] || null != row[15])
1146 {
1147 this.OnMessage(WixWarnings.ColumnsIncompatibleWithInstallerVersion(row.SourceLineNumbers, "Shortcut", outputInstallerVersion));
1148 }
1149 }
1150 }
1151 }
1152 }
1153
1154 /// <summary>
1155 /// Sends a message to the message delegate if there is one.
1156 /// </summary>
1157 /// <param name="mea">Message event arguments.</param>
1158 public void OnMessage(MessageEventArgs e)
1159 {
1160 Messaging.Instance.OnMessage(e);
1161 }
1162
1163 /// <summary>
1164 /// Load the standard action symbols.
1165 /// </summary>
1166 /// <param name="allSymbols">Collection of symbols.</param>
1167 private void LoadStandardActionSymbols(IDictionary<string, Symbol> allSymbols)
1168 {
1169 foreach (WixActionRow actionRow in this.standardActions)
1170 {
1171 Symbol actionSymbol = new Symbol(actionRow);
1172
1173 // If the action's symbol has not already been defined (i.e. overriden by the user), add it now.
1174 if (!allSymbols.ContainsKey(actionSymbol.Name))
1175 {
1176 allSymbols.Add(actionSymbol.Name, actionSymbol);
1177 }
1178 }
1179 }
1180
1181 /// <summary>
1182 /// Process the complex references.
1183 /// </summary>
1184 /// <param name="output">Active output to add sections to.</param>
1185 /// <param name="sections">Sections that are referenced during the link process.</param>
1186 /// <param name="referencedComponents">Collection of all components referenced by complex reference.</param>
1187 /// <param name="componentsToFeatures">Component to feature complex references.</param>
1188 /// <param name="featuresToFeatures">Feature to feature complex references.</param>
1189 /// <param name="modulesToFeatures">Module to feature complex references.</param>
1190 private void ProcessComplexReferences(Output output, IEnumerable<Section> sections, ISet<string> referencedComponents, ConnectToFeatureCollection componentsToFeatures, ConnectToFeatureCollection featuresToFeatures, ConnectToFeatureCollection modulesToFeatures)
1191 {
1192 Hashtable componentsToModules = new Hashtable();
1193
1194 foreach (Section section in sections)
1195 {
1196 Table wixComplexReferenceTable = section.Tables["WixComplexReference"];
1197
1198 if (null != wixComplexReferenceTable)
1199 {
1200 foreach (WixComplexReferenceRow wixComplexReferenceRow in wixComplexReferenceTable.Rows)
1201 {
1202 ConnectToFeature connection;
1203 switch (wixComplexReferenceRow.ParentType)
1204 {
1205 case ComplexReferenceParentType.Feature:
1206 switch (wixComplexReferenceRow.ChildType)
1207 {
1208 case ComplexReferenceChildType.Component:
1209 connection = componentsToFeatures[wixComplexReferenceRow.ChildId];
1210 if (null == connection)
1211 {
1212 componentsToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.ChildId, wixComplexReferenceRow.ParentId, wixComplexReferenceRow.IsPrimary));
1213 }
1214 else if (wixComplexReferenceRow.IsPrimary)
1215 {
1216 if (connection.IsExplicitPrimaryFeature)
1217 {
1218 this.OnMessage(WixErrors.MultiplePrimaryReferences(section.SourceLineNumbers, wixComplexReferenceRow.ChildType.ToString(), wixComplexReferenceRow.ChildId, wixComplexReferenceRow.ParentType.ToString(), wixComplexReferenceRow.ParentId, (null != connection.PrimaryFeature ? "Feature" : "Product"), (null != connection.PrimaryFeature ? connection.PrimaryFeature : this.activeOutput.EntrySection.Id)));
1219 continue;
1220 }
1221 else
1222 {
1223 connection.ConnectFeatures.Add(connection.PrimaryFeature); // move the guessed primary feature to the list of connects
1224 connection.PrimaryFeature = wixComplexReferenceRow.ParentId; // set the new primary feature
1225 connection.IsExplicitPrimaryFeature = true; // and make sure we remember that we set it so we can fail if we try to set it again
1226 }
1227 }
1228 else
1229 {
1230 connection.ConnectFeatures.Add(wixComplexReferenceRow.ParentId);
1231 }
1232
1233 // add a row to the FeatureComponents table
1234 Table featureComponentsTable = output.EnsureTable(this.tableDefinitions["FeatureComponents"]);
1235 Row row = featureComponentsTable.CreateRow(null);
1236 if (this.sectionIdOnRows)
1237 {
1238 row.SectionId = section.Id;
1239 }
1240 row[0] = wixComplexReferenceRow.ParentId;
1241 row[1] = wixComplexReferenceRow.ChildId;
1242
1243 // index the component for finding orphaned records
1244 string symbolName = String.Concat("Component:", wixComplexReferenceRow.ChildId);
1245 referencedComponents.Add(symbolName);
1246
1247 break;
1248
1249 case ComplexReferenceChildType.Feature:
1250 connection = featuresToFeatures[wixComplexReferenceRow.ChildId];
1251 if (null != connection)
1252 {
1253 this.OnMessage(WixErrors.MultiplePrimaryReferences(section.SourceLineNumbers, wixComplexReferenceRow.ChildType.ToString(), wixComplexReferenceRow.ChildId, wixComplexReferenceRow.ParentType.ToString(), wixComplexReferenceRow.ParentId, (null != connection.PrimaryFeature ? "Feature" : "Product"), (null != connection.PrimaryFeature ? connection.PrimaryFeature : this.activeOutput.EntrySection.Id)));
1254 continue;
1255 }
1256
1257 featuresToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.ChildId, wixComplexReferenceRow.ParentId, wixComplexReferenceRow.IsPrimary));
1258 break;
1259
1260 case ComplexReferenceChildType.Module:
1261 connection = modulesToFeatures[wixComplexReferenceRow.ChildId];
1262 if (null == connection)
1263 {
1264 modulesToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.ChildId, wixComplexReferenceRow.ParentId, wixComplexReferenceRow.IsPrimary));
1265 }
1266 else if (wixComplexReferenceRow.IsPrimary)
1267 {
1268 if (connection.IsExplicitPrimaryFeature)
1269 {
1270 this.OnMessage(WixErrors.MultiplePrimaryReferences(section.SourceLineNumbers, wixComplexReferenceRow.ChildType.ToString(), wixComplexReferenceRow.ChildId, wixComplexReferenceRow.ParentType.ToString(), wixComplexReferenceRow.ParentId, (null != connection.PrimaryFeature ? "Feature" : "Product"), (null != connection.PrimaryFeature ? connection.PrimaryFeature : this.activeOutput.EntrySection.Id)));
1271 continue;
1272 }
1273 else
1274 {
1275 connection.ConnectFeatures.Add(connection.PrimaryFeature); // move the guessed primary feature to the list of connects
1276 connection.PrimaryFeature = wixComplexReferenceRow.ParentId; // set the new primary feature
1277 connection.IsExplicitPrimaryFeature = true; // and make sure we remember that we set it so we can fail if we try to set it again
1278 }
1279 }
1280 else
1281 {
1282 connection.ConnectFeatures.Add(wixComplexReferenceRow.ParentId);
1283 }
1284 break;
1285
1286 default:
1287 throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnexpectedComplexReferenceChildType, Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType)));
1288 }
1289 break;
1290
1291 case ComplexReferenceParentType.Module:
1292 switch (wixComplexReferenceRow.ChildType)
1293 {
1294 case ComplexReferenceChildType.Component:
1295 if (componentsToModules.ContainsKey(wixComplexReferenceRow.ChildId))
1296 {
1297 this.OnMessage(WixErrors.ComponentReferencedTwice(section.SourceLineNumbers, wixComplexReferenceRow.ChildId));
1298 continue;
1299 }
1300 else
1301 {
1302 componentsToModules.Add(wixComplexReferenceRow.ChildId, wixComplexReferenceRow); // should always be new
1303
1304 // add a row to the ModuleComponents table
1305 Table moduleComponentsTable = output.EnsureTable(this.tableDefinitions["ModuleComponents"]);
1306 Row row = moduleComponentsTable.CreateRow(null);
1307 if (this.sectionIdOnRows)
1308 {
1309 row.SectionId = section.Id;
1310 }
1311 row[0] = wixComplexReferenceRow.ChildId;
1312 row[1] = wixComplexReferenceRow.ParentId;
1313 row[2] = wixComplexReferenceRow.ParentLanguage;
1314 }
1315
1316 // index the component for finding orphaned records
1317 string componentSymbolName = String.Concat("Component:", wixComplexReferenceRow.ChildId);
1318 referencedComponents.Add(componentSymbolName);
1319
1320 break;
1321
1322 default:
1323 throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnexpectedComplexReferenceChildType, Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType)));
1324 }
1325 break;
1326
1327 case ComplexReferenceParentType.Patch:
1328 switch (wixComplexReferenceRow.ChildType)
1329 {
1330 case ComplexReferenceChildType.PatchFamily:
1331 case ComplexReferenceChildType.PatchFamilyGroup:
1332 break;
1333
1334 default:
1335 throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnexpectedComplexReferenceChildType, Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType)));
1336 }
1337 break;
1338
1339 case ComplexReferenceParentType.Product:
1340 switch (wixComplexReferenceRow.ChildType)
1341 {
1342 case ComplexReferenceChildType.Feature:
1343 connection = featuresToFeatures[wixComplexReferenceRow.ChildId];
1344 if (null != connection)
1345 {
1346 this.OnMessage(WixErrors.MultiplePrimaryReferences(section.SourceLineNumbers, wixComplexReferenceRow.ChildType.ToString(), wixComplexReferenceRow.ChildId, wixComplexReferenceRow.ParentType.ToString(), wixComplexReferenceRow.ParentId, (null != connection.PrimaryFeature ? "Feature" : "Product"), (null != connection.PrimaryFeature ? connection.PrimaryFeature : this.activeOutput.EntrySection.Id)));
1347 continue;
1348 }
1349
1350 featuresToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.ChildId, null, wixComplexReferenceRow.IsPrimary));
1351 break;
1352
1353 default:
1354 throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnexpectedComplexReferenceChildType, Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType)));
1355 }
1356 break;
1357
1358 default:
1359 // Note: Groups have been processed before getting here so they are not handled by any case above.
1360 throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnexpectedComplexReferenceChildType, Enum.GetName(typeof(ComplexReferenceParentType), wixComplexReferenceRow.ParentType)));
1361 }
1362 }
1363 }
1364 }
1365 }
1366
1367 /// <summary>
1368 /// Flattens all complex references in all sections in the collection.
1369 /// </summary>
1370 /// <param name="sections">Sections that are referenced during the link process.</param>
1371 private void FlattenSectionsComplexReferences(IEnumerable<Section> sections)
1372 {
1373 Hashtable parentGroups = new Hashtable();
1374 Hashtable parentGroupsSections = new Hashtable();
1375 Hashtable parentGroupsNeedingProcessing = new Hashtable();
1376
1377 // DisplaySectionComplexReferences("--- section's complex references before flattening ---", sections);
1378
1379 // Step 1: Gather all of the complex references that are going participate
1380 // in the flatting process. This means complex references that have "grouping
1381 // parents" of Features, Modules, and, of course, Groups. These references
1382 // that participate in a "grouping parent" will be removed from their section
1383 // now and after processing added back in Step 3 below.
1384 foreach (Section section in sections)
1385 {
1386 Table wixComplexReferenceTable = section.Tables["WixComplexReference"];
1387
1388 if (null != wixComplexReferenceTable)
1389 {
1390 // Count down because we'll sometimes remove items from the list.
1391 for (int i = wixComplexReferenceTable.Rows.Count - 1; i >= 0; --i)
1392 {
1393 WixComplexReferenceRow wixComplexReferenceRow = (WixComplexReferenceRow)wixComplexReferenceTable.Rows[i];
1394
1395 // Only process the "grouping parents" such as FeatureGroup, ComponentGroup, Feature,
1396 // and Module. Non-grouping complex references are simple and
1397 // resolved during normal complex reference resolutions.
1398 if (ComplexReferenceParentType.FeatureGroup == wixComplexReferenceRow.ParentType ||
1399 ComplexReferenceParentType.ComponentGroup == wixComplexReferenceRow.ParentType ||
1400 ComplexReferenceParentType.Feature == wixComplexReferenceRow.ParentType ||
1401 ComplexReferenceParentType.Module == wixComplexReferenceRow.ParentType ||
1402 ComplexReferenceParentType.PatchFamilyGroup == wixComplexReferenceRow.ParentType ||
1403 ComplexReferenceParentType.Product == wixComplexReferenceRow.ParentType)
1404 {
1405 string parentTypeAndId = CombineTypeAndId(wixComplexReferenceRow.ParentType, wixComplexReferenceRow.ParentId);
1406
1407 // Group all complex references with a common parent
1408 // together so we can find them quickly while processing in
1409 // Step 2.
1410 ArrayList childrenComplexRefs = parentGroups[parentTypeAndId] as ArrayList;
1411 if (null == childrenComplexRefs)
1412 {
1413 childrenComplexRefs = new ArrayList();
1414 parentGroups.Add(parentTypeAndId, childrenComplexRefs);
1415 }
1416
1417 childrenComplexRefs.Add(wixComplexReferenceRow);
1418 wixComplexReferenceTable.Rows.RemoveAt(i);
1419
1420 // Remember the mapping from set of complex references with a common
1421 // parent to their section. We'll need this to add them back to the
1422 // correct section in Step 3.
1423 Section parentSection = parentGroupsSections[parentTypeAndId] as Section;
1424 if (null == parentSection)
1425 {
1426 parentGroupsSections.Add(parentTypeAndId, section);
1427 }
1428 // Debug.Assert(section == (Section)parentGroupsSections[parentTypeAndId]);
1429
1430 // If the child of the complex reference is another group, then in Step 2
1431 // we're going to have to process this complex reference again to copy
1432 // the child group's references into the parent group.
1433 if ((ComplexReferenceChildType.ComponentGroup == wixComplexReferenceRow.ChildType) ||
1434 (ComplexReferenceChildType.FeatureGroup == wixComplexReferenceRow.ChildType) ||
1435 (ComplexReferenceChildType.PatchFamilyGroup == wixComplexReferenceRow.ChildType))
1436 {
1437 if (!parentGroupsNeedingProcessing.ContainsKey(parentTypeAndId))
1438 {
1439 parentGroupsNeedingProcessing.Add(parentTypeAndId, section);
1440 }
1441 // Debug.Assert(section == (Section)parentGroupsNeedingProcessing[parentTypeAndId]);
1442 }
1443 }
1444 }
1445 }
1446 }
1447 Debug.Assert(parentGroups.Count == parentGroupsSections.Count);
1448 Debug.Assert(parentGroupsNeedingProcessing.Count <= parentGroups.Count);
1449
1450 // DisplaySectionComplexReferences("\r\n\r\n--- section's complex references middle of flattening ---", sections);
1451
1452 // Step 2: Loop through the parent groups that have nested groups removing
1453 // them from the hash table as they are processed. At the end of this the
1454 // complex references should all be flattened.
1455 string[] keys = new string[parentGroupsNeedingProcessing.Keys.Count];
1456 parentGroupsNeedingProcessing.Keys.CopyTo(keys, 0);
1457
1458 foreach (string key in keys)
1459 {
1460 if (parentGroupsNeedingProcessing.Contains(key))
1461 {
1462 Stack loopDetector = new Stack();
1463 this.FlattenGroup(key, loopDetector, parentGroups, parentGroupsNeedingProcessing);
1464 }
1465 else
1466 {
1467 // the group must have allready been procesed and removed from the hash table
1468 }
1469 }
1470 Debug.Assert(0 == parentGroupsNeedingProcessing.Count);
1471
1472 // Step 3: Finally, ensure that all of the groups that were removed
1473 // in Step 1 and flattened in Step 2 are added to their appropriate
1474 // section. This is where we will toss out the final no-longer-needed
1475 // groups.
1476 foreach (string parentGroup in parentGroups.Keys)
1477 {
1478 Section section = (Section)parentGroupsSections[parentGroup];
1479 Table wixComplexReferenceTable = section.Tables["WixComplexReference"];
1480
1481 foreach (WixComplexReferenceRow wixComplexReferenceRow in (ArrayList)parentGroups[parentGroup])
1482 {
1483 if ((ComplexReferenceParentType.FeatureGroup != wixComplexReferenceRow.ParentType) &&
1484 (ComplexReferenceParentType.ComponentGroup != wixComplexReferenceRow.ParentType) &&
1485 (ComplexReferenceParentType.PatchFamilyGroup != wixComplexReferenceRow.ParentType))
1486 {
1487 wixComplexReferenceTable.Rows.Add(wixComplexReferenceRow);
1488 }
1489 }
1490 }
1491
1492 // DisplaySectionComplexReferences("\r\n\r\n--- section's complex references after flattening ---", sections);
1493 }
1494
1495 private string CombineTypeAndId(ComplexReferenceParentType type, string id)
1496 {
1497 return String.Concat(type.ToString(), ":", id);
1498 }
1499
1500 private string CombineTypeAndId(ComplexReferenceChildType type, string id)
1501 {
1502 return String.Concat(type.ToString(), ":", id);
1503 }
1504
1505 /// <summary>
1506 /// Recursively processes the group.
1507 /// </summary>
1508 /// <param name="parentTypeAndId">String combination type and id of group to process next.</param>
1509 /// <param name="loopDetector">Stack of groups processed thus far. Used to detect loops.</param>
1510 /// <param name="parentGroups">Hash table of complex references grouped by parent id.</param>
1511 /// <param name="parentGroupsNeedingProcessing">Hash table of parent groups that still have nested groups that need to be flattened.</param>
1512 private void FlattenGroup(string parentTypeAndId, Stack loopDetector, Hashtable parentGroups, Hashtable parentGroupsNeedingProcessing)
1513 {
1514 Debug.Assert(parentGroupsNeedingProcessing.Contains(parentTypeAndId));
1515 loopDetector.Push(parentTypeAndId); // push this complex reference parent identfier into the stack for loop verifying
1516
1517 ArrayList allNewChildComplexReferences = new ArrayList();
1518 ArrayList referencesToParent = (ArrayList)parentGroups[parentTypeAndId];
1519 foreach (WixComplexReferenceRow wixComplexReferenceRow in referencesToParent)
1520 {
1521 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);
1522 Debug.Assert(parentTypeAndId == CombineTypeAndId(wixComplexReferenceRow.ParentType, wixComplexReferenceRow.ParentId));
1523
1524 // We are only interested processing when the child is a group.
1525 if ((ComplexReferenceChildType.ComponentGroup == wixComplexReferenceRow.ChildType) ||
1526 (ComplexReferenceChildType.FeatureGroup == wixComplexReferenceRow.ChildType) ||
1527 (ComplexReferenceChildType.PatchFamilyGroup == wixComplexReferenceRow.ChildType))
1528 {
1529 string childTypeAndId = CombineTypeAndId(wixComplexReferenceRow.ChildType, wixComplexReferenceRow.ChildId);
1530 if (loopDetector.Contains(childTypeAndId))
1531 {
1532 // Create a comma delimited list of the references that participate in the
1533 // loop for the error message. Start at the bottom of the stack and work the
1534 // way up to present the loop as a directed graph.
1535 object[] stack = loopDetector.ToArray();
1536 StringBuilder loop = new StringBuilder();
1537 for (int i = stack.Length - 1; i >= 0; --i)
1538 {
1539 loop.Append((string)stack[i]);
1540 if (0 < i)
1541 {
1542 loop.Append(" -> ");
1543 }
1544 }
1545
1546 this.OnMessage(WixErrors.ReferenceLoopDetected(wixComplexReferenceRow.Table.Section == null ? null : wixComplexReferenceRow.Table.Section.SourceLineNumbers, loop.ToString()));
1547
1548 // Cleanup the parentGroupsNeedingProcessing and the loopDetector just like the
1549 // exit of this method does at the end because we are exiting early.
1550 loopDetector.Pop();
1551 parentGroupsNeedingProcessing.Remove(parentTypeAndId);
1552 return; // bail
1553 }
1554
1555 // Check to see if the child group still needs to be processed. If so,
1556 // go do that so that we'll get all of that children's (and children's
1557 // children) complex references correctly merged into our parent group.
1558 if (parentGroupsNeedingProcessing.ContainsKey(childTypeAndId))
1559 {
1560 this.FlattenGroup(childTypeAndId, loopDetector, parentGroups, parentGroupsNeedingProcessing);
1561 }
1562
1563 // If the child is a parent to anything (i.e. the parent has grandchildren)
1564 // clone each of the children's complex references, repoint them to the parent
1565 // complex reference (because we're moving references up the tree), and finally
1566 // add the cloned child's complex reference to the list of complex references
1567 // that we'll eventually add to the parent group.
1568 ArrayList referencesToChild = (ArrayList)parentGroups[childTypeAndId];
1569 if (null != referencesToChild)
1570 {
1571 foreach (WixComplexReferenceRow crefChild in referencesToChild)
1572 {
1573 // Only merge up the non-group items since groups are purged
1574 // after this part of the processing anyway (cloning them would
1575 // be a complete waste of time).
1576 if ((ComplexReferenceChildType.FeatureGroup != crefChild.ChildType) ||
1577 (ComplexReferenceChildType.ComponentGroup != crefChild.ChildType) ||
1578 (ComplexReferenceChildType.PatchFamilyGroup != crefChild.ChildType))
1579 {
1580 WixComplexReferenceRow crefChildClone = crefChild.Clone();
1581 Debug.Assert(crefChildClone.ParentId == wixComplexReferenceRow.ChildId);
1582
1583 crefChildClone.Reparent(wixComplexReferenceRow);
1584 allNewChildComplexReferences.Add(crefChildClone);
1585 }
1586 }
1587 }
1588 }
1589 }
1590
1591 // Add the children group's complex references to the parent
1592 // group. Clean out any left over groups and quietly remove any
1593 // duplicate complex references that occurred during the merge.
1594 referencesToParent.AddRange(allNewChildComplexReferences);
1595 referencesToParent.Sort();
1596 for (int i = referencesToParent.Count - 1; i >= 0; --i)
1597 {
1598 WixComplexReferenceRow wixComplexReferenceRow = (WixComplexReferenceRow)referencesToParent[i];
1599 if ((ComplexReferenceChildType.FeatureGroup == wixComplexReferenceRow.ChildType) ||
1600 (ComplexReferenceChildType.ComponentGroup == wixComplexReferenceRow.ChildType) ||
1601 (ComplexReferenceChildType.PatchFamilyGroup == wixComplexReferenceRow.ChildType))
1602 {
1603 referencesToParent.RemoveAt(i);
1604 }
1605 else if (i > 0)
1606 {
1607 // Since the list is already sorted, we can find duplicates by simply
1608 // looking at the next sibling in the list and tossing out one if they
1609 // match.
1610 WixComplexReferenceRow crefCompare = (WixComplexReferenceRow)referencesToParent[i - 1];
1611 if (0 == wixComplexReferenceRow.CompareToWithoutConsideringPrimary(crefCompare))
1612 {
1613 referencesToParent.RemoveAt(i);
1614 }
1615 }
1616 }
1617
1618 loopDetector.Pop(); // pop this complex reference off the stack since we're done verify the loop here
1619 parentGroupsNeedingProcessing.Remove(parentTypeAndId); // remove the newly processed complex reference
1620 }
1621
1622 /*
1623 /// <summary>
1624 /// Debugging method for displaying the section complex references.
1625 /// </summary>
1626 /// <param name="header">The header.</param>
1627 /// <param name="sections">The sections to display.</param>
1628 private void DisplaySectionComplexReferences(string header, SectionCollection sections)
1629 {
1630 Console.WriteLine(header);
1631 foreach (Section section in sections)
1632 {
1633 Table wixComplexReferenceTable = section.Tables["WixComplexReference"];
1634
1635 foreach (WixComplexReferenceRow cref in wixComplexReferenceTable.Rows)
1636 {
1637 Console.WriteLine("Section: {0} Parent: {1} Type: {2} Child: {3} Primary: {4}", section.Id, cref.ParentId, cref.ParentType, cref.ChildId, cref.IsPrimary);
1638 }
1639 }
1640 }
1641 */
1642
1643 /// <summary>
1644 /// Flattens the tables used in a Bundle.
1645 /// </summary>
1646 /// <param name="output">Output containing the tables to process.</param>
1647 private void FlattenBundleTables(Output output)
1648 {
1649 if (OutputType.Bundle != output.Type)
1650 {
1651 return;
1652 }
1653
1654 // We need to flatten the nested PayloadGroups and PackageGroups under
1655 // UX, Chain, and any Containers. When we're done, the WixGroups table
1656 // will hold Payloads under UX, ChainPackages (references?) under Chain,
1657 // and ChainPackages/Payloads under the attached and any detatched
1658 // Containers.
1659 WixGroupingOrdering groups = new WixGroupingOrdering(output, this);
1660
1661 // Create UX payloads and Package payloads
1662 groups.UseTypes(new string[] { "Container", "Layout", "PackageGroup", "PayloadGroup", "Package" }, new string[] { "PackageGroup", "Package", "PayloadGroup", "Payload" });
1663 groups.FlattenAndRewriteGroups("Package", false);
1664 groups.FlattenAndRewriteGroups("Container", false);
1665 groups.FlattenAndRewriteGroups("Layout", false);
1666
1667 // Create Chain packages...
1668 groups.UseTypes(new string[] { "PackageGroup" }, new string[] { "Package", "PackageGroup" });
1669 groups.FlattenAndRewriteRows("PackageGroup", "WixChain", false);
1670
1671 groups.RemoveUsedGroupRows();
1672 }
1673
1674 /// <summary>
1675 /// Resolves the features connected to other features in the active output.
1676 /// </summary>
1677 /// <param name="featuresToFeatures">Feature to feature complex references.</param>
1678 /// <param name="allSymbols">All symbols loaded from the sections.</param>
1679 private void ResolveFeatureToFeatureConnects(ConnectToFeatureCollection featuresToFeatures, IDictionary<string, Symbol> allSymbols)
1680 {
1681 foreach (ConnectToFeature connection in featuresToFeatures)
1682 {
1683 WixSimpleReferenceRow wixSimpleReferenceRow = new WixSimpleReferenceRow(null, this.tableDefinitions["WixSimpleReference"]);
1684 wixSimpleReferenceRow.TableName = "Feature";
1685 wixSimpleReferenceRow.PrimaryKeys = connection.ChildId;
1686
1687 Symbol symbol;
1688 if (!allSymbols.TryGetValue(wixSimpleReferenceRow.SymbolicName, out symbol))
1689 {
1690 continue;
1691 }
1692
1693 Row row = symbol.Row;
1694 row[1] = connection.PrimaryFeature;
1695 }
1696 }
1697
1698 /// <summary>
1699 /// Copies a table's rows to an output table.
1700 /// </summary>
1701 /// <param name="table">Source table to copy rows from.</param>
1702 /// <param name="outputTable">Destination table in output to copy rows into.</param>
1703 /// <param name="sectionId">Id of the section that the table lives in.</param>
1704 private void CopyTableRowsToOutputTable(Table table, Table outputTable, string sectionId)
1705 {
1706 int[] localizedColumns = new int[table.Definition.Columns.Count];
1707 int localizedColumnCount = 0;
1708
1709 // if there are localization strings, figure out which columns can be localized in this table
1710 if (null != this.localizer)
1711 {
1712 for (int i = 0; i < table.Definition.Columns.Count; i++)
1713 {
1714 if (table.Definition.Columns[i].IsLocalizable)
1715 {
1716 localizedColumns[localizedColumnCount++] = i;
1717 }
1718 }
1719 }
1720
1721 // process each row in the table doing the string resource substitutions
1722 // then add the row to the output
1723 foreach (Row row in table.Rows)
1724 {
1725 for (int j = 0; j < localizedColumnCount; j++)
1726 {
1727 Field field = row.Fields[localizedColumns[j]];
1728
1729 if (null != field.Data)
1730 {
1731 field.Data = this.WixVariableResolver.ResolveVariables(row.SourceLineNumbers, (string)field.Data, true);
1732 }
1733 }
1734
1735 row.SectionId = (this.sectionIdOnRows ? sectionId : null);
1736 outputTable.Rows.Add(row);
1737 }
1738 }
1739
1740 /// <summary>
1741 /// Set sequence numbers for all the actions and create rows in the output object.
1742 /// </summary>
1743 /// <param name="actionRows">Collection of actions to schedule.</param>
1744 /// <param name="suppressActionRows">Collection of actions to suppress.</param>
1745 private void SequenceActions(List<Row> actionRows, List<Row> suppressActionRows)
1746 {
1747 WixActionRowCollection overridableActionRows = new WixActionRowCollection();
1748 WixActionRowCollection requiredActionRows = new WixActionRowCollection();
1749 ArrayList scheduledActionRows = new ArrayList();
1750
1751 // gather the required actions for the output type
1752 if (OutputType.Product == this.activeOutput.Type)
1753 {
1754 // AdminExecuteSequence table
1755 overridableActionRows.Add(this.standardActions[SequenceTable.AdminExecuteSequence, "CostFinalize"]);
1756 overridableActionRows.Add(this.standardActions[SequenceTable.AdminExecuteSequence, "CostInitialize"]);
1757 overridableActionRows.Add(this.standardActions[SequenceTable.AdminExecuteSequence, "FileCost"]);
1758 overridableActionRows.Add(this.standardActions[SequenceTable.AdminExecuteSequence, "InstallAdminPackage"]);
1759 overridableActionRows.Add(this.standardActions[SequenceTable.AdminExecuteSequence, "InstallFiles"]);
1760 overridableActionRows.Add(this.standardActions[SequenceTable.AdminExecuteSequence, "InstallFinalize"]);
1761 overridableActionRows.Add(this.standardActions[SequenceTable.AdminExecuteSequence, "InstallInitialize"]);
1762 overridableActionRows.Add(this.standardActions[SequenceTable.AdminExecuteSequence, "InstallValidate"]);
1763
1764 // AdminUISequence table
1765 overridableActionRows.Add(this.standardActions[SequenceTable.AdminUISequence, "CostFinalize"]);
1766 overridableActionRows.Add(this.standardActions[SequenceTable.AdminUISequence, "CostInitialize"]);
1767 overridableActionRows.Add(this.standardActions[SequenceTable.AdminUISequence, "ExecuteAction"]);
1768 overridableActionRows.Add(this.standardActions[SequenceTable.AdminUISequence, "FileCost"]);
1769
1770 // AdvtExecuteSequence table
1771 overridableActionRows.Add(this.standardActions[SequenceTable.AdvtExecuteSequence, "CostFinalize"]);
1772 overridableActionRows.Add(this.standardActions[SequenceTable.AdvtExecuteSequence, "CostInitialize"]);
1773 overridableActionRows.Add(this.standardActions[SequenceTable.AdvtExecuteSequence, "InstallFinalize"]);
1774 overridableActionRows.Add(this.standardActions[SequenceTable.AdvtExecuteSequence, "InstallInitialize"]);
1775 overridableActionRows.Add(this.standardActions[SequenceTable.AdvtExecuteSequence, "InstallValidate"]);
1776 overridableActionRows.Add(this.standardActions[SequenceTable.AdvtExecuteSequence, "PublishFeatures"]);
1777 overridableActionRows.Add(this.standardActions[SequenceTable.AdvtExecuteSequence, "PublishProduct"]);
1778
1779 // InstallExecuteSequence table
1780 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "CostFinalize"]);
1781 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "CostInitialize"]);
1782 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "FileCost"]);
1783 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "InstallFinalize"]);
1784 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "InstallInitialize"]);
1785 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "InstallValidate"]);
1786 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "ProcessComponents"]);
1787 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "PublishFeatures"]);
1788 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "PublishProduct"]);
1789 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "RegisterProduct"]);
1790 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "RegisterUser"]);
1791 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "UnpublishFeatures"]);
1792 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "ValidateProductID"]);
1793
1794 // InstallUISequence table
1795 overridableActionRows.Add(this.standardActions[SequenceTable.InstallUISequence, "CostFinalize"]);
1796 overridableActionRows.Add(this.standardActions[SequenceTable.InstallUISequence, "CostInitialize"]);
1797 overridableActionRows.Add(this.standardActions[SequenceTable.InstallUISequence, "ExecuteAction"]);
1798 overridableActionRows.Add(this.standardActions[SequenceTable.InstallUISequence, "FileCost"]);
1799 overridableActionRows.Add(this.standardActions[SequenceTable.InstallUISequence, "ValidateProductID"]);
1800 }
1801
1802 // gather the required actions for each table
1803 foreach (Table table in this.activeOutput.Tables)
1804 {
1805 switch (table.Name)
1806 {
1807 case "AppSearch":
1808 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "AppSearch"], true);
1809 overridableActionRows.Add(this.standardActions[SequenceTable.InstallUISequence, "AppSearch"], true);
1810 break;
1811 case "BindImage":
1812 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "BindImage"], true);
1813 break;
1814 case "CCPSearch":
1815 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "AppSearch"], true);
1816 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "CCPSearch"], true);
1817 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "RMCCPSearch"], true);
1818 overridableActionRows.Add(this.standardActions[SequenceTable.InstallUISequence, "AppSearch"], true);
1819 overridableActionRows.Add(this.standardActions[SequenceTable.InstallUISequence, "CCPSearch"], true);
1820 overridableActionRows.Add(this.standardActions[SequenceTable.InstallUISequence, "RMCCPSearch"], true);
1821 break;
1822 case "Class":
1823 overridableActionRows.Add(this.standardActions[SequenceTable.AdvtExecuteSequence, "RegisterClassInfo"], true);
1824 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "RegisterClassInfo"], true);
1825 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "UnregisterClassInfo"], true);
1826 break;
1827 case "Complus":
1828 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "RegisterComPlus"], true);
1829 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "UnregisterComPlus"], true);
1830 break;
1831 case "CreateFolder":
1832 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "CreateFolders"], true);
1833 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "RemoveFolders"], true);
1834 break;
1835 case "DuplicateFile":
1836 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "DuplicateFiles"], true);
1837 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "RemoveDuplicateFiles"], true);
1838 break;
1839 case "Environment":
1840 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "WriteEnvironmentStrings"], true);
1841 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "RemoveEnvironmentStrings"], true);
1842 break;
1843 case "Extension":
1844 overridableActionRows.Add(this.standardActions[SequenceTable.AdvtExecuteSequence, "RegisterExtensionInfo"], true);
1845 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "RegisterExtensionInfo"], true);
1846 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "UnregisterExtensionInfo"], true);
1847 break;
1848 case "File":
1849 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "InstallFiles"], true);
1850 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "RemoveFiles"], true);
1851 break;
1852 case "Font":
1853 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "RegisterFonts"], true);
1854 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "UnregisterFonts"], true);
1855 break;
1856 case "IniFile":
1857 case "RemoveIniFile":
1858 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "WriteIniValues"], true);
1859 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "RemoveIniValues"], true);
1860 break;
1861 case "IsolatedComponent":
1862 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "IsolateComponents"], true);
1863 break;
1864 case "LaunchCondition":
1865 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "LaunchConditions"], true);
1866 overridableActionRows.Add(this.standardActions[SequenceTable.InstallUISequence, "LaunchConditions"], true);
1867 break;
1868 case "MIME":
1869 overridableActionRows.Add(this.standardActions[SequenceTable.AdvtExecuteSequence, "RegisterMIMEInfo"], true);
1870 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "RegisterMIMEInfo"], true);
1871 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "UnregisterMIMEInfo"], true);
1872 break;
1873 case "MoveFile":
1874 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "MoveFiles"], true);
1875 break;
1876 case "MsiAssembly":
1877 overridableActionRows.Add(this.standardActions[SequenceTable.AdvtExecuteSequence, "MsiPublishAssemblies"], true);
1878 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "MsiPublishAssemblies"], true);
1879 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "MsiUnpublishAssemblies"], true);
1880 break;
1881 case "MsiServiceConfig":
1882 case "MsiServiceConfigFailureActions":
1883 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "MsiConfigureServices"], true);
1884 break;
1885 case "ODBCDataSource":
1886 case "ODBCTranslator":
1887 case "ODBCDriver":
1888 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "SetODBCFolders"], true);
1889 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "InstallODBC"], true);
1890 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "RemoveODBC"], true);
1891 break;
1892 case "ProgId":
1893 overridableActionRows.Add(this.standardActions[SequenceTable.AdvtExecuteSequence, "RegisterProgIdInfo"], true);
1894 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "RegisterProgIdInfo"], true);
1895 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "UnregisterProgIdInfo"], true);
1896 break;
1897 case "PublishComponent":
1898 overridableActionRows.Add(this.standardActions[SequenceTable.AdvtExecuteSequence, "PublishComponents"], true);
1899 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "PublishComponents"], true);
1900 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "UnpublishComponents"], true);
1901 break;
1902 case "Registry":
1903 case "RemoveRegistry":
1904 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "WriteRegistryValues"], true);
1905 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "RemoveRegistryValues"], true);
1906 break;
1907 case "RemoveFile":
1908 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "RemoveFiles"], true);
1909 break;
1910 case "SelfReg":
1911 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "SelfRegModules"], true);
1912 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "SelfUnregModules"], true);
1913 break;
1914 case "ServiceControl":
1915 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "StartServices"], true);
1916 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "StopServices"], true);
1917 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "DeleteServices"], true);
1918 break;
1919 case "ServiceInstall":
1920 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "InstallServices"], true);
1921 break;
1922 case "Shortcut":
1923 overridableActionRows.Add(this.standardActions[SequenceTable.AdvtExecuteSequence, "CreateShortcuts"], true);
1924 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "CreateShortcuts"], true);
1925 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "RemoveShortcuts"], true);
1926 break;
1927 case "TypeLib":
1928 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "RegisterTypeLibraries"], true);
1929 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "UnregisterTypeLibraries"], true);
1930 break;
1931 case "Upgrade":
1932 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "FindRelatedProducts"], true);
1933 overridableActionRows.Add(this.standardActions[SequenceTable.InstallUISequence, "FindRelatedProducts"], true);
1934 // Only add the MigrateFeatureStates action if MigrateFeature attribute is set to yes on at least one UpgradeVersion element.
1935 foreach (Row row in table.Rows)
1936 {
1937 int options = (int)row[4];
1938 if (MsiInterop.MsidbUpgradeAttributesMigrateFeatures == (options & MsiInterop.MsidbUpgradeAttributesMigrateFeatures))
1939 {
1940 overridableActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "MigrateFeatureStates"], true);
1941 overridableActionRows.Add(this.standardActions[SequenceTable.InstallUISequence, "MigrateFeatureStates"], true);
1942 break;
1943 }
1944 }
1945 break;
1946 }
1947 }
1948
1949 // index all the action rows (look for collisions)
1950 foreach (WixActionRow actionRow in actionRows)
1951 {
1952 if (actionRow.Overridable) // overridable action
1953 {
1954 WixActionRow collidingActionRow = overridableActionRows[actionRow.SequenceTable, actionRow.Action];
1955
1956 if (null != collidingActionRow)
1957 {
1958 this.OnMessage(WixErrors.OverridableActionCollision(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action));
1959 if (null != collidingActionRow.SourceLineNumbers)
1960 {
1961 this.OnMessage(WixErrors.OverridableActionCollision2(collidingActionRow.SourceLineNumbers));
1962 }
1963 }
1964 else
1965 {
1966 overridableActionRows.Add(actionRow);
1967 }
1968 }
1969 else // unscheduled/scheduled action
1970 {
1971 // unscheduled action (allowed for certain standard actions)
1972 if (null == actionRow.Before && null == actionRow.After && 0 == actionRow.Sequence)
1973 {
1974 WixActionRow standardAction = this.standardActions[actionRow.SequenceTable, actionRow.Action];
1975
1976 if (null != standardAction)
1977 {
1978 // populate the sequence from the standard action
1979 actionRow.Sequence = standardAction.Sequence;
1980 }
1981 else // not a supported unscheduled action
1982 {
1983 throw new InvalidOperationException(WixStrings.EXP_FoundActionRowWithNoSequenceBeforeOrAfterColumnSet);
1984 }
1985 }
1986
1987 WixActionRow collidingActionRow = requiredActionRows[actionRow.SequenceTable, actionRow.Action];
1988
1989 if (null != collidingActionRow)
1990 {
1991 this.OnMessage(WixErrors.ActionCollision(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action));
1992 if (null != collidingActionRow.SourceLineNumbers)
1993 {
1994 this.OnMessage(WixErrors.ActionCollision2(collidingActionRow.SourceLineNumbers));
1995 }
1996 }
1997 else
1998 {
1999 requiredActionRows.Add(actionRow.Clone());
2000 }
2001 }
2002 }
2003
2004 // add the overridable action rows that are not overridden to the required action rows
2005 foreach (WixActionRow actionRow in overridableActionRows)
2006 {
2007 if (null == requiredActionRows[actionRow.SequenceTable, actionRow.Action])
2008 {
2009 requiredActionRows.Add(actionRow.Clone());
2010 }
2011 }
2012
2013 // suppress the required actions that are overridable
2014 foreach (Row suppressActionRow in suppressActionRows)
2015 {
2016 SequenceTable sequenceTable = (SequenceTable)Enum.Parse(typeof(SequenceTable), (string)suppressActionRow[0]);
2017 string action = (string)suppressActionRow[1];
2018
2019 // get the action being suppressed (if it exists)
2020 WixActionRow requiredActionRow = requiredActionRows[sequenceTable, action];
2021
2022 // if there is an overridable row to suppress; suppress it
2023 // there is no warning if there is no action to suppress because the action may be suppressed from a merge module in the binder
2024 if (null != requiredActionRow)
2025 {
2026 if (requiredActionRow.Overridable)
2027 {
2028 this.OnMessage(WixWarnings.SuppressAction(suppressActionRow.SourceLineNumbers, action, sequenceTable.ToString()));
2029 if (null != requiredActionRow.SourceLineNumbers)
2030 {
2031 this.OnMessage(WixWarnings.SuppressAction2(requiredActionRow.SourceLineNumbers));
2032 }
2033 requiredActionRows.Remove(sequenceTable, action);
2034 }
2035 else // suppressing a non-overridable action row
2036 {
2037 this.OnMessage(WixErrors.SuppressNonoverridableAction(suppressActionRow.SourceLineNumbers, sequenceTable.ToString(), action));
2038 if (null != requiredActionRow.SourceLineNumbers)
2039 {
2040 this.OnMessage(WixErrors.SuppressNonoverridableAction2(requiredActionRow.SourceLineNumbers));
2041 }
2042 }
2043 }
2044 }
2045
2046 // create a copy of the required action rows so that new rows can be added while enumerating
2047 WixActionRow[] copyOfRequiredActionRows = new WixActionRow[requiredActionRows.Count];
2048 requiredActionRows.CopyTo(copyOfRequiredActionRows, 0);
2049
2050 // build up dependency trees of the relatively scheduled actions
2051 foreach (WixActionRow actionRow in copyOfRequiredActionRows)
2052 {
2053 if (0 == actionRow.Sequence)
2054 {
2055 // check for standard actions that don't have a sequence number in a merge module
2056 if (OutputType.Module == this.activeOutput.Type && WindowsInstallerStandard.IsStandardAction(actionRow.Action))
2057 {
2058 this.OnMessage(WixErrors.StandardActionRelativelyScheduledInModule(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action));
2059 }
2060
2061 this.SequenceActionRow(actionRow, requiredActionRows);
2062 }
2063 else if (OutputType.Module == this.activeOutput.Type && 0 < actionRow.Sequence && !WindowsInstallerStandard.IsStandardAction(actionRow.Action)) // check for custom actions and dialogs that have a sequence number
2064 {
2065 this.OnMessage(WixErrors.CustomActionSequencedInModule(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action));
2066 }
2067 }
2068
2069 // look for standard actions with sequence restrictions that aren't necessarily scheduled based on the presence of a particular table
2070 if (requiredActionRows.Contains(SequenceTable.InstallExecuteSequence, "DuplicateFiles") && !requiredActionRows.Contains(SequenceTable.InstallExecuteSequence, "InstallFiles"))
2071 {
2072 requiredActionRows.Add(this.standardActions[SequenceTable.InstallExecuteSequence, "InstallFiles"], true);
2073 }
2074
2075 // schedule actions
2076 if (OutputType.Module == this.activeOutput.Type)
2077 {
2078 // add the action row to the list of scheduled action rows
2079 scheduledActionRows.AddRange(requiredActionRows);
2080 }
2081 else
2082 {
2083 // process each sequence table individually
2084 foreach (SequenceTable sequenceTable in Enum.GetValues(typeof(SequenceTable)))
2085 {
2086 // create a collection of just the action rows in this sequence
2087 WixActionRowCollection sequenceActionRows = new WixActionRowCollection();
2088 foreach (WixActionRow actionRow in requiredActionRows)
2089 {
2090 if (sequenceTable == actionRow.SequenceTable)
2091 {
2092 sequenceActionRows.Add(actionRow);
2093 }
2094 }
2095
2096 // schedule the absolutely scheduled actions (by sorting them by their sequence numbers)
2097 ArrayList absoluteActionRows = new ArrayList();
2098 foreach (WixActionRow actionRow in sequenceActionRows)
2099 {
2100 if (0 != actionRow.Sequence)
2101 {
2102 // look for sequence number collisions
2103 foreach (WixActionRow sequenceScheduledActionRow in absoluteActionRows)
2104 {
2105 if (sequenceScheduledActionRow.Sequence == actionRow.Sequence)
2106 {
2107 this.OnMessage(WixWarnings.ActionSequenceCollision(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action, sequenceScheduledActionRow.Action, actionRow.Sequence));
2108 if (null != sequenceScheduledActionRow.SourceLineNumbers)
2109 {
2110 this.OnMessage(WixWarnings.ActionSequenceCollision2(sequenceScheduledActionRow.SourceLineNumbers));
2111 }
2112 }
2113 }
2114
2115 absoluteActionRows.Add(actionRow);
2116 }
2117 }
2118 absoluteActionRows.Sort();
2119
2120 // schedule the relatively scheduled actions (by resolving the dependency trees)
2121 int previousUsedSequence = 0;
2122 ArrayList relativeActionRows = new ArrayList();
2123 for (int j = 0; j < absoluteActionRows.Count; j++)
2124 {
2125 WixActionRow absoluteActionRow = (WixActionRow)absoluteActionRows[j];
2126 int unusedSequence;
2127
2128 // get all the relatively scheduled action rows occuring before this absolutely scheduled action row
2129 RowIndexedList<WixActionRow> allPreviousActionRows = new RowIndexedList<WixActionRow>();
2130 absoluteActionRow.GetAllPreviousActionRows(sequenceTable, allPreviousActionRows);
2131
2132 // get all the relatively scheduled action rows occuring after this absolutely scheduled action row
2133 RowIndexedList<WixActionRow> allNextActionRows = new RowIndexedList<WixActionRow>();
2134 absoluteActionRow.GetAllNextActionRows(sequenceTable, allNextActionRows);
2135
2136 // check for relatively scheduled actions occuring before/after a special action (these have a negative sequence number)
2137 if (0 > absoluteActionRow.Sequence && (0 < allPreviousActionRows.Count || 0 < allNextActionRows.Count))
2138 {
2139 // create errors for all the before actions
2140 foreach (WixActionRow actionRow in allPreviousActionRows)
2141 {
2142 this.OnMessage(WixErrors.ActionScheduledRelativeToTerminationAction(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action, absoluteActionRow.Action));
2143 }
2144
2145 // create errors for all the after actions
2146 foreach (WixActionRow actionRow in allNextActionRows)
2147 {
2148 this.OnMessage(WixErrors.ActionScheduledRelativeToTerminationAction(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action, absoluteActionRow.Action));
2149 }
2150
2151 // if there is source line information for the absolutely scheduled action display it
2152 if (null != absoluteActionRow.SourceLineNumbers)
2153 {
2154 this.OnMessage(WixErrors.ActionScheduledRelativeToTerminationAction2(absoluteActionRow.SourceLineNumbers));
2155 }
2156
2157 continue;
2158 }
2159
2160 // schedule the action rows before this one
2161 unusedSequence = absoluteActionRow.Sequence - 1;
2162 for (int i = allPreviousActionRows.Count - 1; i >= 0; i--)
2163 {
2164 WixActionRow relativeActionRow = (WixActionRow)allPreviousActionRows[i];
2165
2166 // look for collisions
2167 if (unusedSequence == previousUsedSequence)
2168 {
2169 this.OnMessage(WixErrors.NoUniqueActionSequenceNumber(relativeActionRow.SourceLineNumbers, relativeActionRow.SequenceTable.ToString(), relativeActionRow.Action, absoluteActionRow.Action));
2170 if (null != absoluteActionRow.SourceLineNumbers)
2171 {
2172 this.OnMessage(WixErrors.NoUniqueActionSequenceNumber2(absoluteActionRow.SourceLineNumbers));
2173 }
2174
2175 unusedSequence++;
2176 }
2177
2178 relativeActionRow.Sequence = unusedSequence;
2179 relativeActionRows.Add(relativeActionRow);
2180
2181 unusedSequence--;
2182 }
2183
2184 // determine the next used action sequence number
2185 int nextUsedSequence;
2186 if (absoluteActionRows.Count > j + 1)
2187 {
2188 nextUsedSequence = ((WixActionRow)absoluteActionRows[j + 1]).Sequence;
2189 }
2190 else
2191 {
2192 nextUsedSequence = short.MaxValue + 1;
2193 }
2194
2195 // schedule the action rows after this one
2196 unusedSequence = absoluteActionRow.Sequence + 1;
2197 for (int i = 0; i < allNextActionRows.Count; i++)
2198 {
2199 WixActionRow relativeActionRow = (WixActionRow)allNextActionRows[i];
2200
2201 if (unusedSequence == nextUsedSequence)
2202 {
2203 this.OnMessage(WixErrors.NoUniqueActionSequenceNumber(relativeActionRow.SourceLineNumbers, relativeActionRow.SequenceTable.ToString(), relativeActionRow.Action, absoluteActionRow.Action));
2204 if (null != absoluteActionRow.SourceLineNumbers)
2205 {
2206 this.OnMessage(WixErrors.NoUniqueActionSequenceNumber2(absoluteActionRow.SourceLineNumbers));
2207 }
2208
2209 unusedSequence--;
2210 }
2211
2212 relativeActionRow.Sequence = unusedSequence;
2213 relativeActionRows.Add(relativeActionRow);
2214
2215 unusedSequence++;
2216 }
2217
2218 // keep track of this sequence number as the previous used sequence number for the next iteration
2219 previousUsedSequence = absoluteActionRow.Sequence;
2220 }
2221
2222 // add the absolutely and relatively scheduled actions to the list of scheduled actions
2223 scheduledActionRows.AddRange(absoluteActionRows);
2224 scheduledActionRows.AddRange(relativeActionRows);
2225 }
2226 }
2227
2228 // create the action rows for sequences that are not suppressed
2229 foreach (WixActionRow actionRow in scheduledActionRows)
2230 {
2231 // get the table definition for the action (and ensure the proper table exists for a module)
2232 TableDefinition sequenceTableDefinition = null;
2233 switch (actionRow.SequenceTable)
2234 {
2235 case SequenceTable.AdminExecuteSequence:
2236 if (OutputType.Module == this.activeOutput.Type)
2237 {
2238 this.activeOutput.EnsureTable(this.tableDefinitions["AdminExecuteSequence"]);
2239 sequenceTableDefinition = this.tableDefinitions["ModuleAdminExecuteSequence"];
2240 }
2241 else
2242 {
2243 sequenceTableDefinition = this.tableDefinitions["AdminExecuteSequence"];
2244 }
2245 break;
2246 case SequenceTable.AdminUISequence:
2247 if (OutputType.Module == this.activeOutput.Type)
2248 {
2249 this.activeOutput.EnsureTable(this.tableDefinitions["AdminUISequence"]);
2250 sequenceTableDefinition = this.tableDefinitions["ModuleAdminUISequence"];
2251 }
2252 else
2253 {
2254 sequenceTableDefinition = this.tableDefinitions["AdminUISequence"];
2255 }
2256 break;
2257 case SequenceTable.AdvtExecuteSequence:
2258 if (OutputType.Module == this.activeOutput.Type)
2259 {
2260 this.activeOutput.EnsureTable(this.tableDefinitions["AdvtExecuteSequence"]);
2261 sequenceTableDefinition = this.tableDefinitions["ModuleAdvtExecuteSequence"];
2262 }
2263 else
2264 {
2265 sequenceTableDefinition = this.tableDefinitions["AdvtExecuteSequence"];
2266 }
2267 break;
2268 case SequenceTable.InstallExecuteSequence:
2269 if (OutputType.Module == this.activeOutput.Type)
2270 {
2271 this.activeOutput.EnsureTable(this.tableDefinitions["InstallExecuteSequence"]);
2272 sequenceTableDefinition = this.tableDefinitions["ModuleInstallExecuteSequence"];
2273 }
2274 else
2275 {
2276 sequenceTableDefinition = this.tableDefinitions["InstallExecuteSequence"];
2277 }
2278 break;
2279 case SequenceTable.InstallUISequence:
2280 if (OutputType.Module == this.activeOutput.Type)
2281 {
2282 this.activeOutput.EnsureTable(this.tableDefinitions["InstallUISequence"]);
2283 sequenceTableDefinition = this.tableDefinitions["ModuleInstallUISequence"];
2284 }
2285 else
2286 {
2287 sequenceTableDefinition = this.tableDefinitions["InstallUISequence"];
2288 }
2289 break;
2290 }
2291
2292 // create the action sequence row in the output
2293 Table sequenceTable = this.activeOutput.EnsureTable(sequenceTableDefinition);
2294 Row row = sequenceTable.CreateRow(actionRow.SourceLineNumbers);
2295 if (this.sectionIdOnRows)
2296 {
2297 row.SectionId = actionRow.SectionId;
2298 }
2299
2300 if (OutputType.Module == this.activeOutput.Type)
2301 {
2302 row[0] = actionRow.Action;
2303 if (0 != actionRow.Sequence)
2304 {
2305 row[1] = actionRow.Sequence;
2306 }
2307 else
2308 {
2309 bool after = (null == actionRow.Before);
2310 row[2] = after ? actionRow.After : actionRow.Before;
2311 row[3] = after ? 1 : 0;
2312 }
2313 row[4] = actionRow.Condition;
2314 }
2315 else
2316 {
2317 row[0] = actionRow.Action;
2318 row[1] = actionRow.Condition;
2319 row[2] = actionRow.Sequence;
2320 }
2321 }
2322 }
2323
2324 /// <summary>
2325 /// Sequence an action before or after a standard action.
2326 /// </summary>
2327 /// <param name="actionRow">The action row to be sequenced.</param>
2328 /// <param name="requiredActionRows">Collection of actions which must be included.</param>
2329 [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "System.InvalidOperationException.#ctor(System.String)")]
2330 private void SequenceActionRow(WixActionRow actionRow, WixActionRowCollection requiredActionRows)
2331 {
2332 bool after = false;
2333 if (actionRow.After != null)
2334 {
2335 after = true;
2336 }
2337 else if (actionRow.Before == null)
2338 {
2339 throw new InvalidOperationException(WixStrings.EXP_FoundActionRowWithNoSequenceBeforeOrAfterColumnSet);
2340 }
2341
2342 string parentActionName = (after ? actionRow.After : actionRow.Before);
2343 WixActionRow parentActionRow = requiredActionRows[actionRow.SequenceTable, parentActionName];
2344
2345 if (null == parentActionRow)
2346 {
2347 parentActionRow = this.standardActions[actionRow.SequenceTable, parentActionName];
2348
2349 // if the missing parent action is a standard action (with a suggested sequence number), add it
2350 if (null != parentActionRow)
2351 {
2352 // Create a clone to avoid modifying the static copy of the object.
2353 parentActionRow = parentActionRow.Clone();
2354 requiredActionRows.Add(parentActionRow);
2355 }
2356 else
2357 {
2358 throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_FoundActionRowWinNonExistentAction, (after ? "After" : "Before"), parentActionName));
2359 }
2360 }
2361 else if (actionRow == parentActionRow || actionRow.ContainsChildActionRow(parentActionRow)) // cycle detected
2362 {
2363 throw new WixException(WixErrors.ActionCircularDependency(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action, parentActionRow.Action));
2364 }
2365
2366 // Add this action to the appropriate list of dependent action rows.
2367 WixActionRowCollection relatedRows = (after ? parentActionRow.NextActionRows : parentActionRow.PreviousActionRows);
2368 relatedRows.Add(actionRow);
2369 }
2370
2371 /// <summary>
2372 /// Resolve features for columns that have null guid placeholders.
2373 /// </summary>
2374 /// <param name="rows">Rows to resolve.</param>
2375 /// <param name="connectionColumn">Number of the column containing the connection identifier.</param>
2376 /// <param name="featureColumn">Number of the column containing the feature.</param>
2377 /// <param name="connectToFeatures">Connect to feature complex references.</param>
2378 /// <param name="multipleFeatureComponents">Hashtable of known components under multiple features.</param>
2379 private void ResolveFeatures(IEnumerable<Row> rows, int connectionColumn, int featureColumn, ConnectToFeatureCollection connectToFeatures, Hashtable multipleFeatureComponents)
2380 {
2381 foreach (Row row in rows)
2382 {
2383 string connectionId = (string)row[connectionColumn];
2384 string featureId = (string)row[featureColumn];
2385
2386 if (emptyGuid == featureId)
2387 {
2388 ConnectToFeature connection = connectToFeatures[connectionId];
2389
2390 if (null == connection)
2391 {
2392 // display an error for the component or merge module as approrpriate
2393 if (null != multipleFeatureComponents)
2394 {
2395 this.OnMessage(WixErrors.ComponentExpectedFeature(row.SourceLineNumbers, connectionId, row.Table.Name, row.GetPrimaryKey('/')));
2396 }
2397 else
2398 {
2399 this.OnMessage(WixErrors.MergeModuleExpectedFeature(row.SourceLineNumbers, connectionId));
2400 }
2401 }
2402 else
2403 {
2404 // check for unique, implicit, primary feature parents with multiple possible parent features
2405 if (this.ShowPedanticMessages &&
2406 !connection.IsExplicitPrimaryFeature &&
2407 0 < connection.ConnectFeatures.Count)
2408 {
2409 // display a warning for the component or merge module as approrpriate
2410 if (null != multipleFeatureComponents)
2411 {
2412 if (!multipleFeatureComponents.Contains(connectionId))
2413 {
2414 this.OnMessage(WixWarnings.ImplicitComponentPrimaryFeature(connectionId));
2415
2416 // remember this component so only one warning is generated for it
2417 multipleFeatureComponents[connectionId] = null;
2418 }
2419 }
2420 else
2421 {
2422 this.OnMessage(WixWarnings.ImplicitMergeModulePrimaryFeature(connectionId));
2423 }
2424 }
2425
2426 // set the feature
2427 row[featureColumn] = connection.PrimaryFeature;
2428 }
2429 }
2430 }
2431 }
2432
2433 }
2434}
diff --git a/src/WixToolset.Core/Localizer.cs b/src/WixToolset.Core/Localizer.cs
new file mode 100644
index 00000000..3c299896
--- /dev/null
+++ b/src/WixToolset.Core/Localizer.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
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Xml.Linq;
8 using WixToolset.Data;
9 using WixToolset.Data.Rows;
10 using WixToolset.Core.Native;
11
12 /// <summary>
13 /// Parses localization files and localizes database values.
14 /// </summary>
15 public sealed class Localizer
16 {
17 public static readonly XNamespace WxlNamespace = "http://wixtoolset.org/schemas/v4/wxl";
18 private static string XmlElementName = "WixLocalization";
19
20 private Dictionary<string, WixVariableRow> variables;
21 private Dictionary<string, LocalizedControl> localizedControls;
22
23 /// <summary>
24 /// Instantiate a new Localizer.
25 /// </summary>
26 public Localizer()
27 {
28 this.Codepage = -1;
29 this.variables = new Dictionary<string,WixVariableRow>();
30 this.localizedControls = new Dictionary<string, LocalizedControl>();
31 }
32
33 /// <summary>
34 /// Gets the codepage.
35 /// </summary>
36 /// <value>The codepage.</value>
37 public int Codepage { get; private set; }
38
39 /// <summary>
40 /// Loads a localization file from a path on disk.
41 /// </summary>
42 /// <param name="path">Path to library file saved on disk.</param>
43 /// <param name="tableDefinitions">Collection containing TableDefinitions to use when loading the localization file.</param>
44 /// <param name="suppressSchema">Suppress xml schema validation while loading.</param>
45 /// <returns>Returns the loaded localization file.</returns>
46 public static Localization ParseLocalizationFile(string path, TableDefinitionCollection tableDefinitions)
47 {
48 XElement root = XDocument.Load(path).Root;
49 Localization localization = null;
50
51 SourceLineNumber sourceLineNumbers = SourceLineNumber.CreateFromXObject(root);
52 if (Localizer.XmlElementName == root.Name.LocalName)
53 {
54 if (Localizer.WxlNamespace == root.Name.Namespace)
55 {
56 localization = ParseWixLocalizationElement(root, tableDefinitions);
57 }
58 else // invalid or missing namespace
59 {
60 if (null == root.Name.Namespace)
61 {
62 Messaging.Instance.OnMessage(WixErrors.InvalidWixXmlNamespace(sourceLineNumbers, Localizer.XmlElementName, Localizer.WxlNamespace.NamespaceName));
63 }
64 else
65 {
66 Messaging.Instance.OnMessage(WixErrors.InvalidWixXmlNamespace(sourceLineNumbers, Localizer.XmlElementName, root.Name.LocalName, Localizer.WxlNamespace.NamespaceName));
67 }
68 }
69 }
70 else
71 {
72 Messaging.Instance.OnMessage(WixErrors.InvalidDocumentElement(sourceLineNumbers, root.Name.LocalName, "localization", Localizer.XmlElementName));
73 }
74
75 return localization;
76 }
77
78 /// <summary>
79 /// Add a localization file.
80 /// </summary>
81 /// <param name="localization">The localization file to add.</param>
82 public void AddLocalization(Localization localization)
83 {
84 if (-1 == this.Codepage)
85 {
86 this.Codepage = localization.Codepage;
87 }
88
89 foreach (WixVariableRow wixVariableRow in localization.Variables)
90 {
91 Localizer.AddWixVariable(this.variables, wixVariableRow);
92 }
93
94 foreach (KeyValuePair<string, LocalizedControl> localizedControl in localization.LocalizedControls)
95 {
96 if (!this.localizedControls.ContainsKey(localizedControl.Key))
97 {
98 this.localizedControls.Add(localizedControl.Key, localizedControl.Value);
99 }
100 }
101 }
102
103 /// <summary>
104 /// Get a localized data value.
105 /// </summary>
106 /// <param name="id">The name of the localization variable.</param>
107 /// <returns>The localized data value or null if it wasn't found.</returns>
108 public string GetLocalizedValue(string id)
109 {
110 WixVariableRow wixVariableRow;
111 return this.variables.TryGetValue(id, out wixVariableRow) ? wixVariableRow.Value : null;
112 }
113
114 /// <summary>
115 /// Get a localized control.
116 /// </summary>
117 /// <param name="dialog">The optional id of the control's dialog.</param>
118 /// <param name="control">The id of the control.</param>
119 /// <returns>The localized control or null if it wasn't found.</returns>
120 public LocalizedControl GetLocalizedControl(string dialog, string control)
121 {
122 LocalizedControl localizedControl;
123 return this.localizedControls.TryGetValue(LocalizedControl.GetKey(dialog, control), out localizedControl) ? localizedControl : null;
124 }
125
126 /// <summary>
127 /// Adds a WixVariableRow to a dictionary while performing the expected override checks.
128 /// </summary>
129 /// <param name="variables">Dictionary of variable rows.</param>
130 /// <param name="wixVariableRow">Row to add to the variables dictionary.</param>
131 private static void AddWixVariable(IDictionary<string, WixVariableRow> variables, WixVariableRow wixVariableRow)
132 {
133 WixVariableRow existingWixVariableRow;
134 if (!variables.TryGetValue(wixVariableRow.Id, out existingWixVariableRow) || (existingWixVariableRow.Overridable && !wixVariableRow.Overridable))
135 {
136 variables[wixVariableRow.Id] = wixVariableRow;
137 }
138 else if (!wixVariableRow.Overridable)
139 {
140 Messaging.Instance.OnMessage(WixErrors.DuplicateLocalizationIdentifier(wixVariableRow.SourceLineNumbers, wixVariableRow.Id));
141 }
142 }
143
144 /// <summary>
145 /// Parses the WixLocalization element.
146 /// </summary>
147 /// <param name="node">Element to parse.</param>
148 private static Localization ParseWixLocalizationElement(XElement node, TableDefinitionCollection tableDefinitions)
149 {
150 int codepage = -1;
151 string culture = null;
152 SourceLineNumber sourceLineNumbers = SourceLineNumber.CreateFromXObject(node);
153
154 foreach (XAttribute attrib in node.Attributes())
155 {
156 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || Localizer.WxlNamespace == attrib.Name.Namespace)
157 {
158 switch (attrib.Name.LocalName)
159 {
160 case "Codepage":
161 codepage = Common.GetValidCodePage(attrib.Value, true, false, sourceLineNumbers);
162 break;
163 case "Culture":
164 culture = attrib.Value;
165 break;
166 case "Language":
167 // do nothing; @Language is used for locutil which can't convert Culture to lcid
168 break;
169 default:
170 Common.UnexpectedAttribute(sourceLineNumbers, attrib);
171 break;
172 }
173 }
174 else
175 {
176 Common.UnexpectedAttribute(sourceLineNumbers, attrib);
177 }
178 }
179
180 Dictionary<string, WixVariableRow> variables = new Dictionary<string,WixVariableRow>();
181 Dictionary<string, LocalizedControl> localizedControls = new Dictionary<string, LocalizedControl>();
182
183 foreach (XElement child in node.Elements())
184 {
185 if (Localizer.WxlNamespace == child.Name.Namespace)
186 {
187 switch (child.Name.LocalName)
188 {
189 case "String":
190 Localizer.ParseString(child, variables, tableDefinitions);
191 break;
192
193 case "UI":
194 Localizer.ParseUI(child, localizedControls);
195 break;
196
197 default:
198 Messaging.Instance.OnMessage(WixErrors.UnexpectedElement(sourceLineNumbers, node.Name.ToString(), child.Name.ToString()));
199 break;
200 }
201 }
202 else
203 {
204 Messaging.Instance.OnMessage(WixErrors.UnsupportedExtensionElement(sourceLineNumbers, node.Name.ToString(), child.Name.ToString()));
205 }
206 }
207
208 return Messaging.Instance.EncounteredError ? null : new Localization(codepage, culture, variables, localizedControls);
209 }
210
211 /// <summary>
212 /// Parse a localization string into a WixVariableRow.
213 /// </summary>
214 /// <param name="node">Element to parse.</param>
215 private static void ParseString(XElement node, IDictionary<string, WixVariableRow> variables, TableDefinitionCollection tableDefinitions)
216 {
217 string id = null;
218 bool overridable = false;
219 SourceLineNumber sourceLineNumbers = SourceLineNumber.CreateFromXObject(node);
220
221 foreach (XAttribute attrib in node.Attributes())
222 {
223 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || Localizer.WxlNamespace == attrib.Name.Namespace)
224 {
225 switch (attrib.Name.LocalName)
226 {
227 case "Id":
228 id = Common.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
229 break;
230 case "Overridable":
231 overridable = YesNoType.Yes == Common.GetAttributeYesNoValue(sourceLineNumbers, attrib);
232 break;
233 case "Localizable":
234 ; // do nothing
235 break;
236 default:
237 Messaging.Instance.OnMessage(WixErrors.UnexpectedAttribute(sourceLineNumbers, attrib.Parent.Name.ToString(), attrib.Name.ToString()));
238 break;
239 }
240 }
241 else
242 {
243 Messaging.Instance.OnMessage(WixErrors.UnsupportedExtensionAttribute(sourceLineNumbers, attrib.Parent.Name.ToString(), attrib.Name.ToString()));
244 }
245 }
246
247 string value = Common.GetInnerText(node);
248
249 if (null == id)
250 {
251 Messaging.Instance.OnMessage(WixErrors.ExpectedAttribute(sourceLineNumbers, "String", "Id"));
252 }
253 else if (0 == id.Length)
254 {
255 Messaging.Instance.OnMessage(WixErrors.IllegalIdentifier(sourceLineNumbers, "String", "Id", 0));
256 }
257
258 if (!Messaging.Instance.EncounteredError)
259 {
260 WixVariableRow wixVariableRow = new WixVariableRow(sourceLineNumbers, tableDefinitions["WixVariable"]);
261 wixVariableRow.Id = id;
262 wixVariableRow.Overridable = overridable;
263 wixVariableRow.Value = value;
264
265 Localizer.AddWixVariable(variables, wixVariableRow);
266 }
267 }
268
269 /// <summary>
270 /// Parse a localized control.
271 /// </summary>
272 /// <param name="node">Element to parse.</param>
273 /// <param name="localizedControls">Dictionary of localized controls.</param>
274 private static void ParseUI(XElement node, IDictionary<string, LocalizedControl> localizedControls)
275 {
276 string dialog = null;
277 string control = null;
278 int x = CompilerConstants.IntegerNotSet;
279 int y = CompilerConstants.IntegerNotSet;
280 int width = CompilerConstants.IntegerNotSet;
281 int height = CompilerConstants.IntegerNotSet;
282 int attribs = 0;
283 string text = null;
284 SourceLineNumber sourceLineNumbers = SourceLineNumber.CreateFromXObject(node);
285
286 foreach (XAttribute attrib in node.Attributes())
287 {
288 if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || Localizer.WxlNamespace == attrib.Name.Namespace)
289 {
290 switch (attrib.Name.LocalName)
291 {
292 case "Dialog":
293 dialog = Common.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
294 break;
295 case "Control":
296 control = Common.GetAttributeIdentifierValue(sourceLineNumbers, attrib);
297 break;
298 case "X":
299 x = Common.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue);
300 break;
301 case "Y":
302 y = Common.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue);
303 break;
304 case "Width":
305 width = Common.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue);
306 break;
307 case "Height":
308 height = Common.GetAttributeIntegerValue(sourceLineNumbers, attrib, 0, short.MaxValue);
309 break;
310 case "RightToLeft":
311 if (YesNoType.Yes == Common.GetAttributeYesNoValue(sourceLineNumbers, attrib))
312 {
313 attribs |= MsiInterop.MsidbControlAttributesRTLRO;
314 }
315 break;
316 case "RightAligned":
317 if (YesNoType.Yes == Common.GetAttributeYesNoValue(sourceLineNumbers, attrib))
318 {
319 attribs |= MsiInterop.MsidbControlAttributesRightAligned;
320 }
321 break;
322 case "LeftScroll":
323 if (YesNoType.Yes == Common.GetAttributeYesNoValue(sourceLineNumbers, attrib))
324 {
325 attribs |= MsiInterop.MsidbControlAttributesLeftScroll;
326 }
327 break;
328 default:
329 Common.UnexpectedAttribute(sourceLineNumbers, attrib);
330 break;
331 }
332 }
333 else
334 {
335 Common.UnexpectedAttribute(sourceLineNumbers, attrib);
336 }
337 }
338
339 text = Common.GetInnerText(node);
340
341 if (String.IsNullOrEmpty(control) && 0 < attribs)
342 {
343 if (MsiInterop.MsidbControlAttributesRTLRO == (attribs & MsiInterop.MsidbControlAttributesRTLRO))
344 {
345 Messaging.Instance.OnMessage(WixErrors.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.ToString(), "RightToLeft", "Control"));
346 }
347 else if (MsiInterop.MsidbControlAttributesRightAligned == (attribs & MsiInterop.MsidbControlAttributesRightAligned))
348 {
349 Messaging.Instance.OnMessage(WixErrors.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.ToString(), "RightAligned", "Control"));
350 }
351 else if (MsiInterop.MsidbControlAttributesLeftScroll == (attribs & MsiInterop.MsidbControlAttributesLeftScroll))
352 {
353 Messaging.Instance.OnMessage(WixErrors.IllegalAttributeWithoutOtherAttributes(sourceLineNumbers, node.Name.ToString(), "LeftScroll", "Control"));
354 }
355 }
356
357 if (String.IsNullOrEmpty(control) && String.IsNullOrEmpty(dialog))
358 {
359 Messaging.Instance.OnMessage(WixErrors.ExpectedAttributesWithOtherAttribute(sourceLineNumbers, node.Name.ToString(), "Dialog", "Control"));
360 }
361
362 if (!Messaging.Instance.EncounteredError)
363 {
364 LocalizedControl localizedControl = new LocalizedControl(dialog, control, x, y, width, height, attribs, text);
365 string key = localizedControl.GetKey();
366 if (localizedControls.ContainsKey(key))
367 {
368 if (String.IsNullOrEmpty(localizedControl.Control))
369 {
370 Messaging.Instance.OnMessage(WixErrors.DuplicatedUiLocalization(sourceLineNumbers, localizedControl.Dialog));
371 }
372 else
373 {
374 Messaging.Instance.OnMessage(WixErrors.DuplicatedUiLocalization(sourceLineNumbers, localizedControl.Dialog, localizedControl.Control));
375 }
376 }
377 else
378 {
379 localizedControls.Add(key, localizedControl);
380 }
381 }
382 }
383 }
384}
diff --git a/src/WixToolset.Core/Melter.cs b/src/WixToolset.Core/Melter.cs
new file mode 100644
index 00000000..ccc0cb6f
--- /dev/null
+++ b/src/WixToolset.Core/Melter.cs
@@ -0,0 +1,398 @@
1// Copyright (c) .NET Foundation and 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 using Wix = WixToolset.Data.Serialize;
16
17 /// <summary>
18 /// Converts a wixout representation of an MSM database into a ComponentGroup the form of WiX source.
19 /// </summary>
20 public sealed class Melter
21 {
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 }
398}
diff --git a/src/WixToolset.Core/MelterCore.cs b/src/WixToolset.Core/MelterCore.cs
new file mode 100644
index 00000000..75d43619
--- /dev/null
+++ b/src/WixToolset.Core/MelterCore.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
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 : IMessageHandler
11 {
12 /// <summary>
13 /// Gets whether the melter core encountered an error while processing.
14 /// </summary>
15 /// <value>Flag if core encountered an error during processing.</value>
16 public bool EncounteredError
17 {
18 get { return Messaging.Instance.EncounteredError; }
19 }
20
21 /// <summary>
22 /// Sends a message to the message delegate if there is one.
23 /// </summary>
24 /// <param name="mea">Message event arguments.</param>
25 public void OnMessage(MessageEventArgs e)
26 {
27 Messaging.Instance.OnMessage(e);
28 }
29 }
30}
diff --git a/src/WixToolset.Core/MergeMod/NativeMethods.cs b/src/WixToolset.Core/MergeMod/NativeMethods.cs
new file mode 100644
index 00000000..daf259b4
--- /dev/null
+++ b/src/WixToolset.Core/MergeMod/NativeMethods.cs
@@ -0,0 +1,508 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2#if false
3namespace WixToolset.MergeMod
4{
5 using System;
6 using System.Collections;
7 using System.Runtime.CompilerServices;
8 using System.Runtime.InteropServices;
9
10 /// <summary>
11 /// Errors returned by merge operations.
12 /// </summary>
13 [Guid("0ADDA825-2C26-11D2-AD65-00A0C9AF11A6")]
14 internal enum MsmErrorType
15 {
16 /// <summary>
17 /// A request was made to open a module with a language not supported by the module.
18 /// No more general language is supported by the module.
19 /// Adds msmErrorLanguageUnsupported to the Type property and the requested language
20 /// to the Language Property (Error Object). All Error object properties are empty.
21 /// The OpenModule function returns ERROR_INSTALL_LANGUAGE_UNSUPPORTED (as HRESULT).
22 /// </summary>
23 msmErrorLanguageUnsupported = 1,
24
25 /// <summary>
26 /// A request was made to open a module with a supported language but the module has
27 /// an invalid language transform. Adds msmErrorLanguageFailed to the Type property
28 /// and the applied transform's language to the Language Property of the Error object.
29 /// This may not be the requested language if a more general language was used.
30 /// All other properties of the Error object are empty. The OpenModule function
31 /// returns ERROR_INSTALL_LANGUAGE_UNSUPPORTED (as HRESULT).
32 /// </summary>
33 msmErrorLanguageFailed = 2,
34
35 /// <summary>
36 /// The module cannot be merged because it excludes, or is excluded by, another module
37 /// in the database. Adds msmErrorExclusion to the Type property of the Error object.
38 /// The ModuleKeys property or DatabaseKeys property contains the primary keys of the
39 /// excluded module's row in the ModuleExclusion table. If an existing module excludes
40 /// the module being merged, the excluded module's ModuleSignature information is added
41 /// to ModuleKeys. If the module being merged excludes an existing module, DatabaseKeys
42 /// contains the excluded module's ModuleSignature information. All other properties
43 /// are empty (or -1).
44 /// </summary>
45 msmErrorExclusion = 3,
46
47 /// <summary>
48 /// Merge conflict during merge. The value of the Type property is set to
49 /// msmErrorTableMerge. The DatabaseTable property and DatabaseKeys property contain
50 /// the table name and primary keys of the conflicting row in the database. The
51 /// ModuleTable property and ModuleKeys property contain the table name and primary keys
52 /// of the conflicting row in the module. The ModuleTable and ModuleKeys entries may be
53 /// null if the row does not exist in the database. For example, if the conflict is in a
54 /// generated FeatureComponents table entry. On Windows Installer version 2.0, when
55 /// merging a configurable merge module, configuration may cause these properties to
56 /// refer to rows that do not exist in the module.
57 /// </summary>
58 msmErrorTableMerge = 4,
59
60 /// <summary>
61 /// There was a problem resequencing a sequence table to contain the necessary merged
62 /// actions. The Type property is set to msmErrorResequenceMerge. The DatabaseTable
63 /// and DatabaseKeys properties contain the sequence table name and primary keys
64 /// (action name) of the conflicting row. The ModuleTable and ModuleKeys properties
65 /// contain the sequence table name and primary key (action name) of the conflicting row.
66 /// On Windows Installer version 2.0, when merging a configurable merge module,
67 /// configuration may cause these properties to refer to rows that do not exist in the module.
68 /// </summary>
69 msmErrorResequenceMerge = 5,
70
71 /// <summary>
72 /// Not used.
73 /// </summary>
74 msmErrorFileCreate = 6,
75
76 /// <summary>
77 /// There was a problem creating a directory to extract a file to disk. The Path property
78 /// contains the directory that could not be created. All other properties are empty or -1.
79 /// Not available with Windows Installer version 1.0.
80 /// </summary>
81 msmErrorDirCreate = 7,
82
83 /// <summary>
84 /// A feature name is required to complete the merge, but no feature name was provided.
85 /// The Type property is set to msmErrorFeatureRequired. The DatabaseTable and DatabaseKeys
86 /// contain the table name and primary keys of the conflicting row. The ModuleTable and
87 /// ModuleKeys properties contain the table name and primary keys of the row cannot be merged.
88 /// On Windows Installer version 2.0, when merging a configurable merge module, configuration
89 /// may cause these properties to refer to rows that do not exist in the module.
90 /// If the failure is in a generated FeatureComponents table, the DatabaseTable and
91 /// DatabaseKeys properties are empty and the ModuleTable and ModuleKeys properties refer to
92 /// the row in the Component table causing the failure.
93 /// </summary>
94 msmErrorFeatureRequired = 8,
95
96 /// <summary>
97 /// Available with Window Installer version 2.0. Substitution of a Null value into a
98 /// non-nullable column. This enters msmErrorBadNullSubstitution in the Type property and
99 /// enters "ModuleSubstitution" and the keys from the ModuleSubstitution table for this row
100 /// into the ModuleTable property and ModuleKeys property. All other properties of the Error
101 /// object are set to an empty string or -1. This error causes the immediate failure of the
102 /// merge and the MergeEx function to return E_FAIL.
103 /// </summary>
104 msmErrorBadNullSubstitution = 9,
105
106 /// <summary>
107 /// Available with Window Installer version 2.0. Substitution of Text Format Type or Integer
108 /// Format Type into a Binary Type data column. This type of error returns
109 /// msmErrorBadSubstitutionType in the Type property and enters "ModuleSubstitution" and the
110 /// keys from the ModuleSubstitution table for this row into the ModuleTable property.
111 /// All other properties of the Error object are set to an empty string or -1. This error
112 /// causes the immediate failure of the merge and the MergeEx function to return E_FAIL.
113 /// </summary>
114 msmErrorBadSubstitutionType = 10,
115
116 /// <summary>
117 /// Available with Window Installer Version 2.0. A row in the ModuleSubstitution table
118 /// references a configuration item not defined in the ModuleConfiguration table.
119 /// This type of error returns msmErrorMissingConfigItem in the Type property and enters
120 /// "ModuleSubstitution" and the keys from the ModuleSubstitution table for this row into
121 /// the ModuleTable property. All other properties of the Error object are set to an empty
122 /// string or -1. This error causes the immediate failure of the merge and the MergeEx
123 /// function to return E_FAIL.
124 /// </summary>
125 msmErrorMissingConfigItem = 11,
126
127 /// <summary>
128 /// Available with Window Installer version 2.0. The authoring tool has returned a Null
129 /// value for an item marked with the msmConfigItemNonNullable attribute. An error of this
130 /// type returns msmErrorBadNullResponse in the Type property and enters "ModuleSubstitution"
131 /// and the keys from the ModuleSubstitution table for for the item into the ModuleTable property.
132 /// All other properties of the Error object are set to an empty string or -1. This error
133 /// causes the immediate failure of the merge and the MergeEx function to return E_FAIL.
134 /// </summary>
135 msmErrorBadNullResponse = 12,
136
137 /// <summary>
138 /// Available with Window Installer version 2.0. The authoring tool returned a failure code
139 /// (not S_OK or S_FALSE) when asked for data. An error of this type will return
140 /// msmErrorDataRequestFailed in the Type property and enters "ModuleSubstitution"
141 /// and the keys from the ModuleSubstitution table for the item into the ModuleTable property.
142 /// All other properties of the Error object are set to an empty string or -1. This error
143 /// causes the immediate failure of the merge and the MergeEx function to return E_FAIL.
144 /// </summary>
145 msmErrorDataRequestFailed = 13,
146
147 /// <summary>
148 /// Available with Windows Installer 2.0 and later versions. Indicates that an attempt was
149 /// made to merge a 64-bit module into a package that was not a 64-bit package. An error of
150 /// this type returns msmErrorPlatformMismatch in the Type property. All other properties of
151 /// the error object are set to an empty string or -1. This error causes the immediate failure
152 /// of the merge and causes the Merge function or MergeEx function to return E_FAIL.
153 /// </summary>
154 msmErrorPlatformMismatch = 14,
155 }
156
157 /// <summary>
158 /// IMsmMerge2 interface.
159 /// </summary>
160 [ComImport, Guid("351A72AB-21CB-47ab-B7AA-C4D7B02EA305")]
161 internal interface IMsmMerge2
162 {
163 /// <summary>
164 /// The OpenDatabase method of the Merge object opens a Windows Installer installation
165 /// database, located at a specified path, that is to be merged with a module.
166 /// </summary>
167 /// <param name="path">Path to the database being opened.</param>
168 void OpenDatabase(string path);
169
170 /// <summary>
171 /// The OpenModule method of the Merge object opens a Windows Installer merge module
172 /// in read-only mode. A module must be opened before it can be merged with an installation database.
173 /// </summary>
174 /// <param name="fileName">Fully qualified file name pointing to a merge module.</param>
175 /// <param name="language">A valid language identifier (LANGID).</param>
176 void OpenModule(string fileName, short language);
177
178 /// <summary>
179 /// The CloseDatabase method of the Merge object closes the currently open Windows Installer database.
180 /// </summary>
181 /// <param name="commit">true if changes should be saved, false otherwise.</param>
182 void CloseDatabase(bool commit);
183
184 /// <summary>
185 /// The CloseModule method of the Merge object closes the currently open Windows Installer merge module.
186 /// </summary>
187 void CloseModule();
188
189 /// <summary>
190 /// The OpenLog method of the Merge object opens a log file that receives progress and error messages.
191 /// If the log file already exists, the installer appends new messages. If the log file does not exist,
192 /// the installer creates a log file.
193 /// </summary>
194 /// <param name="fileName">Fully qualified filename pointing to a file to open or create.</param>
195 void OpenLog(string fileName);
196
197 /// <summary>
198 /// The CloseLog method of the Merge object closes the current log file.
199 /// </summary>
200 void CloseLog();
201
202 /// <summary>
203 /// The Log method of the Merge object writes a text string to the currently open log file.
204 /// </summary>
205 /// <param name="message">The text string to display.</param>
206 void Log(string message);
207
208 /// <summary>
209 /// Gets the errors from the last merge operation.
210 /// </summary>
211 /// <value>The errors from the last merge operation.</value>
212 IMsmErrors Errors
213 {
214 get;
215 }
216
217 /// <summary>
218 /// Gets a collection of Dependency objects that enumerates a set of unsatisfied dependencies for the current database.
219 /// </summary>
220 /// <value>A collection of Dependency objects that enumerates a set of unsatisfied dependencies for the current database.</value>
221 object Dependencies
222 {
223 get;
224 }
225
226 /// <summary>
227 /// The Merge method of the Merge object executes a merge of the current database and current
228 /// module. The merge attaches the components in the module to the feature identified by Feature.
229 /// The root of the module's directory tree is redirected to the location given by RedirectDir.
230 /// </summary>
231 /// <param name="feature">The name of a feature in the database.</param>
232 /// <param name="redirectDir">The key of an entry in the Directory table of the database.
233 /// This parameter may be NULL or an empty string.</param>
234 void Merge(string feature, string redirectDir);
235
236 /// <summary>
237 /// The Connect method of the Merge object connects a module to an additional feature.
238 /// The module must have already been merged into the database or will be merged into the database.
239 /// The feature must exist before calling this function.
240 /// </summary>
241 /// <param name="feature">The name of a feature already existing in the database.</param>
242 void Connect(string feature);
243
244 /// <summary>
245 /// The ExtractCAB method of the Merge object extracts the embedded .cab file from a module and
246 /// saves it as the specified file. The installer creates this file if it does not already exist
247 /// and overwritten if it does exist.
248 /// </summary>
249 /// <param name="fileName">The fully qualified destination file.</param>
250 void ExtractCAB(string fileName);
251
252 /// <summary>
253 /// The ExtractFiles method of the Merge object extracts the embedded .cab file from a module
254 /// and then writes those files to the destination directory.
255 /// </summary>
256 /// <param name="path">The fully qualified destination directory.</param>
257 void ExtractFiles(string path);
258
259 /// <summary>
260 /// The MergeEx method of the Merge object is equivalent to the Merge function, except that it
261 /// takes an extra argument. The Merge method executes a merge of the current database and
262 /// current module. The merge attaches the components in the module to the feature identified
263 /// by Feature. The root of the module's directory tree is redirected to the location given by RedirectDir.
264 /// </summary>
265 /// <param name="feature">The name of a feature in the database.</param>
266 /// <param name="redirectDir">The key of an entry in the Directory table of the database. This parameter may
267 /// be NULL or an empty string.</param>
268 /// <param name="configuration">The pConfiguration argument is an interface implemented by the client. The argument may
269 /// be NULL. The presence of this argument indicates that the client is capable of supporting the configuration
270 /// functionality, but does not obligate the client to provide configuration data for any specific configurable item.</param>
271 void MergeEx(string feature, string redirectDir, IMsmConfigureModule configuration);
272
273 /// <summary>
274 /// The ExtractFilesEx method of the Merge object extracts the embedded .cab file from a module and
275 /// then writes those files to the destination directory.
276 /// </summary>
277 /// <param name="path">The fully qualified destination directory.</param>
278 /// <param name="longFileNames">Set to specify using long file names for path segments and final file names.</param>
279 /// <param name="filePaths">This is a list of fully-qualified paths for the files that were successfully extracted.
280 /// The list is empty if no files can be extracted. This argument may be null. No list is provided if pFilePaths is null.</param>
281 void ExtractFilesEx(string path, bool longFileNames, ref IntPtr filePaths);
282
283 /// <summary>
284 /// Gets a collection ConfigurableItem objects, each of which represents a single row from the ModuleConfiguration table.
285 /// </summary>
286 /// <value>A collection ConfigurableItem objects, each of which represents a single row from the ModuleConfiguration table.</value>
287 /// <remarks>Semantically, each interface in the enumerator represents an item that can be configured by the module consumer.
288 /// The collection is a read-only collection and implements the standard read-only collection interfaces of Item(), Count() and _NewEnum().
289 /// The IEnumMsmConfigItems enumerator implements Next(), Skip(), Reset(), and Clone() with the standard semantics.</remarks>
290 object ConfigurableItems
291 {
292 get;
293 }
294
295 /// <summary>
296 /// The CreateSourceImage method of the Merge object allows the client to extract the files from a module to
297 /// a source image on disk after a merge, taking into account changes to the module that might have been made
298 /// during module configuration. The list of files to be extracted is taken from the file table of the module
299 /// during the merge process. The list of files consists of every file successfully copied from the file table
300 /// of the module to the target database. File table entries that were not copied due to primary key conflicts
301 /// with existing rows in the database are not a part of this list. At image creation time, the directory for
302 /// each of these files comes from the open (post-merge) database. The path specified in the Path parameter is
303 /// the root of the source image for the install. fLongFileNames determines whether or not long file names are
304 /// used for both path segments and final file names. The function fails if no database is open, no module is
305 /// open, or no merge has been performed.
306 /// </summary>
307 /// <param name="path">The path of the root of the source image for the install.</param>
308 /// <param name="longFileNames">Determines whether or not long file names are used for both path segments and final file names. </param>
309 /// <param name="filePaths">This is a list of fully-qualified paths for the files that were successfully extracted.
310 /// The list is empty if no files can be extracted. This argument may be null. No list is provided if pFilePaths is null.</param>
311 void CreateSourceImage(string path, bool longFileNames, ref IntPtr filePaths);
312
313 /// <summary>
314 /// The get_ModuleFiles function implements the ModuleFiles property of the GetFiles object. This function
315 /// returns the primary keys in the File table of the currently open module. The primary keys are returned
316 /// as a collection of strings. The module must be opened by a call to the OpenModule function before calling get_ModuleFiles.
317 /// </summary>
318 IMsmStrings ModuleFiles
319 {
320 get;
321 }
322 }
323
324 /// <summary>
325 /// Collection of merge errors.
326 /// </summary>
327 [ComImport, Guid("0ADDA82A-2C26-11D2-AD65-00A0C9AF11A6")]
328 internal interface IMsmErrors
329 {
330 /// <summary>
331 /// Gets the IMsmError at the specified index.
332 /// </summary>
333 /// <param name="index">The one-based index of the IMsmError to get.</param>
334 IMsmError this[int index]
335 {
336 get;
337 }
338
339 /// <summary>
340 /// Gets the count of IMsmErrors in this collection.
341 /// </summary>
342 /// <value>The count of IMsmErrors in this collection.</value>
343 int Count
344 {
345 get;
346 }
347 }
348
349 /// <summary>
350 /// A merge error.
351 /// </summary>
352 [ComImport, Guid("0ADDA828-2C26-11D2-AD65-00A0C9AF11A6")]
353 internal interface IMsmError
354 {
355 /// <summary>
356 /// Gets the type of merge error.
357 /// </summary>
358 /// <value>The type of merge error.</value>
359 MsmErrorType Type
360 {
361 get;
362 }
363
364 /// <summary>
365 /// Gets the path information from the merge error.
366 /// </summary>
367 /// <value>The path information from the merge error.</value>
368 string Path
369 {
370 get;
371 }
372
373 /// <summary>
374 /// Gets the language information from the merge error.
375 /// </summary>
376 /// <value>The language information from the merge error.</value>
377 short Language
378 {
379 get;
380 }
381
382 /// <summary>
383 /// Gets the database table from the merge error.
384 /// </summary>
385 /// <value>The database table from the merge error.</value>
386 string DatabaseTable
387 {
388 get;
389 }
390
391 /// <summary>
392 /// Gets the collection of database keys from the merge error.
393 /// </summary>
394 /// <value>The collection of database keys from the merge error.</value>
395 IMsmStrings DatabaseKeys
396 {
397 get;
398 }
399
400 /// <summary>
401 /// Gets the module table from the merge error.
402 /// </summary>
403 /// <value>The module table from the merge error.</value>
404 string ModuleTable
405 {
406 get;
407 }
408
409 /// <summary>
410 /// Gets the collection of module keys from the merge error.
411 /// </summary>
412 /// <value>The collection of module keys from the merge error.</value>
413 IMsmStrings ModuleKeys
414 {
415 get;
416 }
417 }
418
419 /// <summary>
420 /// A collection of strings.
421 /// </summary>
422 [ComImport, Guid("0ADDA827-2C26-11D2-AD65-00A0C9AF11A6")]
423 internal interface IMsmStrings
424 {
425 /// <summary>
426 /// Gets the string at the specified index.
427 /// </summary>
428 /// <param name="index">The one-based index of the string to get.</param>
429 string this[int index]
430 {
431 get;
432 }
433
434 /// <summary>
435 /// Gets the count of strings in this collection.
436 /// </summary>
437 /// <value>The count of strings in this collection.</value>
438 int Count
439 {
440 get;
441 }
442 }
443
444 /// <summary>
445 /// Callback for configurable merge modules.
446 /// </summary>
447 [ComImport, Guid("AC013209-18A7-4851-8A21-2353443D70A0"), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
448 internal interface IMsmConfigureModule
449 {
450 /// <summary>
451 /// Callback to retrieve text data for configurable merge modules.
452 /// </summary>
453 /// <param name="name">Name of the data to be retrieved.</param>
454 /// <param name="configData">The data corresponding to the name.</param>
455 /// <returns>The error code (HRESULT).</returns>
456 [PreserveSig]
457 int ProvideTextData([In, MarshalAs(UnmanagedType.BStr)] string name, [MarshalAs(UnmanagedType.BStr)] out string configData);
458
459 /// <summary>
460 /// Callback to retrieve integer data for configurable merge modules.
461 /// </summary>
462 /// <param name="name">Name of the data to be retrieved.</param>
463 /// <param name="configData">The data corresponding to the name.</param>
464 /// <returns>The error code (HRESULT).</returns>
465 [PreserveSig]
466 int ProvideIntegerData([In, MarshalAs(UnmanagedType.BStr)] string name, out int configData);
467 }
468
469 /// <summary>
470 /// Merge merge modules into an MSI file.
471 /// </summary>
472 [ComImport, Guid("F94985D5-29F9-4743-9805-99BC3F35B678")]
473 internal class MsmMerge2
474 {
475 }
476
477 /// <summary>
478 /// Defines the standard COM IClassFactory interface.
479 /// </summary>
480 [ComImport, Guid("00000001-0000-0000-C000-000000000046")]
481 [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
482 internal interface IClassFactory
483 {
484 [return:MarshalAs(UnmanagedType.IUnknown)]
485 object CreateInstance(IntPtr unkOuter, [MarshalAs(UnmanagedType.LPStruct)] Guid iid);
486 }
487
488 /// <summary>
489 /// Contains native methods for merge operations.
490 /// </summary>
491 internal class NativeMethods
492 {
493 [DllImport("mergemod.dll", EntryPoint="DllGetClassObject", PreserveSig=false)]
494 [return: MarshalAs(UnmanagedType.IUnknown)]
495 private static extern object MergeModGetClassObject([MarshalAs(UnmanagedType.LPStruct)] Guid clsid, [MarshalAs(UnmanagedType.LPStruct)] Guid iid);
496
497 /// <summary>
498 /// Load the merge object directly from a local mergemod.dll without going through COM registration.
499 /// </summary>
500 /// <returns>Merge interface.</returns>
501 internal static IMsmMerge2 GetMsmMerge()
502 {
503 IClassFactory classFactory = (IClassFactory) MergeModGetClassObject(typeof(MsmMerge2).GUID, typeof(IClassFactory).GUID);
504 return (IMsmMerge2) classFactory.CreateInstance(IntPtr.Zero, typeof(IMsmMerge2).GUID);
505 }
506 }
507}
508#endif \ No newline at end of file
diff --git a/src/WixToolset.Core/Msi/Database.cs b/src/WixToolset.Core/Msi/Database.cs
new file mode 100644
index 00000000..7c3a97bb
--- /dev/null
+++ b/src/WixToolset.Core/Msi/Database.cs
@@ -0,0 +1,303 @@
1// Copyright (c) .NET Foundation and 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.Msi
4{
5 using System;
6 using System.ComponentModel;
7 using System.Globalization;
8 using System.IO;
9 using System.Text;
10 using System.Threading;
11 using WixToolset.Data;
12 using WixToolset.Core.Native;
13
14 /// <summary>
15 /// Wrapper class for managing MSI API database handles.
16 /// </summary>
17 internal sealed class Database : MsiHandle
18 {
19 private const int STG_E_LOCKVIOLATION = unchecked((int)0x80030021);
20
21 /// <summary>
22 /// Constructor that opens an MSI database.
23 /// </summary>
24 /// <param name="path">Path to the database to be opened.</param>
25 /// <param name="type">Persist mode to use when opening the database.</param>
26 public Database(string path, OpenDatabase type)
27 {
28 uint handle = 0;
29 int error = MsiInterop.MsiOpenDatabase(path, new IntPtr((int)type), out handle);
30 if (0 != error)
31 {
32 throw new MsiException(error);
33 }
34 this.Handle = handle;
35 }
36
37 public void ApplyTransform(string transformFile)
38 {
39 // get the curret validation bits
40 TransformErrorConditions conditions = TransformErrorConditions.None;
41 using (SummaryInformation summaryInfo = new SummaryInformation(transformFile))
42 {
43 string value = summaryInfo.GetProperty((int)SummaryInformation.Transform.ValidationFlags);
44 try
45 {
46 int validationFlags = Int32.Parse(value, CultureInfo.InvariantCulture);
47 conditions = (TransformErrorConditions)(validationFlags & 0xffff);
48 }
49 catch (FormatException)
50 {
51 // fallback to default of None
52 }
53 }
54
55 this.ApplyTransform(transformFile, conditions);
56 }
57
58 /// <summary>
59 /// Applies a transform to this database.
60 /// </summary>
61 /// <param name="transformFile">Path to the transform file being applied.</param>
62 /// <param name="errorConditions">Specifies the error conditions that are to be suppressed.</param>
63 public void ApplyTransform(string transformFile, TransformErrorConditions errorConditions)
64 {
65 int error = MsiInterop.MsiDatabaseApplyTransform(this.Handle, transformFile, errorConditions);
66 if (0 != error)
67 {
68 throw new MsiException(error);
69 }
70 }
71
72 /// <summary>
73 /// Commits changes made to the database.
74 /// </summary>
75 public void Commit()
76 {
77 // Retry this call 3 times to deal with an MSI internal locking problem.
78 const int retryWait = 300;
79 const int retryLimit = 3;
80 int error = 0;
81
82 for (int i = 1; i <= retryLimit; ++i)
83 {
84 error = MsiInterop.MsiDatabaseCommit(this.Handle);
85
86 if (0 == error)
87 {
88 return;
89 }
90 else
91 {
92 MsiException exception = new MsiException(error);
93
94 // We need to see if the error code is contained in any of the strings in ErrorInfo.
95 // Join the array together and search for the error code to cover the string array.
96 if (!String.Join(", ", exception.ErrorInfo).Contains(STG_E_LOCKVIOLATION.ToString()))
97 {
98 break;
99 }
100
101 Console.Error.WriteLine(String.Format("Failed to create the database. Info: {0}. Retrying ({1} of {2})", String.Join(", ", exception.ErrorInfo), i, retryLimit));
102 Thread.Sleep(retryWait);
103 }
104 }
105
106 throw new MsiException(error);
107 }
108
109 /// <summary>
110 /// Creates and populates the summary information stream of an existing transform file.
111 /// </summary>
112 /// <param name="referenceDatabase">Required database that does not include the changes.</param>
113 /// <param name="transformFile">The name of the generated transform file.</param>
114 /// <param name="errorConditions">Required error conditions that should be suppressed when the transform is applied.</param>
115 /// <param name="validations">Required when the transform is applied to a database;
116 /// shows which properties should be validated to verify that this transform can be applied to the database.</param>
117 public void CreateTransformSummaryInfo(Database referenceDatabase, string transformFile, TransformErrorConditions errorConditions, TransformValidations validations)
118 {
119 int error = MsiInterop.MsiCreateTransformSummaryInfo(this.Handle, referenceDatabase.Handle, transformFile, errorConditions, validations);
120 if (0 != error)
121 {
122 throw new MsiException(error);
123 }
124 }
125
126 /// <summary>
127 /// Imports an installer text archive table (idt file) into an open database.
128 /// </summary>
129 /// <param name="idtPath">Specifies the path to the file to import.</param>
130 /// <exception cref="WixInvalidIdtException">Attempted to import an IDT file with an invalid format or unsupported data.</exception>
131 /// <exception cref="MsiException">Another error occured while importing the IDT file.</exception>
132 public void Import(string idtPath)
133 {
134 string folderPath = Path.GetDirectoryName(idtPath);
135 string fileName = Path.GetFileName(idtPath);
136
137 int error = MsiInterop.MsiDatabaseImport(this.Handle, folderPath, fileName);
138 if (1627 == error) // ERROR_FUNCTION_FAILED
139 {
140 throw new WixInvalidIdtException(idtPath);
141 }
142 else if (0 != error)
143 {
144 throw new MsiException(error);
145 }
146 }
147
148 /// <summary>
149 /// Exports an installer table from an open database to a text archive file (idt file).
150 /// </summary>
151 /// <param name="tableName">Specifies the name of the table to export.</param>
152 /// <param name="folderPath">Specifies the name of the folder that contains archive files. If null or empty string, uses current directory.</param>
153 /// <param name="fileName">Specifies the name of the exported table archive file.</param>
154 public void Export(string tableName, string folderPath, string fileName)
155 {
156 if (null == folderPath || 0 == folderPath.Length)
157 {
158 folderPath = System.Environment.CurrentDirectory;
159 }
160
161 int error = MsiInterop.MsiDatabaseExport(this.Handle, tableName, folderPath, fileName);
162 if (0 != error)
163 {
164 throw new MsiException(error);
165 }
166 }
167
168 /// <summary>
169 /// Creates a transform that, when applied to the reference database, results in this database.
170 /// </summary>
171 /// <param name="referenceDatabase">Required database that does not include the changes.</param>
172 /// <param name="transformFile">The name of the generated transform file. This is optional.</param>
173 /// <returns>true if a transform is generated; false if a transform is not generated because
174 /// there are no differences between the two databases.</returns>
175 public bool GenerateTransform(Database referenceDatabase, string transformFile)
176 {
177 int error = MsiInterop.MsiDatabaseGenerateTransform(this.Handle, referenceDatabase.Handle, transformFile, 0, 0);
178 if (0 != error && 0xE8 != error) // ERROR_NO_DATA(0xE8) means no differences were found
179 {
180 throw new MsiException(error);
181 }
182
183 return (0xE8 != error);
184 }
185
186 /// <summary>
187 /// Merges two databases together.
188 /// </summary>
189 /// <param name="mergeDatabase">The database to merge into the base database.</param>
190 /// <param name="tableName">The name of the table to receive merge conflict information.</param>
191 public void Merge(Database mergeDatabase, string tableName)
192 {
193 int error = MsiInterop.MsiDatabaseMerge(this.Handle, mergeDatabase.Handle, tableName);
194 if (0 != error)
195 {
196 throw new MsiException(error);
197 }
198 }
199
200 /// <summary>
201 /// Prepares a database query and creates a <see cref="View">View</see> object.
202 /// </summary>
203 /// <param name="query">Specifies a SQL query string for querying the database.</param>
204 /// <returns>A view object is returned if the query was successful.</returns>
205 public View OpenView(string query)
206 {
207 return new View(this, query);
208 }
209
210 /// <summary>
211 /// Prepares and executes a database query and creates a <see cref="View">View</see> object.
212 /// </summary>
213 /// <param name="query">Specifies a SQL query string for querying the database.</param>
214 /// <returns>A view object is returned if the query was successful.</returns>
215 public View OpenExecuteView(string query)
216 {
217 View view = new View(this, query);
218
219 view.Execute();
220 return view;
221 }
222
223 /// <summary>
224 /// Verifies the existence or absence of a table.
225 /// </summary>
226 /// <param name="tableName">Table name to to verify the existence of.</param>
227 /// <returns>Returns true if the table exists, false if it does not.</returns>
228 public bool TableExists(string tableName)
229 {
230 int result = MsiInterop.MsiDatabaseIsTablePersistent(this.Handle, tableName);
231 return MsiInterop.MSICONDITIONTRUE == result;
232 }
233
234 /// <summary>
235 /// Returns a <see cref="Record">Record</see> containing the names of all the primary
236 /// key columns for a specified table.
237 /// </summary>
238 /// <param name="tableName">Specifies the name of the table from which to obtain
239 /// primary key names.</param>
240 /// <returns>Returns a <see cref="Record">Record</see> containing the names of all the
241 /// primary key columns for a specified table.</returns>
242 public Record PrimaryKeys(string tableName)
243 {
244 uint recordHandle;
245 int error = MsiInterop.MsiDatabaseGetPrimaryKeys(this.Handle, tableName, out recordHandle);
246 if (0 != error)
247 {
248 throw new MsiException(error);
249 }
250
251 return new Record(recordHandle);
252 }
253
254 /// <summary>
255 /// Imports a table into the database.
256 /// </summary>
257 /// <param name="codepage">Codepage of the database to import table to.</param>
258 /// <param name="table">Table to import into database.</param>
259 /// <param name="baseDirectory">The base directory where intermediate files are created.</param>
260 /// <param name="keepAddedColumns">Whether to keep columns added in a transform.</param>
261 public void ImportTable(int codepage, Table table, string baseDirectory, bool keepAddedColumns)
262 {
263 // write out the table to an IDT file
264 string idtPath = Path.Combine(baseDirectory, String.Concat(table.Name, ".idt"));
265 Encoding encoding;
266
267 // If UTF8 encoding, use the UTF8-specific constructor to avoid writing
268 // the byte order mark at the beginning of the file
269 if (Encoding.UTF8.CodePage == codepage)
270 {
271 encoding = new UTF8Encoding(false, true);
272 }
273 else
274 {
275 if (0 == codepage)
276 {
277 codepage = Encoding.ASCII.CodePage;
278 }
279
280 encoding = Encoding.GetEncoding(codepage, new EncoderExceptionFallback(), new DecoderExceptionFallback());
281 }
282
283 using (StreamWriter idtWriter = new StreamWriter(idtPath, false, encoding))
284 {
285 table.ToIdtDefinition(idtWriter, keepAddedColumns);
286 }
287
288 // try to import the table into the MSI
289 try
290 {
291 this.Import(idtPath);
292 }
293 catch (WixInvalidIdtException)
294 {
295 table.ValidateRows();
296
297 // If ValidateRows finds anything it doesn't like, it throws. Otherwise, we'll
298 // throw WixInvalidIdtException here which is caught in light and turns off tidy.
299 throw new WixInvalidIdtException(idtPath, table.Name);
300 }
301 }
302 }
303}
diff --git a/src/WixToolset.Core/Msi/Installer.cs b/src/WixToolset.Core/Msi/Installer.cs
new file mode 100644
index 00000000..3beb26f4
--- /dev/null
+++ b/src/WixToolset.Core/Msi/Installer.cs
@@ -0,0 +1,484 @@
1// Copyright (c) .NET Foundation and 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.Msi
4{
5 using System;
6 using System.Diagnostics;
7 using System.Text;
8 using WixToolset.Core.Native;
9
10 /// <summary>
11 /// Windows Installer message types.
12 /// </summary>
13 [Flags]
14 internal enum InstallMessage
15 {
16 /// <summary>
17 /// Premature termination, possibly fatal out of memory.
18 /// </summary>
19 FatalExit = 0x00000000,
20
21 /// <summary>
22 /// Formatted error message, [1] is message number in Error table.
23 /// </summary>
24 Error = 0x01000000,
25
26 /// <summary>
27 /// Formatted warning message, [1] is message number in Error table.
28 /// </summary>
29 Warning = 0x02000000,
30
31 /// <summary>
32 /// User request message, [1] is message number in Error table.
33 /// </summary>
34 User = 0x03000000,
35
36 /// <summary>
37 /// Informative message for log, not to be displayed.
38 /// </summary>
39 Info = 0x04000000,
40
41 /// <summary>
42 /// List of files in use that need to be replaced.
43 /// </summary>
44 FilesInUse = 0x05000000,
45
46 /// <summary>
47 /// Request to determine a valid source location.
48 /// </summary>
49 ResolveSource = 0x06000000,
50
51 /// <summary>
52 /// Insufficient disk space message.
53 /// </summary>
54 OutOfDiskSpace = 0x07000000,
55
56 /// <summary>
57 /// Progress: start of action, [1] action name, [2] description, [3] template for ACTIONDATA messages.
58 /// </summary>
59 ActionStart = 0x08000000,
60
61 /// <summary>
62 /// Action data. Record fields correspond to the template of ACTIONSTART message.
63 /// </summary>
64 ActionData = 0x09000000,
65
66 /// <summary>
67 /// Progress bar information. See the description of record fields below.
68 /// </summary>
69 Progress = 0x0A000000,
70
71 /// <summary>
72 /// To enable the Cancel button set [1] to 2 and [2] to 1. To disable the Cancel button set [1] to 2 and [2] to 0.
73 /// </summary>
74 CommonData = 0x0B000000,
75
76 /// <summary>
77 /// Sent prior to UI initialization, no string data.
78 /// </summary>
79 Initilize = 0x0C000000,
80
81 /// <summary>
82 /// Sent after UI termination, no string data.
83 /// </summary>
84 Terminate = 0x0D000000,
85
86 /// <summary>
87 /// Sent prior to display or authored dialog or wizard.
88 /// </summary>
89 ShowDialog = 0x0E000000
90 }
91
92 /// <summary>
93 /// Windows Installer log modes.
94 /// </summary>
95 [Flags]
96 internal enum InstallLogModes
97 {
98 /// <summary>
99 /// Premature termination of installation.
100 /// </summary>
101 FatalExit = (1 << ((int)InstallMessage.FatalExit >> 24)),
102
103 /// <summary>
104 /// The error messages are logged.
105 /// </summary>
106 Error = (1 << ((int)InstallMessage.Error >> 24)),
107
108 /// <summary>
109 /// The warning messages are logged.
110 /// </summary>
111 Warning = (1 << ((int)InstallMessage.Warning >> 24)),
112
113 /// <summary>
114 /// The user requests are logged.
115 /// </summary>
116 User = (1 << ((int)InstallMessage.User >> 24)),
117
118 /// <summary>
119 /// The status messages that are not displayed are logged.
120 /// </summary>
121 Info = (1 << ((int)InstallMessage.Info >> 24)),
122
123 /// <summary>
124 /// Request to determine a valid source location.
125 /// </summary>
126 ResolveSource = (1 << ((int)InstallMessage.ResolveSource >> 24)),
127
128 /// <summary>
129 /// The was insufficient disk space.
130 /// </summary>
131 OutOfDiskSpace = (1 << ((int)InstallMessage.OutOfDiskSpace >> 24)),
132
133 /// <summary>
134 /// The start of new installation actions are logged.
135 /// </summary>
136 ActionStart = (1 << ((int)InstallMessage.ActionStart >> 24)),
137
138 /// <summary>
139 /// The data record with the installation action is logged.
140 /// </summary>
141 ActionData = (1 << ((int)InstallMessage.ActionData >> 24)),
142
143 /// <summary>
144 /// The parameters for user-interface initialization are logged.
145 /// </summary>
146 CommonData = (1 << ((int)InstallMessage.CommonData >> 24)),
147
148 /// <summary>
149 /// Logs the property values at termination.
150 /// </summary>
151 PropertyDump = (1 << ((int)InstallMessage.Progress >> 24)),
152
153 /// <summary>
154 /// Sends large amounts of information to a log file not generally useful to users.
155 /// May be used for technical support.
156 /// </summary>
157 Verbose = (1 << ((int)InstallMessage.Initilize >> 24)),
158
159 /// <summary>
160 /// Sends extra debugging information, such as handle creation information, to the log file.
161 /// </summary>
162 ExtraDebug = (1 << ((int)InstallMessage.Terminate >> 24)),
163
164 /// <summary>
165 /// Progress bar information. This message includes information on units so far and total number of units.
166 /// See MsiProcessMessage for an explanation of the message format.
167 /// This message is only sent to an external user interface and is not logged.
168 /// </summary>
169 Progress = (1 << ((int)InstallMessage.Progress >> 24)),
170
171 /// <summary>
172 /// If this is not a quiet installation, then the basic UI has been initialized.
173 /// If this is a full UI installation, the full UI is not yet initialized.
174 /// This message is only sent to an external user interface and is not logged.
175 /// </summary>
176 Initialize = (1 << ((int)InstallMessage.Initilize >> 24)),
177
178 /// <summary>
179 /// If a full UI is being used, the full UI has ended.
180 /// If this is not a quiet installation, the basic UI has not yet ended.
181 /// This message is only sent to an external user interface and is not logged.
182 /// </summary>
183 Terminate = (1 << ((int)InstallMessage.Terminate >> 24)),
184
185 /// <summary>
186 /// Sent prior to display of the full UI dialog.
187 /// This message is only sent to an external user interface and is not logged.
188 /// </summary>
189 ShowDialog = (1 << ((int)InstallMessage.ShowDialog >> 24)),
190
191 /// <summary>
192 /// Files in use information. When this message is received, a FilesInUse Dialog should be displayed.
193 /// </summary>
194 FilesInUse = (1 << ((int)InstallMessage.FilesInUse >> 24))
195 }
196
197 /// <summary>
198 /// Windows Installer UI levels.
199 /// </summary>
200 [Flags]
201 internal enum InstallUILevels
202 {
203 /// <summary>
204 /// No change in the UI level. However, if phWnd is not Null, the parent window can change.
205 /// </summary>
206 NoChange = 0,
207
208 /// <summary>
209 /// The installer chooses an appropriate user interface level.
210 /// </summary>
211 Default = 1,
212
213 /// <summary>
214 /// Completely silent installation.
215 /// </summary>
216 None = 2,
217
218 /// <summary>
219 /// Simple progress and error handling.
220 /// </summary>
221 Basic = 3,
222
223 /// <summary>
224 /// Authored user interface with wizard dialog boxes suppressed.
225 /// </summary>
226 Reduced = 4,
227
228 /// <summary>
229 /// Authored user interface with wizards, progress, and errors.
230 /// </summary>
231 Full = 5,
232
233 /// <summary>
234 /// If combined with the Basic value, the installer shows simple progress dialog boxes but
235 /// does not display a Cancel button on the dialog. This prevents users from canceling the install.
236 /// Available with Windows Installer version 2.0.
237 /// </summary>
238 HideCancel = 0x20,
239
240 /// <summary>
241 /// If combined with the Basic value, the installer shows simple progress
242 /// dialog boxes but does not display any modal dialog boxes or error dialog boxes.
243 /// </summary>
244 ProgressOnly = 0x40,
245
246 /// <summary>
247 /// If combined with any above value, the installer displays a modal dialog
248 /// box at the end of a successful installation or if there has been an error.
249 /// No dialog box is displayed if the user cancels.
250 /// </summary>
251 EndDialog = 0x80,
252
253 /// <summary>
254 /// If this value is combined with the None value, the installer displays only the dialog
255 /// boxes used for source resolution. No other dialog boxes are shown. This value has no
256 /// effect if the UI level is not INSTALLUILEVEL_NONE. It is used with an external user
257 /// interface designed to handle all of the UI except for source resolution. In this case,
258 /// the installer handles source resolution. This value is only available with Windows Installer 2.0 and later.
259 /// </summary>
260 SourceResOnly = 0x100
261 }
262
263 /// <summary>
264 /// Represents the Windows Installer, provides wrappers to
265 /// create the top-level objects and access their methods.
266 /// </summary>
267 internal sealed class Installer
268 {
269 /// <summary>
270 /// Protect the constructor.
271 /// </summary>
272 private Installer()
273 {
274 }
275
276 /// <summary>
277 /// Takes the path to a file and returns a 128-bit hash of that file.
278 /// </summary>
279 /// <param name="filePath">Path to file that is to be hashed.</param>
280 /// <param name="options">The value in this column must be 0. This parameter is reserved for future use.</param>
281 /// <param name="hash">Int array that receives the returned file hash information.</param>
282 internal static void GetFileHash(string filePath, int options, out int[] hash)
283 {
284 MsiInterop.MSIFILEHASHINFO hashInterop = new MsiInterop.MSIFILEHASHINFO();
285 hashInterop.FileHashInfoSize = 20;
286
287 int error = MsiInterop.MsiGetFileHash(filePath, Convert.ToUInt32(options), hashInterop);
288 if (0 != error)
289 {
290 throw new MsiException(error);
291 }
292
293 Debug.Assert(20 == hashInterop.FileHashInfoSize);
294
295 hash = new int[4];
296 hash[0] = hashInterop.Data0;
297 hash[1] = hashInterop.Data1;
298 hash[2] = hashInterop.Data2;
299 hash[3] = hashInterop.Data3;
300 }
301
302 /// <summary>
303 /// Returns the version string and language string in the format that the installer
304 /// expects to find them in the database. If you just want version information, set
305 /// lpLangBuf and pcchLangBuf to zero. If you just want language information, set
306 /// lpVersionBuf and pcchVersionBuf to zero.
307 /// </summary>
308 /// <param name="filePath">Specifies the path to the file.</param>
309 /// <param name="version">Returns the file version. Set to 0 for language information only.</param>
310 /// <param name="language">Returns the file language. Set to 0 for version information only.</param>
311 internal static void GetFileVersion(string filePath, out string version, out string language)
312 {
313 int versionLength = 20;
314 int languageLength = 20;
315 StringBuilder versionBuffer = new StringBuilder(versionLength);
316 StringBuilder languageBuffer = new StringBuilder(languageLength);
317
318 int error = MsiInterop.MsiGetFileVersion(filePath, versionBuffer, ref versionLength, languageBuffer, ref languageLength);
319 if (234 == error)
320 {
321 versionBuffer.EnsureCapacity(++versionLength);
322 languageBuffer.EnsureCapacity(++languageLength);
323 error = MsiInterop.MsiGetFileVersion(filePath, versionBuffer, ref versionLength, languageBuffer, ref languageLength);
324 }
325 else if (1006 == error)
326 {
327 // file has no version or language, so no error
328 error = 0;
329 }
330
331 if (0 != error)
332 {
333 throw new MsiException(error);
334 }
335
336 version = versionBuffer.ToString();
337 language = languageBuffer.ToString();
338 }
339
340 /// <summary>
341 /// Enables an external user-interface handler.
342 /// </summary>
343 /// <param name="installUIHandler">Specifies a callback function.</param>
344 /// <param name="messageFilter">Specifies which messages to handle using the external message handler.</param>
345 /// <param name="context">Pointer to an application context that is passed to the callback function.</param>
346 /// <returns>The return value is the previously set external handler, or null if there was no previously set handler.</returns>
347 internal static InstallUIHandler SetExternalUI(InstallUIHandler installUIHandler, int messageFilter, IntPtr context)
348 {
349 return MsiInterop.MsiSetExternalUI(installUIHandler, messageFilter, context);
350 }
351
352 /// <summary>
353 /// Enables the installer's internal user interface.
354 /// </summary>
355 /// <param name="uiLevel">Specifies the level of complexity of the user interface.</param>
356 /// <param name="hwnd">Pointer to a window. This window becomes the owner of any user interface created.</param>
357 /// <returns>The previous user interface level is returned. If an invalid dwUILevel is passed, then INSTALLUILEVEL_NOCHANGE is returned.</returns>
358 internal static int SetInternalUI(int uiLevel, ref IntPtr hwnd)
359 {
360 return MsiInterop.MsiSetInternalUI(uiLevel, ref hwnd);
361 }
362
363 /// <summary>
364 /// Get the source/target and short/long file names from an MSI Filename column.
365 /// </summary>
366 /// <param name="value">The Filename value.</param>
367 /// <returns>An array of strings of length 4. The contents are: short target, long target, short source, and long source.</returns>
368 /// <remarks>
369 /// If any particular file name part is not parsed, its set to null in the appropriate location of the returned array of strings.
370 /// However, the returned array will always be of length 4.
371 /// </remarks>
372 internal static string[] GetNames(string value)
373 {
374 string[] names = new string[4];
375 int targetSeparator = value.IndexOf(":", StringComparison.Ordinal);
376
377 // split source and target
378 string sourceName = null;
379 string targetName = value;
380 if (0 <= targetSeparator)
381 {
382 sourceName = value.Substring(targetSeparator + 1);
383 targetName = value.Substring(0, targetSeparator);
384 }
385
386 // split the source short and long names
387 string sourceLongName = null;
388 if (null != sourceName)
389 {
390 int sourceLongNameSeparator = sourceName.IndexOf("|", StringComparison.Ordinal);
391 if (0 <= sourceLongNameSeparator)
392 {
393 sourceLongName = sourceName.Substring(sourceLongNameSeparator + 1);
394 sourceName = sourceName.Substring(0, sourceLongNameSeparator);
395 }
396 }
397
398 // split the target short and long names
399 int targetLongNameSeparator = targetName.IndexOf("|", StringComparison.Ordinal);
400 string targetLongName = null;
401 if (0 <= targetLongNameSeparator)
402 {
403 targetLongName = targetName.Substring(targetLongNameSeparator + 1);
404 targetName = targetName.Substring(0, targetLongNameSeparator);
405 }
406
407 // remove the long source name when its identical to the long source name
408 if (null != sourceName && sourceName == sourceLongName)
409 {
410 sourceLongName = null;
411 }
412
413 // remove the long target name when its identical to the long target name
414 if (null != targetName && targetName == targetLongName)
415 {
416 targetLongName = null;
417 }
418
419 // remove the source names when they are identical to the target names
420 if (sourceName == targetName && sourceLongName == targetLongName)
421 {
422 sourceName = null;
423 sourceLongName = null;
424 }
425
426 // target name(s)
427 if ("." != targetName)
428 {
429 names[0] = targetName;
430 }
431
432 if (null != targetLongName && "." != targetLongName)
433 {
434 names[1] = targetLongName;
435 }
436
437 // source name(s)
438 if (null != sourceName)
439 {
440 names[2] = sourceName;
441 }
442
443 if (null != sourceLongName && "." != sourceLongName)
444 {
445 names[3] = sourceLongName;
446 }
447
448 return names;
449 }
450
451 /// <summary>
452 /// Get a source/target and short/long file name from an MSI Filename column.
453 /// </summary>
454 /// <param name="value">The Filename value.</param>
455 /// <param name="source">true to get a source name; false to get a target name</param>
456 /// <param name="longName">true to get a long name; false to get a short name</param>
457 /// <returns>The name.</returns>
458 internal static string GetName(string value, bool source, bool longName)
459 {
460 string[] names = GetNames(value);
461
462 if (source)
463 {
464 if (longName && null != names[3])
465 {
466 return names[3];
467 }
468 else if (null != names[2])
469 {
470 return names[2];
471 }
472 }
473
474 if (longName && null != names[1])
475 {
476 return names[1];
477 }
478 else
479 {
480 return names[0];
481 }
482 }
483 }
484}
diff --git a/src/WixToolset.Core/Msi/Interop/MsiInterop.cs b/src/WixToolset.Core/Msi/Interop/MsiInterop.cs
new file mode 100644
index 00000000..054289ee
--- /dev/null
+++ b/src/WixToolset.Core/Msi/Interop/MsiInterop.cs
@@ -0,0 +1,697 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2#if false
3namespace WixToolset.Msi.Interop
4{
5 using System;
6 using System.Text;
7 using System.Runtime.InteropServices;
8 using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME;
9
10 /// <summary>
11 /// A callback function that the installer calls for progress notification and error messages.
12 /// </summary>
13 /// <param name="context">Pointer to an application context.
14 /// This parameter can be used for error checking.</param>
15 /// <param name="messageType">Specifies a combination of one message box style,
16 /// one message box icon type, one default button, and one installation message type.</param>
17 /// <param name="message">Specifies the message text.</param>
18 /// <returns>-1 for an error, 0 if no action was taken, 1 if OK, 3 to abort.</returns>
19 internal delegate int InstallUIHandler(IntPtr context, uint messageType, [MarshalAs(UnmanagedType.LPWStr)] string message);
20
21 /// <summary>
22 /// Class exposing static functions and structs from MSI API.
23 /// </summary>
24 internal sealed class MsiInterop
25 {
26 // Patching constants
27 internal const int MsiMaxStreamNameLength = 62; // http://msdn2.microsoft.com/library/aa370551.aspx
28
29 // Component.Attributes
30 internal const int MsidbComponentAttributesLocalOnly = 0;
31 internal const int MsidbComponentAttributesSourceOnly = 1;
32 internal const int MsidbComponentAttributesOptional = 2;
33 internal const int MsidbComponentAttributesRegistryKeyPath = 4;
34 internal const int MsidbComponentAttributesSharedDllRefCount = 8;
35 internal const int MsidbComponentAttributesPermanent = 16;
36 internal const int MsidbComponentAttributesODBCDataSource = 32;
37 internal const int MsidbComponentAttributesTransitive = 64;
38 internal const int MsidbComponentAttributesNeverOverwrite = 128;
39 internal const int MsidbComponentAttributes64bit = 256;
40 internal const int MsidbComponentAttributesDisableRegistryReflection = 512;
41 internal const int MsidbComponentAttributesUninstallOnSupersedence = 1024;
42 internal const int MsidbComponentAttributesShared = 2048;
43
44 // BBControl.Attributes & Control.Attributes
45 internal const int MsidbControlAttributesVisible = 0x00000001;
46 internal const int MsidbControlAttributesEnabled = 0x00000002;
47 internal const int MsidbControlAttributesSunken = 0x00000004;
48 internal const int MsidbControlAttributesIndirect = 0x00000008;
49 internal const int MsidbControlAttributesInteger = 0x00000010;
50 internal const int MsidbControlAttributesRTLRO = 0x00000020;
51 internal const int MsidbControlAttributesRightAligned = 0x00000040;
52 internal const int MsidbControlAttributesLeftScroll = 0x00000080;
53 internal const int MsidbControlAttributesBiDi = MsidbControlAttributesRTLRO | MsidbControlAttributesRightAligned | MsidbControlAttributesLeftScroll;
54
55 // Text controls
56 internal const int MsidbControlAttributesTransparent = 0x00010000;
57 internal const int MsidbControlAttributesNoPrefix = 0x00020000;
58 internal const int MsidbControlAttributesNoWrap = 0x00040000;
59 internal const int MsidbControlAttributesFormatSize = 0x00080000;
60 internal const int MsidbControlAttributesUsersLanguage = 0x00100000;
61
62 // Edit controls
63 internal const int MsidbControlAttributesMultiline = 0x00010000;
64 internal const int MsidbControlAttributesPasswordInput = 0x00200000;
65
66 // ProgressBar controls
67 internal const int MsidbControlAttributesProgress95 = 0x00010000;
68
69 // VolumeSelectCombo and DirectoryCombo controls
70 internal const int MsidbControlAttributesRemovableVolume = 0x00010000;
71 internal const int MsidbControlAttributesFixedVolume = 0x00020000;
72 internal const int MsidbControlAttributesRemoteVolume = 0x00040000;
73 internal const int MsidbControlAttributesCDROMVolume = 0x00080000;
74 internal const int MsidbControlAttributesRAMDiskVolume = 0x00100000;
75 internal const int MsidbControlAttributesFloppyVolume = 0x00200000;
76
77 // VolumeCostList controls
78 internal const int MsidbControlShowRollbackCost = 0x00400000;
79
80 // ListBox and ComboBox controls
81 internal const int MsidbControlAttributesSorted = 0x00010000;
82 internal const int MsidbControlAttributesComboList = 0x00020000;
83
84 // picture button controls
85 internal const int MsidbControlAttributesImageHandle = 0x00010000;
86 internal const int MsidbControlAttributesPushLike = 0x00020000;
87 internal const int MsidbControlAttributesBitmap = 0x00040000;
88 internal const int MsidbControlAttributesIcon = 0x00080000;
89 internal const int MsidbControlAttributesFixedSize = 0x00100000;
90 internal const int MsidbControlAttributesIconSize16 = 0x00200000;
91 internal const int MsidbControlAttributesIconSize32 = 0x00400000;
92 internal const int MsidbControlAttributesIconSize48 = 0x00600000;
93 internal const int MsidbControlAttributesElevationShield = 0x00800000;
94
95 // RadioButton controls
96 internal const int MsidbControlAttributesHasBorder = 0x01000000;
97
98 // CustomAction.Type
99 // executable types
100 internal const int MsidbCustomActionTypeDll = 0x00000001; // Target = entry point name
101 internal const int MsidbCustomActionTypeExe = 0x00000002; // Target = command line args
102 internal const int MsidbCustomActionTypeTextData = 0x00000003; // Target = text string to be formatted and set into property
103 internal const int MsidbCustomActionTypeJScript = 0x00000005; // Target = entry point name; null if none to call
104 internal const int MsidbCustomActionTypeVBScript = 0x00000006; // Target = entry point name; null if none to call
105 internal const int MsidbCustomActionTypeInstall = 0x00000007; // Target = property list for nested engine initialization
106 internal const int MsidbCustomActionTypeSourceBits = 0x00000030;
107 internal const int MsidbCustomActionTypeTargetBits = 0x00000007;
108 internal const int MsidbCustomActionTypeReturnBits = 0x000000C0;
109 internal const int MsidbCustomActionTypeExecuteBits = 0x00000700;
110
111 // source of code
112 internal const int MsidbCustomActionTypeBinaryData = 0x00000000; // Source = Binary.Name; data stored in stream
113 internal const int MsidbCustomActionTypeSourceFile = 0x00000010; // Source = File.File; file part of installation
114 internal const int MsidbCustomActionTypeDirectory = 0x00000020; // Source = Directory.Directory; folder containing existing file
115 internal const int MsidbCustomActionTypeProperty = 0x00000030; // Source = Property.Property; full path to executable
116
117 // return processing; default is syncronous execution; process return code
118 internal const int MsidbCustomActionTypeContinue = 0x00000040; // ignore action return status; continue running
119 internal const int MsidbCustomActionTypeAsync = 0x00000080; // run asynchronously
120
121 // execution scheduling flags; default is execute whenever sequenced
122 internal const int MsidbCustomActionTypeFirstSequence = 0x00000100; // skip if UI sequence already run
123 internal const int MsidbCustomActionTypeOncePerProcess = 0x00000200; // skip if UI sequence already run in same process
124 internal const int MsidbCustomActionTypeClientRepeat = 0x00000300; // run on client only if UI already run on client
125 internal const int MsidbCustomActionTypeInScript = 0x00000400; // queue for execution within script
126 internal const int MsidbCustomActionTypeRollback = 0x00000100; // in conjunction with InScript: queue in Rollback script
127 internal const int MsidbCustomActionTypeCommit = 0x00000200; // in conjunction with InScript: run Commit ops from script on success
128
129 // security context flag; default to impersonate as user; valid only if InScript
130 internal const int MsidbCustomActionTypeNoImpersonate = 0x00000800; // no impersonation; run in system context
131 internal const int MsidbCustomActionTypeTSAware = 0x00004000; // impersonate for per-machine installs on TS machines
132 internal const int MsidbCustomActionType64BitScript = 0x00001000; // script should run in 64bit process
133 internal const int MsidbCustomActionTypeHideTarget = 0x00002000; // don't record the contents of the Target field in the log file.
134
135 internal const int MsidbCustomActionTypePatchUninstall = 0x00008000; // run on patch uninstall
136
137 // Dialog.Attributes
138 internal const int MsidbDialogAttributesVisible = 0x00000001;
139 internal const int MsidbDialogAttributesModal = 0x00000002;
140 internal const int MsidbDialogAttributesMinimize = 0x00000004;
141 internal const int MsidbDialogAttributesSysModal = 0x00000008;
142 internal const int MsidbDialogAttributesKeepModeless = 0x00000010;
143 internal const int MsidbDialogAttributesTrackDiskSpace = 0x00000020;
144 internal const int MsidbDialogAttributesUseCustomPalette = 0x00000040;
145 internal const int MsidbDialogAttributesRTLRO = 0x00000080;
146 internal const int MsidbDialogAttributesRightAligned = 0x00000100;
147 internal const int MsidbDialogAttributesLeftScroll = 0x00000200;
148 internal const int MsidbDialogAttributesBiDi = MsidbDialogAttributesRTLRO | MsidbDialogAttributesRightAligned | MsidbDialogAttributesLeftScroll;
149 internal const int MsidbDialogAttributesError = 0x00010000;
150 internal const int CommonControlAttributesInvert = MsidbControlAttributesVisible + MsidbControlAttributesEnabled;
151 internal const int DialogAttributesInvert = MsidbDialogAttributesVisible + MsidbDialogAttributesModal + MsidbDialogAttributesMinimize;
152
153 // Feature.Attributes
154 internal const int MsidbFeatureAttributesFavorLocal = 0;
155 internal const int MsidbFeatureAttributesFavorSource = 1;
156 internal const int MsidbFeatureAttributesFollowParent = 2;
157 internal const int MsidbFeatureAttributesFavorAdvertise = 4;
158 internal const int MsidbFeatureAttributesDisallowAdvertise = 8;
159 internal const int MsidbFeatureAttributesUIDisallowAbsent = 16;
160 internal const int MsidbFeatureAttributesNoUnsupportedAdvertise = 32;
161
162 // File.Attributes
163 internal const int MsidbFileAttributesReadOnly = 1;
164 internal const int MsidbFileAttributesHidden = 2;
165 internal const int MsidbFileAttributesSystem = 4;
166 internal const int MsidbFileAttributesVital = 512;
167 internal const int MsidbFileAttributesChecksum = 1024;
168 internal const int MsidbFileAttributesPatchAdded = 4096;
169 internal const int MsidbFileAttributesNoncompressed = 8192;
170 internal const int MsidbFileAttributesCompressed = 16384;
171
172 // IniFile.Action & RemoveIniFile.Action
173 internal const int MsidbIniFileActionAddLine = 0;
174 internal const int MsidbIniFileActionCreateLine = 1;
175 internal const int MsidbIniFileActionRemoveLine = 2;
176 internal const int MsidbIniFileActionAddTag = 3;
177 internal const int MsidbIniFileActionRemoveTag = 4;
178
179 // MoveFile.Options
180 internal const int MsidbMoveFileOptionsMove = 1;
181
182 // ServiceInstall.Attributes
183 internal const int MsidbServiceInstallOwnProcess = 0x00000010;
184 internal const int MsidbServiceInstallShareProcess = 0x00000020;
185 internal const int MsidbServiceInstallInteractive = 0x00000100;
186 internal const int MsidbServiceInstallAutoStart = 0x00000002;
187 internal const int MsidbServiceInstallDemandStart = 0x00000003;
188 internal const int MsidbServiceInstallDisabled = 0x00000004;
189 internal const int MsidbServiceInstallErrorIgnore = 0x00000000;
190 internal const int MsidbServiceInstallErrorNormal = 0x00000001;
191 internal const int MsidbServiceInstallErrorCritical = 0x00000003;
192 internal const int MsidbServiceInstallErrorControlVital = 0x00008000;
193
194 // ServiceConfig.Event
195 internal const int MsidbServiceConfigEventInstall = 0x00000001;
196 internal const int MsidbServiceConfigEventUninstall = 0x00000002;
197 internal const int MsidbServiceConfigEventReinstall = 0x00000004;
198
199 // ServiceControl.Attributes
200 internal const int MsidbServiceControlEventStart = 0x00000001;
201 internal const int MsidbServiceControlEventStop = 0x00000002;
202 internal const int MsidbServiceControlEventDelete = 0x00000008;
203 internal const int MsidbServiceControlEventUninstallStart = 0x00000010;
204 internal const int MsidbServiceControlEventUninstallStop = 0x00000020;
205 internal const int MsidbServiceControlEventUninstallDelete = 0x00000080;
206
207 // TextStyle.StyleBits
208 internal const int MsidbTextStyleStyleBitsBold = 1;
209 internal const int MsidbTextStyleStyleBitsItalic = 2;
210 internal const int MsidbTextStyleStyleBitsUnderline = 4;
211 internal const int MsidbTextStyleStyleBitsStrike = 8;
212
213 // Upgrade.Attributes
214 internal const int MsidbUpgradeAttributesMigrateFeatures = 0x00000001;
215 internal const int MsidbUpgradeAttributesOnlyDetect = 0x00000002;
216 internal const int MsidbUpgradeAttributesIgnoreRemoveFailure = 0x00000004;
217 internal const int MsidbUpgradeAttributesVersionMinInclusive = 0x00000100;
218 internal const int MsidbUpgradeAttributesVersionMaxInclusive = 0x00000200;
219 internal const int MsidbUpgradeAttributesLanguagesExclusive = 0x00000400;
220
221 // Registry Hive Roots
222 internal const int MsidbRegistryRootClassesRoot = 0;
223 internal const int MsidbRegistryRootCurrentUser = 1;
224 internal const int MsidbRegistryRootLocalMachine = 2;
225 internal const int MsidbRegistryRootUsers = 3;
226
227 // Locator Types
228 internal const int MsidbLocatorTypeDirectory = 0;
229 internal const int MsidbLocatorTypeFileName = 1;
230 internal const int MsidbLocatorTypeRawValue = 2;
231 internal const int MsidbLocatorType64bit = 16;
232
233 internal const int MsidbClassAttributesRelativePath = 1;
234
235 // RemoveFile.InstallMode
236 internal const int MsidbRemoveFileInstallModeOnInstall = 0x00000001;
237 internal const int MsidbRemoveFileInstallModeOnRemove = 0x00000002;
238 internal const int MsidbRemoveFileInstallModeOnBoth = 0x00000003;
239
240 // ODBCDataSource.Registration
241 internal const int MsidbODBCDataSourceRegistrationPerMachine = 0;
242 internal const int MsidbODBCDataSourceRegistrationPerUser = 1;
243
244 // ModuleConfiguration.Format
245 internal const int MsidbModuleConfigurationFormatText = 0;
246 internal const int MsidbModuleConfigurationFormatKey = 1;
247 internal const int MsidbModuleConfigurationFormatInteger = 2;
248 internal const int MsidbModuleConfigurationFormatBitfield = 3;
249
250 // ModuleConfiguration.Attributes
251 internal const int MsidbMsmConfigurableOptionKeyNoOrphan = 1;
252 internal const int MsidbMsmConfigurableOptionNonNullable = 2;
253
254 // ' Windows API function ShowWindow constants - used in Shortcut table
255 internal const int SWSHOWNORMAL = 0x00000001;
256 internal const int SWSHOWMAXIMIZED = 0x00000003;
257 internal const int SWSHOWMINNOACTIVE = 0x00000007;
258
259 // NameToBit arrays
260 // UI elements
261 internal static readonly string[] CommonControlAttributes = { "Hidden", "Disabled", "Sunken", "Indirect", "Integer", "RightToLeft", "RightAligned", "LeftScroll" };
262 internal static readonly string[] TextControlAttributes = { "Transparent", "NoPrefix", "NoWrap", "FormatSize", "UserLanguage" };
263 internal static readonly string[] HyperlinkControlAttributes = { "Transparent" };
264 internal static readonly string[] EditControlAttributes = { "Multiline", null, null, null, null, "Password" };
265 internal static readonly string[] ProgressControlAttributes = { "ProgressBlocks" };
266 internal static readonly string[] VolumeControlAttributes = { "Removable", "Fixed", "Remote", "CDROM", "RAMDisk", "Floppy", "ShowRollbackCost" };
267 internal static readonly string[] ListboxControlAttributes = { "Sorted", null, null, null, "UserLanguage" };
268 internal static readonly string[] ListviewControlAttributes = { "Sorted", null, null, null, "FixedSize", "Icon16", "Icon32" };
269 internal static readonly string[] ComboboxControlAttributes = { "Sorted", "ComboList", null, null, "UserLanguage" };
270 internal static readonly string[] RadioControlAttributes = { "Image", "PushLike", "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32", null, "HasBorder" };
271 internal static readonly string[] ButtonControlAttributes = { "Image", null, "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32", "ElevationShield" };
272 internal static readonly string[] IconControlAttributes = { "Image", null, null, null, "FixedSize", "Icon16", "Icon32" };
273 internal static readonly string[] BitmapControlAttributes = { "Image", null, null, null, "FixedSize" };
274 internal static readonly string[] CheckboxControlAttributes = { null, "PushLike", "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32" };
275
276 internal const int MsidbEmbeddedUI = 0x01;
277 internal const int MsidbEmbeddedHandlesBasic = 0x02;
278
279 internal const int INSTALLLOGMODE_FATALEXIT = 0x00001;
280 internal const int INSTALLLOGMODE_ERROR = 0x00002;
281 internal const int INSTALLLOGMODE_WARNING = 0x00004;
282 internal const int INSTALLLOGMODE_USER = 0x00008;
283 internal const int INSTALLLOGMODE_INFO = 0x00010;
284 internal const int INSTALLLOGMODE_FILESINUSE = 0x00020;
285 internal const int INSTALLLOGMODE_RESOLVESOURCE = 0x00040;
286 internal const int INSTALLLOGMODE_OUTOFDISKSPACE = 0x00080;
287 internal const int INSTALLLOGMODE_ACTIONSTART = 0x00100;
288 internal const int INSTALLLOGMODE_ACTIONDATA = 0x00200;
289 internal const int INSTALLLOGMODE_PROGRESS = 0x00400;
290 internal const int INSTALLLOGMODE_COMMONDATA = 0x00800;
291 internal const int INSTALLLOGMODE_INITIALIZE = 0x01000;
292 internal const int INSTALLLOGMODE_TERMINATE = 0x02000;
293 internal const int INSTALLLOGMODE_SHOWDIALOG = 0x04000;
294 internal const int INSTALLLOGMODE_RMFILESINUSE = 0x02000000;
295 internal const int INSTALLLOGMODE_INSTALLSTART = 0x04000000;
296 internal const int INSTALLLOGMODE_INSTALLEND = 0x08000000;
297
298 internal const int MSICONDITIONFALSE = 0; // The table is temporary.
299 internal const int MSICONDITIONTRUE = 1; // The table is persistent.
300 internal const int MSICONDITIONNONE = 2; // The table is unknown.
301 internal const int MSICONDITIONERROR = 3; // An invalid handle or invalid parameter was passed to the function.
302
303 internal const int MSIDBOPENREADONLY = 0;
304 internal const int MSIDBOPENTRANSACT = 1;
305 internal const int MSIDBOPENDIRECT = 2;
306 internal const int MSIDBOPENCREATE = 3;
307 internal const int MSIDBOPENCREATEDIRECT = 4;
308 internal const int MSIDBOPENPATCHFILE = 32;
309
310 internal const int MSIMODIFYSEEK = -1; // Refreshes the information in the supplied record without changing the position in the result set and without affecting subsequent fetch operations. The record may then be used for subsequent Update, Delete, and Refresh. All primary key columns of the table must be in the query and the record must have at least as many fields as the query. Seek cannot be used with multi-table queries. This mode cannot be used with a view containing joins. See also the remarks.
311 internal const int MSIMODIFYREFRESH = 0; // Refreshes the information in the record. Must first call MsiViewFetch with the same record. Fails for a deleted row. Works with read-write and read-only records.
312 internal const int MSIMODIFYINSERT = 1; // Inserts a record. Fails if a row with the same primary keys exists. Fails with a read-only database. This mode cannot be used with a view containing joins.
313 internal const int MSIMODIFYUPDATE = 2; // Updates an existing record. Nonprimary keys only. Must first call MsiViewFetch. Fails with a deleted record. Works only with read-write records.
314 internal const int MSIMODIFYASSIGN = 3; // Writes current data in the cursor to a table row. Updates record if the primary keys match an existing row and inserts if they do not match. Fails with a read-only database. This mode cannot be used with a view containing joins.
315 internal const int MSIMODIFYREPLACE = 4; // Updates or deletes and inserts a record into a table. Must first call MsiViewFetch with the same record. Updates record if the primary keys are unchanged. Deletes old row and inserts new if primary keys have changed. Fails with a read-only database. This mode cannot be used with a view containing joins.
316 internal const int MSIMODIFYMERGE = 5; // Inserts or validates a record in a table. Inserts if primary keys do not match any row and validates if there is a match. Fails if the record does not match the data in the table. Fails if there is a record with a duplicate key that is not identical. Works only with read-write records. This mode cannot be used with a view containing joins.
317 internal const int MSIMODIFYDELETE = 6; // Remove a row from the table. You must first call the MsiViewFetch function with the same record. Fails if the row has been deleted. Works only with read-write records. This mode cannot be used with a view containing joins.
318 internal const int MSIMODIFYINSERTTEMPORARY = 7; // Inserts a temporary record. The information is not persistent. Fails if a row with the same primary key exists. Works only with read-write records. This mode cannot be used with a view containing joins.
319 internal const int MSIMODIFYVALIDATE = 8; // Validates a record. Does not validate across joins. You must first call the MsiViewFetch function with the same record. Obtain validation errors with MsiViewGetError. Works with read-write and read-only records. This mode cannot be used with a view containing joins.
320 internal const int MSIMODIFYVALIDATENEW = 9; // Validate a new record. Does not validate across joins. Checks for duplicate keys. Obtain validation errors by calling MsiViewGetError. Works with read-write and read-only records. This mode cannot be used with a view containing joins.
321 internal const int MSIMODIFYVALIDATEFIELD = 10; // Validates fields of a fetched or new record. Can validate one or more fields of an incomplete record. Obtain validation errors by calling MsiViewGetError. Works with read-write and read-only records. This mode cannot be used with a view containing joins.
322 internal const int MSIMODIFYVALIDATEDELETE = 11; // Validates a record that will be deleted later. You must first call MsiViewFetch. Fails if another row refers to the primary keys of this row. Validation does not check for the existence of the primary keys of this row in properties or strings. Does not check if a column is a foreign key to multiple tables. Obtain validation errors by calling MsiViewGetError. Works with read-write and read-only records. This mode cannot be used with a view containing joins.
323
324 internal const uint VTI2 = 2;
325 internal const uint VTI4 = 3;
326 internal const uint VTLPWSTR = 30;
327 internal const uint VTFILETIME = 64;
328
329 internal const int MSICOLINFONAMES = 0; // return column names
330 internal const int MSICOLINFOTYPES = 1; // return column definitions, datatype code followed by width
331
332 /// <summary>
333 /// Protect the constructor.
334 /// </summary>
335 private MsiInterop()
336 {
337 }
338
339 /// <summary>
340 /// PInvoke of MsiCloseHandle.
341 /// </summary>
342 /// <param name="database">Handle to a database.</param>
343 /// <returns>Error code.</returns>
344 [DllImport("msi.dll", EntryPoint = "MsiCloseHandle", CharSet = CharSet.Unicode, ExactSpelling = true)]
345 internal static extern int MsiCloseHandle(uint database);
346
347 /// <summary>
348 /// PInvoke of MsiCreateRecord
349 /// </summary>
350 /// <param name="parameters">Count of columns in the record.</param>
351 /// <returns>Handle referencing the record.</returns>
352 [DllImport("msi.dll", EntryPoint = "MsiCreateRecord", CharSet = CharSet.Unicode, ExactSpelling = true)]
353 internal static extern uint MsiCreateRecord(int parameters);
354
355 /// <summary>
356 /// Creates summary information of an existing transform to include validation and error conditions.
357 /// </summary>
358 /// <param name="database">The handle to the database that contains the new database summary information.</param>
359 /// <param name="referenceDatabase">The handle to the database that contains the original summary information.</param>
360 /// <param name="transformFile">The name of the transform to which the summary information is added.</param>
361 /// <param name="errorConditions">The error conditions that should be suppressed when the transform is applied.</param>
362 /// <param name="validations">Specifies the properties to be validated to verify that the transform can be applied to the database.</param>
363 /// <returns>Error code.</returns>
364 [DllImport("msi.dll", EntryPoint = "MsiCreateTransformSummaryInfoW", CharSet = CharSet.Unicode, ExactSpelling = true)]
365 internal static extern int MsiCreateTransformSummaryInfo(uint database, uint referenceDatabase, string transformFile, TransformErrorConditions errorConditions, TransformValidations validations);
366
367 /// <summary>
368 /// Applies a transform to a database.
369 /// </summary>
370 /// <param name="database">Handle to the database obtained from MsiOpenDatabase to transform.</param>
371 /// <param name="transformFile">Specifies the name of the transform file to apply.</param>
372 /// <param name="errorConditions">Error conditions that should be suppressed.</param>
373 /// <returns>Error code.</returns>
374 [DllImport("msi.dll", EntryPoint = "MsiDatabaseApplyTransformW", CharSet = CharSet.Unicode, ExactSpelling = true)]
375 internal static extern int MsiDatabaseApplyTransform(uint database, string transformFile, TransformErrorConditions errorConditions);
376
377 /// <summary>
378 /// PInvoke of MsiDatabaseCommit.
379 /// </summary>
380 /// <param name="database">Handle to a databse.</param>
381 /// <returns>Error code.</returns>
382 [DllImport("msi.dll", EntryPoint = "MsiDatabaseCommit", CharSet = CharSet.Unicode, ExactSpelling = true)]
383 internal static extern int MsiDatabaseCommit(uint database);
384
385 /// <summary>
386 /// PInvoke of MsiDatabaseExportW.
387 /// </summary>
388 /// <param name="database">Handle to a database.</param>
389 /// <param name="tableName">Table name.</param>
390 /// <param name="folderPath">Folder path.</param>
391 /// <param name="fileName">File name.</param>
392 /// <returns>Error code.</returns>
393 [DllImport("msi.dll", EntryPoint = "MsiDatabaseExportW", CharSet = CharSet.Unicode, ExactSpelling = true)]
394 internal static extern int MsiDatabaseExport(uint database, string tableName, string folderPath, string fileName);
395
396 /// <summary>
397 /// Generates a transform file of differences between two databases.
398 /// </summary>
399 /// <param name="database">Handle to the database obtained from MsiOpenDatabase that includes the changes.</param>
400 /// <param name="databaseReference">Handle to the database obtained from MsiOpenDatabase that does not include the changes.</param>
401 /// <param name="transformFile">A null-terminated string that specifies the name of the transform file being generated.
402 /// This parameter can be null. If szTransformFile is null, you can use MsiDatabaseGenerateTransform to test whether two
403 /// databases are identical without creating a transform. If the databases are identical, the function returns ERROR_NO_DATA.
404 /// If the databases are different the function returns NOERROR.</param>
405 /// <param name="reserved1">This is a reserved argument and must be set to 0.</param>
406 /// <param name="reserved2">This is a reserved argument and must be set to 0.</param>
407 /// <returns>Error code.</returns>
408 [DllImport("msi.dll", EntryPoint = "MsiDatabaseGenerateTransformW", CharSet = CharSet.Unicode, ExactSpelling = true)]
409 internal static extern int MsiDatabaseGenerateTransform(uint database, uint databaseReference, string transformFile, int reserved1, int reserved2);
410
411 /// <summary>
412 /// PInvoke of MsiDatabaseImportW.
413 /// </summary>
414 /// <param name="database">Handle to a database.</param>
415 /// <param name="folderPath">Folder path.</param>
416 /// <param name="fileName">File name.</param>
417 /// <returns>Error code.</returns>
418 [DllImport("msi.dll", EntryPoint = "MsiDatabaseImportW", CharSet = CharSet.Unicode, ExactSpelling = true)]
419 internal static extern int MsiDatabaseImport(uint database, string folderPath, string fileName);
420
421 /// <summary>
422 /// PInvoke of MsiDatabaseMergeW.
423 /// </summary>
424 /// <param name="database">The handle to the database obtained from MsiOpenDatabase.</param>
425 /// <param name="databaseMerge">The handle to the database obtained from MsiOpenDatabase to merge into the base database.</param>
426 /// <param name="tableName">The name of the table to receive merge conflict information.</param>
427 /// <returns>Error code.</returns>
428 [DllImport("msi.dll", EntryPoint = "MsiDatabaseMergeW", CharSet = CharSet.Unicode, ExactSpelling = true)]
429 internal static extern int MsiDatabaseMerge(uint database, uint databaseMerge, string tableName);
430
431 /// <summary>
432 /// PInvoke of MsiDatabaseOpenViewW.
433 /// </summary>
434 /// <param name="database">Handle to a database.</param>
435 /// <param name="query">SQL query.</param>
436 /// <param name="view">View handle.</param>
437 /// <returns>Error code.</returns>
438 [DllImport("msi.dll", EntryPoint = "MsiDatabaseOpenViewW", CharSet = CharSet.Unicode, ExactSpelling = true)]
439 internal static extern int MsiDatabaseOpenView(uint database, string query, out uint view);
440
441 /// <summary>
442 /// PInvoke of MsiGetFileHashW.
443 /// </summary>
444 /// <param name="filePath">File path.</param>
445 /// <param name="options">Hash options (must be 0).</param>
446 /// <param name="hash">Buffer to recieve hash.</param>
447 /// <returns>Error code.</returns>
448 [DllImport("msi.dll", EntryPoint = "MsiGetFileHashW", CharSet = CharSet.Unicode, ExactSpelling = true)]
449 internal static extern int MsiGetFileHash(string filePath, uint options, MSIFILEHASHINFO hash);
450
451 /// <summary>
452 /// PInvoke of MsiGetFileVersionW.
453 /// </summary>
454 /// <param name="filePath">File path.</param>
455 /// <param name="versionBuf">Buffer to receive version info.</param>
456 /// <param name="versionBufSize">Size of version buffer.</param>
457 /// <param name="langBuf">Buffer to recieve lang info.</param>
458 /// <param name="langBufSize">Size of lang buffer.</param>
459 /// <returns>Error code.</returns>
460 [DllImport("msi.dll", EntryPoint = "MsiGetFileVersionW", CharSet = CharSet.Unicode, ExactSpelling = true)]
461 internal static extern int MsiGetFileVersion(string filePath, StringBuilder versionBuf, ref int versionBufSize, StringBuilder langBuf, ref int langBufSize);
462
463 /// <summary>
464 /// PInvoke of MsiGetLastErrorRecord.
465 /// </summary>
466 /// <returns>Handle to error record if one exists.</returns>
467 [DllImport("msi.dll", EntryPoint = "MsiGetLastErrorRecord", CharSet = CharSet.Unicode, ExactSpelling = true)]
468 internal static extern uint MsiGetLastErrorRecord();
469
470 /// <summary>
471 /// PInvoke of MsiDatabaseGetPrimaryKeysW.
472 /// </summary>
473 /// <param name="database">Handle to a database.</param>
474 /// <param name="tableName">Table name.</param>
475 /// <param name="record">Handle to receive resulting record.</param>
476 /// <returns>Error code.</returns>
477 [DllImport("msi.dll", EntryPoint = "MsiDatabaseGetPrimaryKeysW", CharSet = CharSet.Unicode, ExactSpelling = true)]
478 internal static extern int MsiDatabaseGetPrimaryKeys(uint database, string tableName, out uint record);
479
480 /// <summary>
481 /// PInvoke of MsiDoActionW.
482 /// </summary>
483 /// <param name="product">Handle to the installation provided to a DLL custom action or
484 /// obtained through MsiOpenPackage, MsiOpenPackageEx, or MsiOpenProduct.</param>
485 /// <param name="action">Specifies the action to execute.</param>
486 /// <returns>Error code.</returns>
487 [DllImport("msi.dll", EntryPoint = "MsiDoActionW", CharSet = CharSet.Unicode, ExactSpelling = true)]
488 internal static extern int MsiDoAction(uint product, string action);
489
490 /// <summary>
491 /// PInvoke of MsiGetSummaryInformationW. Can use either database handle or database path as input.
492 /// </summary>
493 /// <param name="database">Handle to a database.</param>
494 /// <param name="databasePath">Path to a database.</param>
495 /// <param name="updateCount">Max number of updated values.</param>
496 /// <param name="summaryInfo">Handle to summary information.</param>
497 /// <returns>Error code.</returns>
498 [DllImport("msi.dll", EntryPoint = "MsiGetSummaryInformationW", CharSet = CharSet.Unicode, ExactSpelling = true)]
499 internal static extern int MsiGetSummaryInformation(uint database, string databasePath, uint updateCount, ref uint summaryInfo);
500
501 /// <summary>
502 /// PInvoke of MsiDatabaseIsTablePersitentW.
503 /// </summary>
504 /// <param name="database">Handle to a database.</param>
505 /// <param name="tableName">Table name.</param>
506 /// <returns>MSICONDITION</returns>
507 [DllImport("msi.dll", EntryPoint = "MsiDatabaseIsTablePersistentW", CharSet = CharSet.Unicode, ExactSpelling = true)]
508 internal static extern int MsiDatabaseIsTablePersistent(uint database, string tableName);
509
510 /// <summary>
511 /// PInvoke of MsiOpenDatabaseW.
512 /// </summary>
513 /// <param name="databasePath">Path to database.</param>
514 /// <param name="persist">Persist mode.</param>
515 /// <param name="database">Handle to database.</param>
516 /// <returns>Error code.</returns>
517 [DllImport("msi.dll", EntryPoint = "MsiOpenDatabaseW", CharSet = CharSet.Unicode, ExactSpelling = true)]
518 internal static extern int MsiOpenDatabase(string databasePath, IntPtr persist, out uint database);
519
520 /// <summary>
521 /// PInvoke of MsiOpenPackageW.
522 /// </summary>
523 /// <param name="packagePath">The path to the package.</param>
524 /// <param name="product">A pointer to a variable that receives the product handle.</param>
525 /// <returns>Error code.</returns>
526 [DllImport("msi.dll", EntryPoint = "MsiOpenPackageW", CharSet = CharSet.Unicode, ExactSpelling = true)]
527 internal static extern int MsiOpenPackage(string packagePath, out uint product);
528
529 /// <summary>
530 /// PInvoke of MsiRecordIsNull.
531 /// </summary>
532 /// <param name="record">MSI Record handle.</param>
533 /// <param name="field">Index of field to check for null value.</param>
534 /// <returns>true if the field is null, false if not, and an error code for any error.</returns>
535 [DllImport("msi.dll", EntryPoint = "MsiRecordIsNull", CharSet = CharSet.Unicode, ExactSpelling = true)]
536 internal static extern int MsiRecordIsNull(uint record, int field);
537
538 /// <summary>
539 /// PInvoke of MsiRecordGetInteger.
540 /// </summary>
541 /// <param name="record">MSI Record handle.</param>
542 /// <param name="field">Index of field to retrieve integer from.</param>
543 /// <returns>Integer value.</returns>
544 [DllImport("msi.dll", EntryPoint = "MsiRecordGetInteger", CharSet = CharSet.Unicode, ExactSpelling = true)]
545 internal static extern int MsiRecordGetInteger(uint record, int field);
546
547 /// <summary>
548 /// PInvoke of MsiRectordSetInteger.
549 /// </summary>
550 /// <param name="record">MSI Record handle.</param>
551 /// <param name="field">Index of field to set integer value in.</param>
552 /// <param name="value">Value to set field to.</param>
553 /// <returns>Error code.</returns>
554 [DllImport("msi.dll", EntryPoint = "MsiRecordSetInteger", CharSet = CharSet.Unicode, ExactSpelling = true)]
555 internal static extern int MsiRecordSetInteger(uint record, int field, int value);
556
557 /// <summary>
558 /// PInvoke of MsiRecordGetStringW.
559 /// </summary>
560 /// <param name="record">MSI Record handle.</param>
561 /// <param name="field">Index of field to get string value from.</param>
562 /// <param name="valueBuf">Buffer to recieve value.</param>
563 /// <param name="valueBufSize">Size of buffer.</param>
564 /// <returns>Error code.</returns>
565 [DllImport("msi.dll", EntryPoint = "MsiRecordGetStringW", CharSet = CharSet.Unicode, ExactSpelling = true)]
566 internal static extern int MsiRecordGetString(uint record, int field, StringBuilder valueBuf, ref int valueBufSize);
567
568 /// <summary>
569 /// PInvoke of MsiRecordSetStringW.
570 /// </summary>
571 /// <param name="record">MSI Record handle.</param>
572 /// <param name="field">Index of field to set string value in.</param>
573 /// <param name="value">String value.</param>
574 /// <returns>Error code.</returns>
575 [DllImport("msi.dll", EntryPoint = "MsiRecordSetStringW", CharSet = CharSet.Unicode, ExactSpelling = true)]
576 internal static extern int MsiRecordSetString(uint record, int field, string value);
577
578 /// <summary>
579 /// PInvoke of MsiRecordSetStreamW.
580 /// </summary>
581 /// <param name="record">MSI Record handle.</param>
582 /// <param name="field">Index of field to set stream value in.</param>
583 /// <param name="filePath">Path to file to set stream value to.</param>
584 /// <returns>Error code.</returns>
585 [DllImport("msi.dll", EntryPoint = "MsiRecordSetStreamW", CharSet = CharSet.Unicode, ExactSpelling = true)]
586 internal static extern int MsiRecordSetStream(uint record, int field, string filePath);
587
588 /// <summary>
589 /// PInvoke of MsiRecordReadStreamW.
590 /// </summary>
591 /// <param name="record">MSI Record handle.</param>
592 /// <param name="field">Index of field to read stream from.</param>
593 /// <param name="dataBuf">Data buffer to recieve stream value.</param>
594 /// <param name="dataBufSize">Size of data buffer.</param>
595 /// <returns>Error code.</returns>
596 [DllImport("msi.dll", EntryPoint = "MsiRecordReadStream", CharSet = CharSet.Unicode, ExactSpelling = true)]
597 internal static extern int MsiRecordReadStream(uint record, int field, byte[] dataBuf, ref int dataBufSize);
598
599 /// <summary>
600 /// PInvoke of MsiRecordGetFieldCount.
601 /// </summary>
602 /// <param name="record">MSI Record handle.</param>
603 /// <returns>Count of fields in the record.</returns>
604 [DllImport("msi.dll", EntryPoint = "MsiRecordGetFieldCount", CharSet = CharSet.Unicode, ExactSpelling = true)]
605 internal static extern int MsiRecordGetFieldCount(uint record);
606
607 /// <summary>
608 /// PInvoke of MsiSetExternalUIW.
609 /// </summary>
610 /// <param name="installUIHandler">Specifies a callback function that conforms to the INSTALLUI_HANDLER specification.</param>
611 /// <param name="installLogMode">Specifies which messages to handle using the external message handler. If the external
612 /// handler returns a non-zero result, then that message will not be sent to the UI, instead the message will be logged
613 /// if logging has been enabled.</param>
614 /// <param name="context">Pointer to an application context that is passed to the callback function.
615 /// This parameter can be used for error checking.</param>
616 /// <returns>The return value is the previously set external handler, or zero (0) if there was no previously set handler.</returns>
617 [DllImport("msi.dll", EntryPoint = "MsiSetExternalUIW", CharSet = CharSet.Unicode, ExactSpelling = true)]
618 internal static extern InstallUIHandler MsiSetExternalUI(InstallUIHandler installUIHandler, int installLogMode, IntPtr context);
619
620 /// <summary>
621 /// PInvoke of MsiSetInternalUI.
622 /// </summary>
623 /// <param name="uiLevel">Specifies the level of complexity of the user interface.</param>
624 /// <param name="hwnd">Pointer to a window. This window becomes the owner of any user interface created.
625 /// A pointer to the previous owner of the user interface is returned.
626 /// If this parameter is null, the owner of the user interface does not change.</param>
627 /// <returns>The previous user interface level is returned. If an invalid dwUILevel is passed, then INSTALLUILEVEL_NOCHANGE is returned.</returns>
628 [DllImport("msi.dll", EntryPoint = "MsiSetInternalUI", CharSet = CharSet.Unicode, ExactSpelling = true)]
629 internal static extern int MsiSetInternalUI(int uiLevel, ref IntPtr hwnd);
630
631 /// <summary>
632 /// PInvoke of MsiSummaryInfoGetPropertyW.
633 /// </summary>
634 /// <param name="summaryInfo">Handle to summary info.</param>
635 /// <param name="property">Property to get value from.</param>
636 /// <param name="dataType">Data type of property.</param>
637 /// <param name="integerValue">Integer to receive integer value.</param>
638 /// <param name="fileTimeValue">File time to receive file time value.</param>
639 /// <param name="stringValueBuf">String buffer to receive string value.</param>
640 /// <param name="stringValueBufSize">Size of string buffer.</param>
641 /// <returns>Error code.</returns>
642 [DllImport("msi.dll", EntryPoint = "MsiSummaryInfoGetPropertyW", CharSet = CharSet.Unicode, ExactSpelling = true)]
643 internal static extern int MsiSummaryInfoGetProperty(uint summaryInfo, int property, out uint dataType, out int integerValue, ref FILETIME fileTimeValue, StringBuilder stringValueBuf, ref int stringValueBufSize);
644
645 /// <summary>
646 /// PInvoke of MsiViewGetColumnInfo.
647 /// </summary>
648 /// <param name="view">Handle to view.</param>
649 /// <param name="columnInfo">Column info.</param>
650 /// <param name="record">Handle for returned record.</param>
651 /// <returns>Error code.</returns>
652 [DllImport("msi.dll", EntryPoint = "MsiViewGetColumnInfo", CharSet = CharSet.Unicode, ExactSpelling = true)]
653 internal static extern int MsiViewGetColumnInfo(uint view, int columnInfo, out uint record);
654
655 /// <summary>
656 /// PInvoke of MsiViewExecute.
657 /// </summary>
658 /// <param name="view">Handle of view to execute.</param>
659 /// <param name="record">Handle to a record that supplies the parameters for the view.</param>
660 /// <returns>Error code.</returns>
661 [DllImport("msi.dll", EntryPoint = "MsiViewExecute", CharSet = CharSet.Unicode, ExactSpelling = true)]
662 internal static extern int MsiViewExecute(uint view, uint record);
663
664 /// <summary>
665 /// PInvoke of MsiViewFetch.
666 /// </summary>
667 /// <param name="view">Handle of view to fetch a row from.</param>
668 /// <param name="record">Handle to receive record info.</param>
669 /// <returns>Error code.</returns>
670 [DllImport("msi.dll", EntryPoint = "MsiViewFetch", CharSet = CharSet.Unicode, ExactSpelling = true)]
671 internal static extern int MsiViewFetch(uint view, out uint record);
672
673 /// <summary>
674 /// PInvoke of MsiViewModify.
675 /// </summary>
676 /// <param name="view">Handle of view to modify.</param>
677 /// <param name="modifyMode">Modify mode.</param>
678 /// <param name="record">Handle of record.</param>
679 /// <returns>Error code.</returns>
680 [DllImport("msi.dll", EntryPoint = "MsiViewModify", CharSet = CharSet.Unicode, ExactSpelling = true)]
681 internal static extern int MsiViewModify(uint view, int modifyMode, uint record);
682
683 /// <summary>
684 /// contains the file hash information returned by MsiGetFileHash and used in the MsiFileHash table.
685 /// </summary>
686 [StructLayout(LayoutKind.Explicit)]
687 internal class MSIFILEHASHINFO
688 {
689 [FieldOffset(0)] internal uint FileHashInfoSize;
690 [FieldOffset(4)] internal int Data0;
691 [FieldOffset(8)] internal int Data1;
692 [FieldOffset(12)]internal int Data2;
693 [FieldOffset(16)]internal int Data3;
694 }
695 }
696}
697#endif \ No newline at end of file
diff --git a/src/WixToolset.Core/Msi/MsiException.cs b/src/WixToolset.Core/Msi/MsiException.cs
new file mode 100644
index 00000000..b33bf27a
--- /dev/null
+++ b/src/WixToolset.Core/Msi/MsiException.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 WixToolset.Msi
4{
5 using System;
6 using System.ComponentModel;
7 using WixToolset.Core.Native;
8
9 /// <summary>
10 /// Exception that wraps MsiGetLastError().
11 /// </summary>
12 [Serializable]
13 public class MsiException : Win32Exception
14 {
15 /// <summary>
16 /// Instantiate a new MsiException with a given error.
17 /// </summary>
18 /// <param name="error">The error code from the MsiXxx() function call.</param>
19 public MsiException(int error) : base(error)
20 {
21 uint handle = MsiInterop.MsiGetLastErrorRecord();
22 if (0 != handle)
23 {
24 using (Record record = new Record(handle))
25 {
26 this.MsiError = record.GetInteger(1);
27
28 int errorInfoCount = record.GetFieldCount() - 1;
29 this.ErrorInfo = new string[errorInfoCount];
30 for (int i = 0; i < errorInfoCount; ++i)
31 {
32 this.ErrorInfo[i] = record.GetString(i + 2);
33 }
34 }
35 }
36 else
37 {
38 this.MsiError = 0;
39 this.ErrorInfo = new string[0];
40 }
41
42 this.Error = error;
43 }
44
45 /// <summary>
46 /// Gets the error number.
47 /// </summary>
48 public int Error { get; private set; }
49
50 /// <summary>
51 /// Gets the internal MSI error number.
52 /// </summary>
53 public int MsiError { get; private set; }
54
55 /// <summary>
56 /// Gets any additional the error information.
57 /// </summary>
58 public string[] ErrorInfo { get; private set; }
59
60 /// <summary>
61 /// Overrides Message property to return useful error message.
62 /// </summary>
63 public override string Message
64 {
65 get
66 {
67 if (0 == this.MsiError)
68 {
69 return base.Message;
70 }
71 else
72 {
73 return String.Format("Internal MSI failure. Win32 error: {0}, MSI error: {1}, detail: {2}", this.Error, this.MsiError, String.Join(", ", this.ErrorInfo));
74 }
75 }
76 }
77 }
78}
diff --git a/src/WixToolset.Core/Msi/MsiHandle.cs b/src/WixToolset.Core/Msi/MsiHandle.cs
new file mode 100644
index 00000000..6d2dc984
--- /dev/null
+++ b/src/WixToolset.Core/Msi/MsiHandle.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.Msi
4{
5 using System;
6 using System.ComponentModel;
7 using System.Diagnostics;
8 using System.Threading;
9 using WixToolset.Core.Native;
10
11 /// <summary>
12 /// Wrapper class for MSI handle.
13 /// </summary>
14 public class MsiHandle : IDisposable
15 {
16 private bool disposed;
17 private uint handle;
18 private int owningThread;
19#if DEBUG
20 private string creationStack;
21#endif
22
23 /// <summary>
24 /// MSI handle destructor.
25 /// </summary>
26 ~MsiHandle()
27 {
28 this.Dispose(false);
29 }
30
31 /// <summary>
32 /// Gets or sets the MSI handle.
33 /// </summary>
34 /// <value>The MSI handle.</value>
35 internal uint Handle
36 {
37 get
38 {
39 if (this.disposed)
40 {
41 throw new ObjectDisposedException("MsiHandle");
42 }
43
44 return this.handle;
45 }
46
47 set
48 {
49 if (this.disposed)
50 {
51 throw new ObjectDisposedException("MsiHandle");
52 }
53
54 this.handle = value;
55 this.owningThread = Thread.CurrentThread.ManagedThreadId;
56#if DEBUG
57 this.creationStack = Environment.StackTrace;
58#endif
59 }
60 }
61
62 /// <summary>
63 /// Close the MSI handle.
64 /// </summary>
65 public void Close()
66 {
67 this.Dispose();
68 }
69
70 /// <summary>
71 /// Disposes the managed and unmanaged objects in this object.
72 /// </summary>
73 public void Dispose()
74 {
75 this.Dispose(true);
76 GC.SuppressFinalize(this);
77 }
78
79 /// <summary>
80 /// Disposes the managed and unmanaged objects in this object.
81 /// </summary>
82 /// <param name="disposing">true to dispose the managed objects.</param>
83 protected virtual void Dispose(bool disposing)
84 {
85 if (!this.disposed)
86 {
87 if (0 != this.handle)
88 {
89 if (Thread.CurrentThread.ManagedThreadId == this.owningThread)
90 {
91 int error = MsiInterop.MsiCloseHandle(this.handle);
92 if (0 != error)
93 {
94 throw new Win32Exception(error);
95 }
96 this.handle = 0;
97 }
98 else
99 {
100 // Don't try to close the handle on a different thread than it was opened.
101 // This will occasionally cause MSI to AV.
102 string message = String.Format("Leaked msi handle {0} created on thread {1} by type {2}. This handle cannot be closed on thread {3}",
103 this.handle, this.owningThread, this.GetType(), Thread.CurrentThread.ManagedThreadId);
104#if DEBUG
105 throw new InvalidOperationException(String.Format("{0}. Created {1}", message, this.creationStack));
106#else
107 Debug.WriteLine(message);
108#endif
109 }
110 }
111
112 this.disposed = true;
113 }
114 }
115 }
116}
diff --git a/src/WixToolset.Core/Msi/Record.cs b/src/WixToolset.Core/Msi/Record.cs
new file mode 100644
index 00000000..438aa3b0
--- /dev/null
+++ b/src/WixToolset.Core/Msi/Record.cs
@@ -0,0 +1,182 @@
1// Copyright (c) .NET Foundation and 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.Msi
4{
5 using System;
6 using System.ComponentModel;
7 using System.Text;
8 using WixToolset.Core.Native;
9
10 /// <summary>
11 /// Wrapper class around msi.dll interop for a record.
12 /// </summary>
13 public sealed class Record : MsiHandle
14 {
15 /// <summary>
16 /// Creates a record with the specified number of fields.
17 /// </summary>
18 /// <param name="fieldCount">Number of fields in record.</param>
19 public Record(int fieldCount)
20 {
21 this.Handle = MsiInterop.MsiCreateRecord(fieldCount);
22 if (0 == this.Handle)
23 {
24 throw new OutOfMemoryException();
25 }
26 }
27
28 /// <summary>
29 /// Creates a record from a handle.
30 /// </summary>
31 /// <param name="handle">Handle to create record from.</param>
32 internal Record(uint handle)
33 {
34 this.Handle = handle;
35 }
36
37 /// <summary>
38 /// Gets a string value at specified location.
39 /// </summary>
40 /// <param name="field">Index into record to get string.</param>
41 public string this[int field]
42 {
43 get { return this.GetString(field); }
44 set { this.SetString(field, (string)value); }
45 }
46
47 /// <summary>
48 /// Determines if the value is null at the specified location.
49 /// </summary>
50 /// <param name="field">Index into record of the field to query.</param>
51 /// <returns>true if the value is null, false otherwise.</returns>
52 public bool IsNull(int field)
53 {
54 int error = MsiInterop.MsiRecordIsNull(this.Handle, field);
55
56 switch (error)
57 {
58 case 0:
59 return false;
60 case 1:
61 return true;
62 default:
63 throw new Win32Exception(error);
64 }
65 }
66
67 /// <summary>
68 /// Gets integer value at specified location.
69 /// </summary>
70 /// <param name="field">Index into record to get integer</param>
71 /// <returns>Integer value</returns>
72 public int GetInteger(int field)
73 {
74 return MsiInterop.MsiRecordGetInteger(this.Handle, field);
75 }
76
77 /// <summary>
78 /// Sets integer value at specified location.
79 /// </summary>
80 /// <param name="field">Index into record to set integer.</param>
81 /// <param name="value">Value to set into record.</param>
82 public void SetInteger(int field, int value)
83 {
84 int error = MsiInterop.MsiRecordSetInteger(this.Handle, field, value);
85 if (0 != error)
86 {
87 throw new Win32Exception(error);
88 }
89 }
90
91 /// <summary>
92 /// Gets string value at specified location.
93 /// </summary>
94 /// <param name="field">Index into record to get string.</param>
95 /// <returns>String value</returns>
96 public string GetString(int field)
97 {
98 int bufferSize = 255;
99 StringBuilder buffer = new StringBuilder(bufferSize);
100 int error = MsiInterop.MsiRecordGetString(this.Handle, field, buffer, ref bufferSize);
101 if (234 == error)
102 {
103 buffer.EnsureCapacity(++bufferSize);
104 error = MsiInterop.MsiRecordGetString(this.Handle, field, buffer, ref bufferSize);
105 }
106
107 if (0 != error)
108 {
109 throw new Win32Exception(error);
110 }
111
112 return (0 < buffer.Length ? buffer.ToString() : null);
113 }
114
115 /// <summary>
116 /// Set string value at specified location
117 /// </summary>
118 /// <param name="field">Index into record to set string.</param>
119 /// <param name="value">Value to set into record</param>
120 public void SetString(int field, string value)
121 {
122 int error = MsiInterop.MsiRecordSetString(this.Handle, field, value);
123 if (0 != error)
124 {
125 throw new Win32Exception(error);
126 }
127 }
128
129 /// <summary>
130 /// Get stream at specified location.
131 /// </summary>
132 /// <param name="field">Index into record to get stream.</param>
133 /// <param name="buffer">buffer to receive bytes from stream.</param>
134 /// <param name="requestedBufferSize">Buffer size to read.</param>
135 /// <returns>Stream read into string.</returns>
136 public int GetStream(int field, byte[] buffer, int requestedBufferSize)
137 {
138 int bufferSize = 255;
139 if (requestedBufferSize > 0)
140 {
141 bufferSize = requestedBufferSize;
142 }
143
144 int error = MsiInterop.MsiRecordReadStream(this.Handle, field, buffer, ref bufferSize);
145 if (0 != error)
146 {
147 throw new Win32Exception(error);
148 }
149
150 return bufferSize;
151 }
152
153 /// <summary>
154 /// Sets a stream at a specified location.
155 /// </summary>
156 /// <param name="field">Index into record to set stream.</param>
157 /// <param name="path">Path to file to read into stream.</param>
158 public void SetStream(int field, string path)
159 {
160 int error = MsiInterop.MsiRecordSetStream(this.Handle, field, path);
161 if (0 != error)
162 {
163 throw new Win32Exception(error);
164 }
165 }
166
167 /// <summary>
168 /// Gets the number of fields in record.
169 /// </summary>
170 /// <returns>Count of fields in record.</returns>
171 public int GetFieldCount()
172 {
173 int size = MsiInterop.MsiRecordGetFieldCount(this.Handle);
174 if (0 > size)
175 {
176 throw new Win32Exception();
177 }
178
179 return size;
180 }
181 }
182}
diff --git a/src/WixToolset.Core/Msi/Session.cs b/src/WixToolset.Core/Msi/Session.cs
new file mode 100644
index 00000000..d3a19711
--- /dev/null
+++ b/src/WixToolset.Core/Msi/Session.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.Msi
4{
5 using System;
6 using System.ComponentModel;
7 using System.Globalization;
8 using WixToolset.Core.Native;
9
10 /// <summary>
11 /// Controls the installation process.
12 /// </summary>
13 internal sealed class Session : MsiHandle
14 {
15 /// <summary>
16 /// Instantiate a new Session.
17 /// </summary>
18 /// <param name="database">The database to open.</param>
19 public Session(Database database)
20 {
21 string packagePath = String.Format(CultureInfo.InvariantCulture, "#{0}", (uint)database.Handle);
22
23 uint handle = 0;
24 int error = MsiInterop.MsiOpenPackage(packagePath, out handle);
25 if (0 != error)
26 {
27 throw new MsiException(error);
28 }
29 this.Handle = handle;
30 }
31
32 /// <summary>
33 /// Executes a built-in action, custom action, or user-interface wizard action.
34 /// </summary>
35 /// <param name="action">Specifies the action to execute.</param>
36 public void DoAction(string action)
37 {
38 int error = MsiInterop.MsiDoAction(this.Handle, action);
39 if (0 != error)
40 {
41 throw new MsiException(error);
42 }
43 }
44 }
45}
diff --git a/src/WixToolset.Core/Msi/SummaryInformation.cs b/src/WixToolset.Core/Msi/SummaryInformation.cs
new file mode 100644
index 00000000..39949db6
--- /dev/null
+++ b/src/WixToolset.Core/Msi/SummaryInformation.cs
@@ -0,0 +1,323 @@
1// Copyright (c) .NET Foundation and 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.Msi
4{
5 using System;
6 using System.ComponentModel;
7 using System.Diagnostics.CodeAnalysis;
8 using System.Globalization;
9 using System.Text;
10 using System.Runtime.InteropServices;
11 using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME;
12 using WixToolset.Core.Native;
13
14 /// <summary>
15 /// Summary information for the MSI files.
16 /// </summary>
17 internal sealed class SummaryInformation : MsiHandle
18 {
19 /// <summary>
20 /// Summary information properties for transforms.
21 /// </summary>
22 public enum Transform
23 {
24 /// <summary>PID_CODEPAGE = code page for the summary information stream</summary>
25 CodePage = 1,
26
27 /// <summary>PID_TITLE = typically just "Transform"</summary>
28 Title = 2,
29
30 /// <summary>PID_SUBJECT = original subject of target</summary>
31 TargetSubject = 3,
32
33 /// <summary>PID_AUTHOR = original manufacturer of target</summary>
34 TargetManufacturer = 4,
35
36 /// <summary>PID_KEYWORDS = keywords for the transform, typically including at least "Installer"</summary>
37 Keywords = 5,
38
39 /// <summary>PID_COMMENTS = describes what this package does</summary>
40 Comments = 6,
41
42 /// <summary>PID_TEMPLATE = target platform;language</summary>
43 TargetPlatformAndLanguage = 7,
44
45 /// <summary>PID_LASTAUTHOR = updated platform;language</summary>
46 UpdatedPlatformAndLanguage = 8,
47
48 /// <summary>PID_REVNUMBER = {productcode}version;{newproductcode}newversion;upgradecode</summary>
49 ProductCodes = 9,
50
51 /// <summary>PID_LASTPRINTED should be null for transforms</summary>
52 Reserved11 = 11,
53
54 ///.<summary>PID_CREATE_DTM = the timestamp when the transform was created</summary>
55 CreationTime = 12,
56
57 /// <summary>PID_PAGECOUNT = minimum installer version</summary>
58 InstallerRequirement = 14,
59
60 /// <summary>PID_CHARCOUNT = validation and error flags</summary>
61 ValidationFlags = 16,
62
63 /// <summary>PID_APPNAME = the application that created the transform</summary>
64 CreatingApplication = 18,
65
66 /// <summary>PID_SECURITY = whether read-only is enforced; should always be 4 for transforms</summary>
67 Security = 19,
68 }
69
70 /// <summary>
71 /// Summary information properties for patches.
72 /// </summary>
73 public enum Patch
74 {
75 /// <summary>PID_CODEPAGE = code page of the summary information stream</summary>
76 CodePage = 1,
77
78 /// <summary>PID_TITLE = a brief description of the package type</summary>
79 Title = 2,
80
81 /// <summary>PID_SUBJECT = package name</summary>
82 PackageName = 3,
83
84 /// <summary>PID_AUTHOR = manufacturer of the patch package</summary>
85 Manufacturer = 4,
86
87 /// <summary>PID_KEYWORDS = alternate sources for the patch package</summary>
88 Sources = 5,
89
90 /// <summary>PID_COMMENTS = general purpose of the patch package</summary>
91 Comments = 6,
92
93 /// <summary>PID_TEMPLATE = semicolon delimited list of ProductCodes</summary>
94 ProductCodes = 7,
95
96 /// <summary>PID_LASTAUTHOR = semicolon delimited list of transform names</summary>
97 TransformNames = 8,
98
99 /// <summary>PID_REVNUMBER = GUID patch code</summary>
100 PatchCode = 9,
101
102 /// <summary>PID_LASTPRINTED should be null for patches</summary>
103 Reserved11 = 11,
104
105 /// <summary>PID_PAGECOUNT should be null for patches</summary>
106 Reserved14 = 14,
107
108 /// <summary>PID_WORDCOUNT = minimum installer version</summary>
109 InstallerRequirement = 15,
110
111 /// <summary>PID_CHARCOUNT should be null for patches</summary>
112 Reserved16 = 16,
113
114 /// <summary>PID_SECURITY = read-only attribute of the patch package</summary>
115 Security = 19,
116 }
117
118 /// <summary>
119 /// Summary information values for the InstallerRequirement property.
120 /// </summary>
121 public enum InstallerRequirement
122 {
123 /// <summary>Any version of the installer will do</summary>
124 Version10 = 1,
125
126 /// <summary>At least 1.2</summary>
127 Version12 = 2,
128
129 /// <summary>At least 2.0</summary>
130 Version20 = 3,
131
132 /// <summary>At least 3.0</summary>
133 Version30 = 4,
134
135 /// <summary>At least 3.1</summary>
136 Version31 = 5,
137 }
138
139 /// <summary>
140 /// Instantiate a new SummaryInformation class from an open database.
141 /// </summary>
142 /// <param name="db">Database to retrieve summary information from.</param>
143 public SummaryInformation(Database db)
144 {
145 if (null == db)
146 {
147 throw new ArgumentNullException("db");
148 }
149
150 uint handle = 0;
151 int error = MsiInterop.MsiGetSummaryInformation(db.Handle, null, 0, ref handle);
152 if (0 != error)
153 {
154 throw new MsiException(error);
155 }
156 this.Handle = handle;
157 }
158
159 /// <summary>
160 /// Instantiate a new SummaryInformation class from a database file.
161 /// </summary>
162 /// <param name="databaseFile">The database file.</param>
163 public SummaryInformation(string databaseFile)
164 {
165 if (null == databaseFile)
166 {
167 throw new ArgumentNullException("databaseFile");
168 }
169
170 uint handle = 0;
171 int error = MsiInterop.MsiGetSummaryInformation(0, databaseFile, 0, ref handle);
172 if (0 != error)
173 {
174 throw new MsiException(error);
175 }
176 this.Handle = handle;
177 }
178
179 /// <summary>
180 /// Variant types in the summary information table.
181 /// </summary>
182 private enum VT : uint
183 {
184 /// <summary>Variant has not been assigned.</summary>
185 EMPTY = 0,
186
187 /// <summary>Null variant type.</summary>
188 NULL = 1,
189
190 /// <summary>16-bit integer variant type.</summary>
191 I2 = 2,
192
193 /// <summary>32-bit integer variant type.</summary>
194 I4 = 3,
195
196 /// <summary>String variant type.</summary>
197 LPSTR = 30,
198
199 /// <summary>Date time (FILETIME, converted to Variant time) variant type.</summary>
200 FILETIME = 64,
201 }
202
203 /// <summary>
204 /// Gets a summary information property.
205 /// </summary>
206 /// <param name="index">Index of the summary information property.</param>
207 /// <returns>The summary information property.</returns>
208 public string GetProperty(int index)
209 {
210 uint dataType;
211 StringBuilder stringValue = new StringBuilder("");
212 int bufSize = 0;
213 int intValue;
214 FILETIME timeValue;
215 timeValue.dwHighDateTime = 0;
216 timeValue.dwLowDateTime = 0;
217
218 int error = MsiInterop.MsiSummaryInfoGetProperty(this.Handle, index, out dataType, out intValue, ref timeValue, stringValue, ref bufSize);
219 if (234 == error)
220 {
221 stringValue.EnsureCapacity(++bufSize);
222 error = MsiInterop.MsiSummaryInfoGetProperty(this.Handle, index, out dataType, out intValue, ref timeValue, stringValue, ref bufSize);
223 }
224
225 if (0 != error)
226 {
227 throw new MsiException(error);
228 }
229
230 switch ((VT)dataType)
231 {
232 case VT.EMPTY:
233 return String.Empty;
234 case VT.LPSTR:
235 return stringValue.ToString();
236 case VT.I2:
237 case VT.I4:
238 return Convert.ToString(intValue, CultureInfo.InvariantCulture);
239 case VT.FILETIME:
240 long longFileTime = (((long)timeValue.dwHighDateTime) << 32) | unchecked((uint)timeValue.dwLowDateTime);
241 DateTime dateTime = DateTime.FromFileTime(longFileTime);
242 return dateTime.ToString("yyyy/MM/dd HH:mm:ss", CultureInfo.InvariantCulture);
243 default:
244 throw new InvalidOperationException();
245 }
246 }
247 }
248
249 /// <summary>
250 /// Summary information values for the CharCount property in transforms.
251 /// </summary>
252 [Flags]
253 [SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")]
254 public enum TransformFlags
255 {
256 /// <summary>Ignore error when adding a row that exists.</summary>
257 ErrorAddExistingRow = 0x1,
258
259 /// <summary>Ignore error when deleting a row that does not exist.</summary>
260 ErrorDeleteMissingRow = 0x2,
261
262 /// <summary>Ignore error when adding a table that exists. </summary>
263 ErrorAddExistingTable = 0x4,
264
265 /// <summary>Ignore error when deleting a table that does not exist. </summary>
266 ErrorDeleteMissingTable = 0x8,
267
268 /// <summary>Ignore error when updating a row that does not exist. </summary>
269 ErrorUpdateMissingRow = 0x10,
270
271 /// <summary>Ignore error when transform and database code pages do not match, and their code pages are neutral.</summary>
272 ErrorChangeCodePage = 0x20,
273
274 /// <summary>Default language must match base database. </summary>
275 ValidateLanguage = 0x10000,
276
277 /// <summary>Product must match base database.</summary>
278 ValidateProduct = 0x20000,
279
280 /// <summary>Check major version only. </summary>
281 ValidateMajorVersion = 0x80000,
282
283 /// <summary>Check major and minor versions only. </summary>
284 ValidateMinorVersion = 0x100000,
285
286 /// <summary>Check major, minor, and update versions.</summary>
287 ValidateUpdateVersion = 0x200000,
288
289 /// <summary>Installed version lt base version. </summary>
290 ValidateNewLessBaseVersion = 0x400000,
291
292 /// <summary>Installed version lte base version. </summary>
293 ValidateNewLessEqualBaseVersion = 0x800000,
294
295 /// <summary>Installed version eq base version. </summary>
296 ValidateNewEqualBaseVersion = 0x1000000,
297
298 /// <summary>Installed version gte base version.</summary>
299 ValidateNewGreaterEqualBaseVersion = 0x2000000,
300
301 /// <summary>Installed version gt base version.</summary>
302 ValidateNewGreaterBaseVersion = 0x4000000,
303
304 /// <summary>UpgradeCode must match base database.</summary>
305 ValidateUpgradeCode = 0x8000000,
306
307 /// <summary>Masks all version checks on ProductVersion.</summary>
308 ProductVersionMask = ValidateMajorVersion | ValidateMinorVersion | ValidateUpdateVersion,
309
310 /// <summary>Masks all operations on ProductVersion.</summary>
311 ProductVersionOperatorMask = ValidateNewLessBaseVersion | ValidateNewLessEqualBaseVersion | ValidateNewEqualBaseVersion | ValidateNewGreaterEqualBaseVersion | ValidateNewGreaterBaseVersion,
312
313 /// <summary>Default value for instance transforms.</summary>
314 InstanceTransformDefault = ErrorAddExistingRow | ErrorDeleteMissingRow | ErrorAddExistingTable | ErrorDeleteMissingTable | ErrorUpdateMissingRow | ErrorChangeCodePage | ValidateProduct | ValidateUpdateVersion | ValidateNewGreaterEqualBaseVersion,
315
316 /// <summary>Default value for language transforms.</summary>
317 LanguageTransformDefault = ErrorAddExistingRow | ErrorDeleteMissingRow | ErrorAddExistingTable | ErrorDeleteMissingTable | ErrorUpdateMissingRow | ErrorChangeCodePage | ValidateProduct,
318
319 /// <summary>Default value for patch transforms.</summary>
320 PatchTransformDefault = ErrorAddExistingRow | ErrorDeleteMissingRow | ErrorAddExistingTable | ErrorDeleteMissingTable | ErrorUpdateMissingRow | ValidateProduct | ValidateUpdateVersion | ValidateNewEqualBaseVersion | ValidateUpgradeCode,
321 }
322
323}
diff --git a/src/WixToolset.Core/Msi/View.cs b/src/WixToolset.Core/Msi/View.cs
new file mode 100644
index 00000000..d6542824
--- /dev/null
+++ b/src/WixToolset.Core/Msi/View.cs
@@ -0,0 +1,189 @@
1// Copyright (c) .NET Foundation and 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.Msi
4{
5 using System;
6 using System.ComponentModel;
7 using System.Globalization;
8 using WixToolset.Core.Native;
9
10 /// <summary>
11 /// Enumeration of different modify modes.
12 /// </summary>
13 public enum ModifyView
14 {
15 /// <summary>
16 /// Writes current data in the cursor to a table row. Updates record if the primary
17 /// keys match an existing row and inserts if they do not match. Fails with a read-only
18 /// database. This mode cannot be used with a view containing joins.
19 /// </summary>
20 Assign = MsiInterop.MSIMODIFYASSIGN,
21
22 /// <summary>
23 /// Remove a row from the table. You must first call the Fetch function with the same
24 /// record. Fails if the row has been deleted. Works only with read-write records. This
25 /// mode cannot be used with a view containing joins.
26 /// </summary>
27 Delete = MsiInterop.MSIMODIFYDELETE,
28
29 /// <summary>
30 /// Inserts a record. Fails if a row with the same primary keys exists. Fails with a read-only
31 /// database. This mode cannot be used with a view containing joins.
32 /// </summary>
33 Insert = MsiInterop.MSIMODIFYINSERT,
34
35 /// <summary>
36 /// Inserts a temporary record. The information is not persistent. Fails if a row with the
37 /// same primary key exists. Works only with read-write records. This mode cannot be
38 /// used with a view containing joins.
39 /// </summary>
40 InsertTemporary = MsiInterop.MSIMODIFYINSERTTEMPORARY,
41
42 /// <summary>
43 /// Inserts or validates a record in a table. Inserts if primary keys do not match any row
44 /// and validates if there is a match. Fails if the record does not match the data in
45 /// the table. Fails if there is a record with a duplicate key that is not identical.
46 /// Works only with read-write records. This mode cannot be used with a view containing joins.
47 /// </summary>
48 Merge = MsiInterop.MSIMODIFYMERGE,
49
50 /// <summary>
51 /// Refreshes the information in the record. Must first call Fetch with the
52 /// same record. Fails for a deleted row. Works with read-write and read-only records.
53 /// </summary>
54 Refresh = MsiInterop.MSIMODIFYREFRESH,
55
56 /// <summary>
57 /// Updates or deletes and inserts a record into a table. Must first call Fetch with
58 /// the same record. Updates record if the primary keys are unchanged. Deletes old row and
59 /// inserts new if primary keys have changed. Fails with a read-only database. This mode cannot
60 /// be used with a view containing joins.
61 /// </summary>
62 Replace = MsiInterop.MSIMODIFYREPLACE,
63
64 /// <summary>
65 /// Refreshes the information in the supplied record without changing the position in the
66 /// result set and without affecting subsequent fetch operations. The record may then
67 /// be used for subsequent Update, Delete, and Refresh. All primary key columns of the
68 /// table must be in the query and the record must have at least as many fields as the
69 /// query. Seek cannot be used with multi-table queries. This mode cannot be used with
70 /// a view containing joins. See also the remarks.
71 /// </summary>
72 Seek = MsiInterop.MSIMODIFYSEEK,
73
74 /// <summary>
75 /// Updates an existing record. Non-primary keys only. Must first call Fetch. Fails with a
76 /// deleted record. Works only with read-write records.
77 /// </summary>
78 Update = MsiInterop.MSIMODIFYUPDATE
79 }
80
81 /// <summary>
82 /// Wrapper class for MSI API views.
83 /// </summary>
84 internal sealed class View : MsiHandle
85 {
86 /// <summary>
87 /// Constructor that creates a view given a database handle and a query.
88 /// </summary>
89 /// <param name="db">Handle to the database to run the query on.</param>
90 /// <param name="query">Query to be executed.</param>
91 public View(Database db, string query)
92 {
93 if (null == db)
94 {
95 throw new ArgumentNullException("db");
96 }
97
98 if (null == query)
99 {
100 throw new ArgumentNullException("query");
101 }
102
103 uint handle = 0;
104
105 int error = MsiInterop.MsiDatabaseOpenView(db.Handle, query, out handle);
106 if (0 != error)
107 {
108 throw new MsiException(error);
109 }
110
111 this.Handle = handle;
112 }
113
114 /// <summary>
115 /// Executes a view with no customizable parameters.
116 /// </summary>
117 public void Execute()
118 {
119 this.Execute(null);
120 }
121
122 /// <summary>
123 /// Executes a query substituing the values from the records into the customizable parameters
124 /// in the view.
125 /// </summary>
126 /// <param name="record">Record containing parameters to be substituded into the view.</param>
127 public void Execute(Record record)
128 {
129 int error = MsiInterop.MsiViewExecute(this.Handle, null == record ? 0 : record.Handle);
130 if (0 != error)
131 {
132 throw new MsiException(error);
133 }
134 }
135
136 /// <summary>
137 /// Fetches the next row in the view.
138 /// </summary>
139 /// <returns>Returns the fetched record; otherwise null.</returns>
140 public Record Fetch()
141 {
142 uint recordHandle;
143
144 int error = MsiInterop.MsiViewFetch(this.Handle, out recordHandle);
145 if (259 == error)
146 {
147 return null;
148 }
149 else if (0 != error)
150 {
151 throw new MsiException(error);
152 }
153
154 return new Record(recordHandle);
155 }
156
157 /// <summary>
158 /// Updates a fetched record.
159 /// </summary>
160 /// <param name="type">Type of modification mode.</param>
161 /// <param name="record">Record to be modified.</param>
162 public void Modify(ModifyView type, Record record)
163 {
164 int error = MsiInterop.MsiViewModify(this.Handle, Convert.ToInt32(type, CultureInfo.InvariantCulture), record.Handle);
165 if (0 != error)
166 {
167 throw new MsiException(error);
168 }
169 }
170
171 /// <summary>
172 /// Returns a record containing column names or definitions.
173 /// </summary>
174 /// <param name="columnType">Specifies a flag indicating what type of information is needed. Either MSICOLINFO_NAMES or MSICOLINFO_TYPES.</param>
175 /// <returns>The record containing information about the column.</returns>
176 public Record GetColumnInfo(int columnType)
177 {
178 uint recordHandle;
179
180 int error = MsiInterop.MsiViewGetColumnInfo(this.Handle, columnType, out recordHandle);
181 if (0 != error)
182 {
183 throw new MsiException(error);
184 }
185
186 return new Record(recordHandle);
187 }
188 }
189}
diff --git a/src/WixToolset.Core/Mutator.cs b/src/WixToolset.Core/Mutator.cs
new file mode 100644
index 00000000..d37815f4
--- /dev/null
+++ b/src/WixToolset.Core/Mutator.cs
@@ -0,0 +1,115 @@
1// Copyright (c) .NET Foundation and 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.Collections;
7 using WixToolset.Extensibility;
8 using Wix = WixToolset.Data.Serialize;
9
10 /// <summary>
11 /// The WiX Toolset mutator.
12 /// </summary>
13 public sealed class Mutator
14 {
15 private SortedList extensions;
16 private string extensionArgument;
17
18 /// <summary>
19 /// Instantiate a new mutator.
20 /// </summary>
21 public Mutator()
22 {
23 this.extensions = new SortedList();
24 }
25
26 /// <summary>
27 /// Gets or sets the harvester core for the extension.
28 /// </summary>
29 /// <value>The harvester core for the extension.</value>
30 public IHarvesterCore Core { get; set; }
31
32 /// <summary>
33 /// Gets or sets the value of the extension argument passed to heat.
34 /// </summary>
35 /// <value>The extension argument.</value>
36 public string ExtensionArgument
37 {
38 get { return this.extensionArgument; }
39 set { this.extensionArgument = value; }
40 }
41
42 /// <summary>
43 /// Adds a mutator extension.
44 /// </summary>
45 /// <param name="mutatorExtension">The mutator extension to add.</param>
46 public void AddExtension(MutatorExtension mutatorExtension)
47 {
48 this.extensions.Add(mutatorExtension.Sequence, mutatorExtension);
49 }
50
51 /// <summary>
52 /// Mutate a WiX document.
53 /// </summary>
54 /// <param name="wix">The Wix document element.</param>
55 /// <returns>true if mutation was successful</returns>
56 public bool Mutate(Wix.Wix wix)
57 {
58 bool encounteredError = false;
59
60 try
61 {
62 foreach (MutatorExtension mutatorExtension in this.extensions.Values)
63 {
64 if (null == mutatorExtension.Core)
65 {
66 mutatorExtension.Core = this.Core;
67 }
68
69 mutatorExtension.Mutate(wix);
70 }
71 }
72 finally
73 {
74 encounteredError = this.Core.EncounteredError;
75 }
76
77 // return the Wix document element only if mutation completed successfully
78 return !encounteredError;
79 }
80
81 /// <summary>
82 /// Mutate a WiX document.
83 /// </summary>
84 /// <param name="wixString">The Wix document as a string.</param>
85 /// <returns>The mutated Wix document as a string if mutation was successful, else null.</returns>
86 public string Mutate(string wixString)
87 {
88 bool encounteredError = false;
89
90 try
91 {
92 foreach (MutatorExtension mutatorExtension in this.extensions.Values)
93 {
94 if (null == mutatorExtension.Core)
95 {
96 mutatorExtension.Core = this.Core;
97 }
98
99 wixString = mutatorExtension.Mutate(wixString);
100
101 if (String.IsNullOrEmpty(wixString) || this.Core.EncounteredError)
102 {
103 break;
104 }
105 }
106 }
107 finally
108 {
109 encounteredError = this.Core.EncounteredError;
110 }
111
112 return encounteredError ? null : wixString;
113 }
114 }
115}
diff --git a/src/WixToolset.Core/Ole32/Storage.cs b/src/WixToolset.Core/Ole32/Storage.cs
new file mode 100644
index 00000000..c6a43bc4
--- /dev/null
+++ b/src/WixToolset.Core/Ole32/Storage.cs
@@ -0,0 +1,437 @@
1// Copyright (c) .NET Foundation and 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.Ole32
4{
5 using System;
6 using System.Runtime.InteropServices;
7 using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME;
8 using STATSTG = System.Runtime.InteropServices.ComTypes.STATSTG;
9
10 /// <summary>
11 /// Specifies the access mode to use when opening, creating, or deleting a storage object.
12 /// </summary>
13 internal enum StorageMode
14 {
15 /// <summary>
16 /// Indicates that the object is read-only, meaning that modifications cannot be made.
17 /// </summary>
18 Read = 0x0,
19
20 /// <summary>
21 /// Enables you to save changes to the object, but does not permit access to its data.
22 /// </summary>
23 Write = 0x1,
24
25 /// <summary>
26 /// Enables access and modification of object data.
27 /// </summary>
28 ReadWrite = 0x2,
29
30 /// <summary>
31 /// Specifies that subsequent openings of the object are not denied read or write access.
32 /// </summary>
33 ShareDenyNone = 0x40,
34
35 /// <summary>
36 /// Prevents others from subsequently opening the object in Read mode.
37 /// </summary>
38 ShareDenyRead = 0x30,
39
40 /// <summary>
41 /// Prevents others from subsequently opening the object for Write or ReadWrite access.
42 /// </summary>
43 ShareDenyWrite = 0x20,
44
45 /// <summary>
46 /// Prevents others from subsequently opening the object in any mode.
47 /// </summary>
48 ShareExclusive = 0x10,
49
50 /// <summary>
51 /// Opens the storage object with exclusive access to the most recently committed version.
52 /// </summary>
53 Priority = 0x40000,
54
55 /// <summary>
56 /// Indicates that an existing storage object or stream should be removed before the new object replaces it.
57 /// </summary>
58 Create = 0x1000,
59 }
60
61 /// <summary>
62 /// Wrapper for the compound storage file APIs.
63 /// </summary>
64 internal sealed class Storage : IDisposable
65 {
66 private bool disposed;
67 private IStorage storage;
68
69 /// <summary>
70 /// Instantiate a new Storage.
71 /// </summary>
72 /// <param name="storage">The native storage interface.</param>
73 private Storage(IStorage storage)
74 {
75 this.storage = storage;
76 }
77
78 /// <summary>
79 /// Storage destructor.
80 /// </summary>
81 ~Storage()
82 {
83 this.Dispose();
84 }
85
86 /// <summary>
87 /// The IEnumSTATSTG interface enumerates an array of STATSTG structures.
88 /// </summary>
89 [ComImport, Guid("0000000d-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
90 public interface IEnumSTATSTG
91 {
92 /// <summary>
93 /// Gets a specified number of STATSTG structures.
94 /// </summary>
95 /// <param name="celt">The number of STATSTG structures requested.</param>
96 /// <param name="rgelt">An array of STATSTG structures returned.</param>
97 /// <param name="pceltFetched">The number of STATSTG structures retrieved in the rgelt parameter.</param>
98 /// <returns>The error code.</returns>
99 [PreserveSig]
100 uint Next(uint celt, [MarshalAs(UnmanagedType.LPArray), Out] STATSTG[] rgelt, out uint pceltFetched);
101
102 /// <summary>
103 /// Skips a specified number of STATSTG structures in the enumeration sequence.
104 /// </summary>
105 /// <param name="celt">The number of STATSTG structures to skip.</param>
106 void Skip(uint celt);
107
108 /// <summary>
109 /// Resets the enumeration sequence to the beginning of the STATSTG structure array.
110 /// </summary>
111 void Reset();
112
113 /// <summary>
114 /// Creates a new enumerator that contains the same enumeration state as the current STATSTG structure enumerator.
115 /// </summary>
116 /// <returns>The cloned IEnumSTATSTG interface.</returns>
117 [return: MarshalAs(UnmanagedType.Interface)]
118 IEnumSTATSTG Clone();
119 }
120
121 /// <summary>
122 /// The IStorage interface supports the creation and management of structured storage objects.
123 /// </summary>
124 [ComImport, Guid("0000000b-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
125 private interface IStorage
126 {
127 /// <summary>
128 /// Creates and opens a stream object with the specified name contained in this storage object.
129 /// </summary>
130 /// <param name="pwcsName">The name of the newly created stream.</param>
131 /// <param name="grfMode">Specifies the access mode to use when opening the newly created stream.</param>
132 /// <param name="reserved1">Reserved for future use; must be zero.</param>
133 /// <param name="reserved2">Reserved for future use; must be zero.</param>
134 /// <param name="ppstm">On return, pointer to the location of the new IStream interface pointer.</param>
135 void CreateStream(string pwcsName, uint grfMode, uint reserved1, uint reserved2, out IStream ppstm);
136
137 /// <summary>
138 /// Opens an existing stream object within this storage object using the specified access permissions in grfMode.
139 /// </summary>
140 /// <param name="pwcsName">The name of the stream to open.</param>
141 /// <param name="reserved1">Reserved for future use; must be NULL.</param>
142 /// <param name="grfMode">Specifies the access mode to be assigned to the open stream.</param>
143 /// <param name="reserved2">Reserved for future use; must be zero.</param>
144 /// <param name="ppstm">A pointer to IStream pointer variable that receives the interface pointer to the newly opened stream object.</param>
145 void OpenStream(string pwcsName, IntPtr reserved1, uint grfMode, uint reserved2, out IStream ppstm);
146
147 /// <summary>
148 /// Creates and opens a new storage object nested within this storage object with the specified name in the specified access mode.
149 /// </summary>
150 /// <param name="pwcsName">The name of the newly created storage object.</param>
151 /// <param name="grfMode">A value that specifies the access mode to use when opening the newly created storage object.</param>
152 /// <param name="reserved1">Reserved for future use; must be zero.</param>
153 /// <param name="reserved2">Reserved for future use; must be zero.</param>
154 /// <param name="ppstg">A pointer, when successful, to the location of the IStorage pointer to the newly created storage object.</param>
155 void CreateStorage(string pwcsName, uint grfMode, uint reserved1, uint reserved2, out IStorage ppstg);
156
157 /// <summary>
158 /// Opens an existing storage object with the specified name in the specified access mode.
159 /// </summary>
160 /// <param name="pwcsName">The name of the storage object to open.</param>
161 /// <param name="pstgPriority">Must be NULL.</param>
162 /// <param name="grfMode">Specifies the access mode to use when opening the storage object.</param>
163 /// <param name="snbExclude">Must be NULL.</param>
164 /// <param name="reserved">Reserved for future use; must be zero.</param>
165 /// <param name="ppstg">When successful, pointer to the location of an IStorage pointer to the opened storage object.</param>
166 void OpenStorage(string pwcsName, IStorage pstgPriority, uint grfMode, IntPtr snbExclude, uint reserved, out IStorage ppstg);
167
168 /// <summary>
169 /// Copies the entire contents of an open storage object to another storage object.
170 /// </summary>
171 /// <param name="ciidExclude">The number of elements in the array pointed to by rgiidExclude.</param>
172 /// <param name="rgiidExclude">An array of interface identifiers (IIDs) that either the caller knows about and does not want
173 /// copied or that the storage object does not support, but whose state the caller will later explicitly copy.</param>
174 /// <param name="snbExclude">A string name block (refer to SNB) that specifies a block of storage or stream objects that are not to be copied to the destination.</param>
175 /// <param name="pstgDest">A pointer to the open storage object into which this storage object is to be copied.</param>
176 void CopyTo(uint ciidExclude, IntPtr rgiidExclude, IntPtr snbExclude, IStorage pstgDest);
177
178 /// <summary>
179 /// Copies or moves a substorage or stream from this storage object to another storage object.
180 /// </summary>
181 /// <param name="pwcsName">The name of the element in this storage object to be moved or copied.</param>
182 /// <param name="pstgDest">IStorage pointer to the destination storage object.</param>
183 /// <param name="pwcsNewName">The new name for the element in its new storage object.</param>
184 /// <param name="grfFlags">Specifies whether the operation should be a move (STGMOVE_MOVE) or a copy (STGMOVE_COPY).</param>
185 void MoveElementTo(string pwcsName, IStorage pstgDest, string pwcsNewName, uint grfFlags);
186
187 /// <summary>
188 /// Reflects changes for a transacted storage object to the parent level.
189 /// </summary>
190 /// <param name="grfCommitFlags">Controls how the changes are committed to the storage object.</param>
191 void Commit(uint grfCommitFlags);
192
193 /// <summary>
194 /// Discards all changes that have been made to the storage object since the last commit operation.
195 /// </summary>
196 void Revert();
197
198 /// <summary>
199 /// Returns an enumerator object that can be used to enumerate the storage and stream objects contained within this storage object.
200 /// </summary>
201 /// <param name="reserved1">Reserved for future use; must be zero.</param>
202 /// <param name="reserved2">Reserved for future use; must be NULL.</param>
203 /// <param name="reserved3">Reserved for future use; must be zero.</param>
204 /// <param name="ppenum">Pointer to IEnumSTATSTG* pointer variable that receives the interface pointer to the new enumerator object.</param>
205 void EnumElements(uint reserved1, IntPtr reserved2, uint reserved3, out IEnumSTATSTG ppenum);
206
207 /// <summary>
208 /// Removes the specified storage or stream from this storage object.
209 /// </summary>
210 /// <param name="pwcsName">The name of the storage or stream to be removed.</param>
211 void DestroyElement(string pwcsName);
212
213 /// <summary>
214 /// Renames the specified storage or stream in this storage object.
215 /// </summary>
216 /// <param name="pwcsOldName">The name of the substorage or stream to be changed.</param>
217 /// <param name="pwcsNewName">The new name for the specified substorage or stream.</param>
218 void RenameElement(string pwcsOldName, string pwcsNewName);
219
220 /// <summary>
221 /// Sets the modification, access, and creation times of the indicated storage element, if supported by the underlying file system.
222 /// </summary>
223 /// <param name="pwcsName">The name of the storage object element whose times are to be modified.</param>
224 /// <param name="pctime">Either the new creation time for the element or NULL if the creation time is not to be modified.</param>
225 /// <param name="patime">Either the new access time for the element or NULL if the access time is not to be modified.</param>
226 /// <param name="pmtime">Either the new modification time for the element or NULL if the modification time is not to be modified.</param>
227 void SetElementTimes(string pwcsName, FILETIME pctime, FILETIME patime, FILETIME pmtime);
228
229 /// <summary>
230 /// Assigns the specified CLSID to this storage object.
231 /// </summary>
232 /// <param name="clsid">The CLSID that is to be associated with the storage object.</param>
233 void SetClass(Guid clsid);
234
235 /// <summary>
236 /// Stores up to 32 bits of state information in this storage object.
237 /// </summary>
238 /// <param name="grfStateBits">Specifies the new values of the bits to set.</param>
239 /// <param name="grfMask">A binary mask indicating which bits in grfStateBits are significant in this call.</param>
240 void SetStateBits(uint grfStateBits, uint grfMask);
241
242 /// <summary>
243 /// Returns the STATSTG structure for this open storage object.
244 /// </summary>
245 /// <param name="pstatstg">On return, pointer to a STATSTG structure where this method places information about the open storage object.</param>
246 /// <param name="grfStatFlag">Specifies that some of the members in the STATSTG structure are not returned, thus saving a memory allocation operation.</param>
247 void Stat(out STATSTG pstatstg, uint grfStatFlag);
248 }
249
250 /// <summary>
251 /// The IStream interface lets you read and write data to stream objects.
252 /// </summary>
253 [ComImport, Guid("0000000c-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
254 private interface IStream
255 {
256 /// <summary>
257 /// Reads a specified number of bytes from the stream object into memory starting at the current seek pointer.
258 /// </summary>
259 /// <param name="pv">A pointer to the buffer which the stream data is read into.</param>
260 /// <param name="cb">The number of bytes of data to read from the stream object.</param>
261 /// <param name="pcbRead">A pointer to a ULONG variable that receives the actual number of bytes read from the stream object.</param>
262 void Read([Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] byte[] pv, int cb, IntPtr pcbRead);
263
264 /// <summary>
265 /// Writes a specified number of bytes into the stream object starting at the current seek pointer.
266 /// </summary>
267 /// <param name="pv">A pointer to the buffer that contains the data that is to be written to the stream.</param>
268 /// <param name="cb">The number of bytes of data to attempt to write into the stream.</param>
269 /// <param name="pcbWritten">A pointer to a ULONG variable where this method writes the actual number of bytes written to the stream object.</param>
270 void Write([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] byte[] pv, int cb, IntPtr pcbWritten);
271
272 /// <summary>
273 /// Changes the seek pointer to a new location relative to the beginning of the stream, the end of the stream, or the current seek pointer.
274 /// </summary>
275 /// <param name="dlibMove">The displacement to be added to the location indicated by the dwOrigin parameter.</param>
276 /// <param name="dwOrigin">The origin for the displacement specified in dlibMove.</param>
277 /// <param name="plibNewPosition">A pointer to the location where this method writes the value of the new seek pointer from the beginning of the stream.</param>
278 void Seek(long dlibMove, int dwOrigin, IntPtr plibNewPosition);
279
280 /// <summary>
281 /// Changes the size of the stream object.
282 /// </summary>
283 /// <param name="libNewSize">Specifies the new size of the stream as a number of bytes.</param>
284 void SetSize(long libNewSize);
285
286 /// <summary>
287 /// Copies a specified number of bytes from the current seek pointer in the stream to the current seek pointer in another stream.
288 /// </summary>
289 /// <param name="pstm">A pointer to the destination stream.</param>
290 /// <param name="cb">The number of bytes to copy from the source stream.</param>
291 /// <param name="pcbRead">A pointer to the location where this method writes the actual number of bytes read from the source.</param>
292 /// <param name="pcbWritten">A pointer to the location where this method writes the actual number of bytes written to the destination.</param>
293 void CopyTo(IStream pstm, long cb, IntPtr pcbRead, IntPtr pcbWritten);
294
295 /// <summary>
296 /// Ensures that any changes made to a stream object open in transacted mode are reflected in the parent storage object.
297 /// </summary>
298 /// <param name="grfCommitFlags">Controls how the changes for the stream object are committed.</param>
299 void Commit(int grfCommitFlags);
300
301 /// <summary>
302 /// Discards all changes that have been made to a transacted stream since the last call to IStream::Commit.
303 /// </summary>
304 void Revert();
305
306 /// <summary>
307 /// Restricts access to a specified range of bytes in the stream.
308 /// </summary>
309 /// <param name="libOffset">Integer that specifies the byte offset for the beginning of the range.</param>
310 /// <param name="cb">Integer that specifies the length of the range, in bytes, to be restricted.</param>
311 /// <param name="dwLockType">Specifies the restrictions being requested on accessing the range.</param>
312 void LockRegion(long libOffset, long cb, int dwLockType);
313
314 /// <summary>
315 /// Removes the access restriction on a range of bytes previously restricted with IStream::LockRegion.
316 /// </summary>
317 /// <param name="libOffset">Specifies the byte offset for the beginning of the range.</param>
318 /// <param name="cb">Specifies, in bytes, the length of the range to be restricted.</param>
319 /// <param name="dwLockType">Specifies the access restrictions previously placed on the range.</param>
320 void UnlockRegion(long libOffset, long cb, int dwLockType);
321
322 /// <summary>
323 /// Retrieves the STATSTG structure for this stream.
324 /// </summary>
325 /// <param name="pstatstg">Pointer to a STATSTG structure where this method places information about this stream object.</param>
326 /// <param name="grfStatFlag">Specifies that this method does not return some of the members in the STATSTG structure, thus saving a memory allocation operation.</param>
327 void Stat(out STATSTG pstatstg, int grfStatFlag);
328
329 /// <summary>
330 /// Creates a new stream object that references the same bytes as the original stream but provides a separate seek pointer to those bytes.
331 /// </summary>
332 /// <param name="ppstm">When successful, pointer to the location of an IStream pointer to the new stream object.</param>
333 void Clone(out IStream ppstm);
334 }
335
336 /// <summary>
337 /// Creates a new compound file storage object.
338 /// </summary>
339 /// <param name="storageFile">The compound file being created.</param>
340 /// <param name="mode">Specifies the access mode to use when opening the new storage object.</param>
341 /// <returns>The created Storage object.</returns>
342 public static Storage CreateDocFile(string storageFile, StorageMode mode)
343 {
344 IStorage storage = NativeMethods.StgCreateDocfile(storageFile, (uint)mode, 0);
345
346 return new Storage(storage);
347 }
348
349 /// <summary>
350 /// Opens an existing root storage object in the file system.
351 /// </summary>
352 /// <param name="storageFile">The file that contains the storage object to open.</param>
353 /// <param name="mode">Specifies the access mode to use to open the storage object.</param>
354 /// <returns>The created Storage object.</returns>
355 public static Storage Open(string storageFile, StorageMode mode)
356 {
357 IStorage storage = NativeMethods.StgOpenStorage(storageFile, IntPtr.Zero, (uint)mode, IntPtr.Zero, 0);
358
359 return new Storage(storage);
360 }
361
362 /// <summary>
363 /// Copies the entire contents of this open storage object into another Storage object.
364 /// </summary>
365 /// <param name="destinationStorage">The destination Storage object.</param>
366 public void CopyTo(Storage destinationStorage)
367 {
368 this.storage.CopyTo(0, IntPtr.Zero, IntPtr.Zero, destinationStorage.storage);
369 }
370
371 /// <summary>
372 /// Opens an existing Storage object with the specified name according to the specified access mode.
373 /// </summary>
374 /// <param name="name">The name of the Storage object.</param>
375 /// <returns>The opened Storage object.</returns>
376 public Storage OpenStorage(string name)
377 {
378 IStorage subStorage;
379
380 this.storage.OpenStorage(name, null, (uint)(StorageMode.Read | StorageMode.ShareExclusive), IntPtr.Zero, 0, out subStorage);
381
382 return new Storage(subStorage);
383 }
384
385 /// <summary>
386 /// Disposes the managed and unmanaged objects in this object.
387 /// </summary>
388 public void Dispose()
389 {
390 if (!this.disposed)
391 {
392 Marshal.ReleaseComObject(this.storage);
393
394 this.disposed = true;
395 }
396
397 GC.SuppressFinalize(this);
398 }
399
400 /// <summary>
401 /// The native methods.
402 /// </summary>
403 private sealed class NativeMethods
404 {
405 /// <summary>
406 /// Protect the constructor since this class only contains static methods.
407 /// </summary>
408 private NativeMethods()
409 {
410 }
411
412 /// <summary>
413 /// Creates a new compound file storage object.
414 /// </summary>
415 /// <param name="pwcsName">The name for the compound file being created.</param>
416 /// <param name="grfMode">Specifies the access mode to use when opening the new storage object.</param>
417 /// <param name="reserved">Reserved for future use; must be zero.</param>
418 /// <returns>A pointer to the location of the IStorage pointer to the new storage object.</returns>
419 [DllImport("ole32.dll", PreserveSig = false)]
420 [return: MarshalAs(UnmanagedType.Interface)]
421 internal static extern IStorage StgCreateDocfile([MarshalAs(UnmanagedType.LPWStr)] string pwcsName, uint grfMode, uint reserved);
422
423 /// <summary>
424 /// Opens an existing root storage object in the file system.
425 /// </summary>
426 /// <param name="pwcsName">The file that contains the storage object to open.</param>
427 /// <param name="pstgPriority">Most often NULL.</param>
428 /// <param name="grfMode">Specifies the access mode to use to open the storage object.</param>
429 /// <param name="snbExclude">If not NULL, pointer to a block of elements in the storage to be excluded as the storage object is opened.</param>
430 /// <param name="reserved">Indicates reserved for future use; must be zero.</param>
431 /// <returns>A pointer to a IStorage* pointer variable that receives the interface pointer to the opened storage.</returns>
432 [DllImport("ole32.dll", PreserveSig = false)]
433 [return: MarshalAs(UnmanagedType.Interface)]
434 internal static extern IStorage StgOpenStorage([MarshalAs(UnmanagedType.LPWStr)] string pwcsName, IntPtr pstgPriority, uint grfMode, IntPtr snbExclude, uint reserved);
435 }
436 }
437}
diff --git a/src/WixToolset.Core/Patch.cs b/src/WixToolset.Core/Patch.cs
new file mode 100644
index 00000000..e3e6c27f
--- /dev/null
+++ b/src/WixToolset.Core/Patch.cs
@@ -0,0 +1,1284 @@
1// Copyright (c) .NET Foundation and 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.Data
4{
5 using System;
6 using System.Collections;
7 using System.Collections.Generic;
8 using System.Diagnostics.CodeAnalysis;
9 using System.Globalization;
10 using WixToolset.Data.Rows;
11 using WixToolset.Extensibility;
12 using WixToolset.Msi;
13 using WixToolset.Core.Native;
14
15 /// <summary>
16 /// Values for the OptimizeCA MsiPatchMetdata property, which indicates whether custom actions can be skipped when applying the patch.
17 /// </summary>
18 [Flags]
19 internal enum OptimizeCA
20 {
21 /// <summary>
22 /// No custom actions are skipped.
23 /// </summary>
24 None = 0,
25
26 /// <summary>
27 /// Skip property (type 51) and directory (type 35) assignment custom actions.
28 /// </summary>
29 SkipAssignment = 1,
30
31 /// <summary>
32 /// Skip immediate custom actions that are not property or directory assignment custom actions.
33 /// </summary>
34 SkipImmediate = 2,
35
36 /// <summary>
37 /// Skip custom actions that run within the script.
38 /// </summary>
39 SkipDeferred = 4,
40 }
41
42 /// <summary>
43 /// Contains output tables and logic for building an MSP package.
44 /// </summary>
45 public class Patch
46 {
47 private List<IInspectorExtension> inspectorExtensions;
48 private Output patch;
49 private TableDefinitionCollection tableDefinitions;
50
51 public Output PatchOutput
52 {
53 get { return this.patch; }
54 }
55
56 public Patch()
57 {
58 this.inspectorExtensions = new List<IInspectorExtension>();
59 this.tableDefinitions = new TableDefinitionCollection(WindowsInstallerStandard.GetTableDefinitions());
60 }
61
62 /// <summary>
63 /// Adds an extension.
64 /// </summary>
65 /// <param name="extension">The extension to add.</param>
66 public void AddExtension(IInspectorExtension extension)
67 {
68 this.inspectorExtensions.Add(extension);
69 }
70
71 public void Load(string patchPath)
72 {
73 this.patch = Output.Load(patchPath, false);
74 }
75
76 /// <summary>
77 /// Include transforms in a patch.
78 /// </summary>
79 /// <param name="transforms">List of transforms to attach.</param>
80 [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "System.InvalidOperationException.#ctor(System.String)")]
81 public void AttachTransforms(List<PatchTransform> transforms)
82 {
83 InspectorCore inspectorCore = new InspectorCore();
84
85 // Track if at least one transform gets attached.
86 bool attachedTransform = false;
87
88 if (transforms == null || transforms.Count == 0)
89 {
90 throw new WixException(WixErrors.PatchWithoutTransforms());
91 }
92
93 // Get the patch id from the WixPatchId table.
94 string patchId = null;
95 string clientPatchId = null;
96 Table wixPatchIdTable = this.patch.Tables["WixPatchId"];
97 if (null != wixPatchIdTable && 0 < wixPatchIdTable.Rows.Count)
98 {
99 Row patchIdRow = wixPatchIdTable.Rows[0];
100 if (null != patchIdRow)
101 {
102 patchId = patchIdRow[0].ToString();
103 clientPatchId = patchIdRow[1].ToString();
104 }
105 }
106
107 if (null == patchId)
108 {
109 throw new WixException(WixErrors.ExpectedPatchIdInWixMsp());
110 }
111 if (null == clientPatchId)
112 {
113 throw new WixException(WixErrors.ExpectedClientPatchIdInWixMsp());
114 }
115
116 // enumerate patch.Media to map diskId to Media row
117 Table patchMediaTable = patch.Tables["Media"];
118
119 if (null == patchMediaTable || patchMediaTable.Rows.Count == 0)
120 {
121 throw new WixException(WixErrors.ExpectedMediaRowsInWixMsp());
122 }
123
124 Hashtable mediaRows = new Hashtable(patchMediaTable.Rows.Count);
125 foreach (MediaRow row in patchMediaTable.Rows)
126 {
127 int media = row.DiskId;
128 mediaRows[media] = row;
129 }
130
131 // enumerate patch.WixPatchBaseline to map baseline to diskId
132 Table patchBaselineTable = patch.Tables["WixPatchBaseline"];
133
134 int numPatchBaselineRows = (null != patchBaselineTable) ? patchBaselineTable.Rows.Count : 0;
135
136 Hashtable baselineMedia = new Hashtable(numPatchBaselineRows);
137 if (patchBaselineTable != null)
138 {
139 foreach (Row row in patchBaselineTable.Rows)
140 {
141 string baseline = (string)row[0];
142 int media = (int)row[1];
143 int validationFlags = (int)row[2];
144 if (baselineMedia.Contains(baseline))
145 {
146 this.OnMessage(WixErrors.SamePatchBaselineId(row.SourceLineNumbers, baseline));
147 }
148 baselineMedia[baseline] = new int[] { media, validationFlags };
149 }
150 }
151
152 // populate MSP summary information
153 Table patchSummaryInfo = patch.EnsureTable(this.tableDefinitions["_SummaryInformation"]);
154
155 // Remove properties that will be calculated or are reserved.
156 for (int i = patchSummaryInfo.Rows.Count - 1; i >= 0; i--)
157 {
158 Row row = patchSummaryInfo.Rows[i];
159 switch ((SummaryInformation.Patch)row[0])
160 {
161 case SummaryInformation.Patch.ProductCodes:
162 case SummaryInformation.Patch.TransformNames:
163 case SummaryInformation.Patch.PatchCode:
164 case SummaryInformation.Patch.InstallerRequirement:
165 case SummaryInformation.Patch.Reserved11:
166 case SummaryInformation.Patch.Reserved14:
167 case SummaryInformation.Patch.Reserved16:
168 patchSummaryInfo.Rows.RemoveAt(i);
169 break;
170 }
171 }
172
173 // Index remaining summary properties.
174 SummaryInfoRowCollection summaryInfo = new SummaryInfoRowCollection(patchSummaryInfo);
175
176 // PID_CODEPAGE
177 if (!summaryInfo.Contains((int)SummaryInformation.Patch.CodePage))
178 {
179 // set the code page by default to the same code page for the
180 // string pool in the database.
181 Row codePage = patchSummaryInfo.CreateRow(null);
182 codePage[0] = (int)SummaryInformation.Patch.CodePage;
183 codePage[1] = this.patch.Codepage.ToString(CultureInfo.InvariantCulture);
184 }
185
186 // GUID patch code for the patch.
187 Row revisionRow = patchSummaryInfo.CreateRow(null);
188 revisionRow[0] = (int)SummaryInformation.Patch.PatchCode;
189 revisionRow[1] = patchId;
190
191 // Indicates the minimum Windows Installer version that is required to install the patch.
192 Row wordsRow = patchSummaryInfo.CreateRow(null);
193 wordsRow[0] = (int)SummaryInformation.Patch.InstallerRequirement;
194 wordsRow[1] = ((int)SummaryInformation.InstallerRequirement.Version31).ToString(CultureInfo.InvariantCulture);
195
196 if (!summaryInfo.Contains((int)SummaryInformation.Patch.Security))
197 {
198 Row security = patchSummaryInfo.CreateRow(null);
199 security[0] = (int)SummaryInformation.Patch.Security;
200 security[1] = "4"; // Read-only enforced
201 }
202
203 // use authored comments or default to DisplayName (required)
204 string comments = null;
205
206 Table msiPatchMetadataTable = patch.Tables["MsiPatchMetadata"];
207 Hashtable metadataTable = new Hashtable();
208 if (null != msiPatchMetadataTable)
209 {
210 foreach (Row row in msiPatchMetadataTable.Rows)
211 {
212 metadataTable.Add(row.Fields[1].Data.ToString(), row.Fields[2].Data.ToString());
213 }
214
215 if (!summaryInfo.Contains((int)SummaryInformation.Patch.Title) && metadataTable.Contains("DisplayName"))
216 {
217 string displayName = (string)metadataTable["DisplayName"];
218
219 Row title = patchSummaryInfo.CreateRow(null);
220 title[0] = (int)SummaryInformation.Patch.Title;
221 title[1] = displayName;
222
223 // default comments use DisplayName as-is (no loc)
224 comments = displayName;
225 }
226
227 if (!summaryInfo.Contains((int)SummaryInformation.Patch.CodePage) && metadataTable.Contains("CodePage"))
228 {
229 Row codePage = patchSummaryInfo.CreateRow(null);
230 codePage[0] = (int)SummaryInformation.Patch.CodePage;
231 codePage[1] = metadataTable["CodePage"];
232 }
233
234 if (!summaryInfo.Contains((int)SummaryInformation.Patch.PackageName) && metadataTable.Contains("Description"))
235 {
236 Row subject = patchSummaryInfo.CreateRow(null);
237 subject[0] = (int)SummaryInformation.Patch.PackageName;
238 subject[1] = metadataTable["Description"];
239 }
240
241 if (!summaryInfo.Contains((int)SummaryInformation.Patch.Manufacturer) && metadataTable.Contains("ManufacturerName"))
242 {
243 Row author = patchSummaryInfo.CreateRow(null);
244 author[0] = (int)SummaryInformation.Patch.Manufacturer;
245 author[1] = metadataTable["ManufacturerName"];
246 }
247 }
248
249 // special metadata marshalled through the build
250 Table wixPatchMetadataTable = patch.Tables["WixPatchMetadata"];
251 Hashtable wixMetadataTable = new Hashtable();
252 if (null != wixPatchMetadataTable)
253 {
254 foreach (Row row in wixPatchMetadataTable.Rows)
255 {
256 wixMetadataTable.Add(row.Fields[0].Data.ToString(), row.Fields[1].Data.ToString());
257 }
258
259 if (wixMetadataTable.Contains("Comments"))
260 {
261 comments = (string)wixMetadataTable["Comments"];
262 }
263 }
264
265 // write the package comments to summary info
266 if (!summaryInfo.Contains((int)SummaryInformation.Patch.Comments) && null != comments)
267 {
268 Row commentsRow = patchSummaryInfo.CreateRow(null);
269 commentsRow[0] = (int)SummaryInformation.Patch.Comments;
270 commentsRow[1] = comments;
271 }
272
273 // enumerate transforms
274 Dictionary<string, object> productCodes = new Dictionary<string, object>();
275 ArrayList transformNames = new ArrayList();
276 ArrayList validTransform = new ArrayList();
277 int transformCount = 0;
278 foreach (PatchTransform mainTransform in transforms)
279 {
280 string baseline = null;
281 int media = -1;
282 int validationFlags = 0;
283
284 if (baselineMedia.Contains(mainTransform.Baseline))
285 {
286 int[] baselineData = (int[])baselineMedia[mainTransform.Baseline];
287 int newMedia = baselineData[0];
288 if (media != -1 && media != newMedia)
289 {
290 throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_TransformAuthoredIntoMultipleMedia, media, newMedia));
291 }
292 baseline = mainTransform.Baseline;
293 media = newMedia;
294 validationFlags = baselineData[1];
295 }
296
297 if (media == -1)
298 {
299 // transform's baseline not attached to any Media
300 continue;
301 }
302
303 Table patchRefTable = patch.Tables["WixPatchRef"];
304 if (patchRefTable != null && patchRefTable.Rows.Count > 0)
305 {
306 if (!Patch.ReduceTransform(mainTransform.Transform, patchRefTable))
307 {
308 // transform has none of the content authored into this patch
309 continue;
310 }
311 }
312
313 // Validate the transform doesn't break any patch specific rules.
314 mainTransform.Validate();
315
316 // ensure consistent File.Sequence within each Media
317 MediaRow mediaRow = (MediaRow)mediaRows[media];
318
319 // Ensure that files are sequenced after the last file in any transform.
320 Table transformMediaTable = mainTransform.Transform.Tables["Media"];
321 if (null != transformMediaTable && 0 < transformMediaTable.Rows.Count)
322 {
323 foreach (MediaRow transformMediaRow in transformMediaTable.Rows)
324 {
325 if (mediaRow.LastSequence < transformMediaRow.LastSequence)
326 {
327 // The Binder will pre-increment the sequence.
328 mediaRow.LastSequence = transformMediaRow.LastSequence;
329 }
330 }
331 }
332
333 // Use the Media/@DiskId if greater for backward compatibility.
334 if (mediaRow.LastSequence < mediaRow.DiskId)
335 {
336 mediaRow.LastSequence = mediaRow.DiskId;
337 }
338
339 // ignore media table from transform.
340 mainTransform.Transform.Tables.Remove("Media");
341 mainTransform.Transform.Tables.Remove("WixMedia");
342 mainTransform.Transform.Tables.Remove("MsiDigitalSignature");
343
344 string productCode;
345 Output pairedTransform = this.BuildPairedTransform(patchId, clientPatchId, mainTransform.Transform, mediaRow, validationFlags, out productCode);
346 productCodes[productCode] = null;
347 DictionaryEntry entry = new DictionaryEntry();
348 entry.Key = productCode;
349 entry.Value = mainTransform.Transform;
350 validTransform.Add(entry);
351
352 // attach these transforms to the patch object
353 // TODO: is this an acceptable way to auto-generate transform stream names?
354 string transformName = baseline + "." + (++transformCount).ToString(CultureInfo.InvariantCulture);
355 patch.SubStorages.Add(new SubStorage(transformName, mainTransform.Transform));
356 patch.SubStorages.Add(new SubStorage("#" + transformName, pairedTransform));
357 transformNames.Add(":" + transformName);
358 transformNames.Add(":#" + transformName);
359 attachedTransform = true;
360 }
361
362 if (!attachedTransform)
363 {
364 throw new WixException(WixErrors.PatchWithoutValidTransforms());
365 }
366
367 // Validate that a patch authored as removable is actually removable
368 if (metadataTable.Contains("AllowRemoval"))
369 {
370 if ("1" == metadataTable["AllowRemoval"].ToString())
371 {
372 ArrayList tables = Patch.GetPatchUninstallBreakingTables();
373 bool result = true;
374 foreach (DictionaryEntry entry in validTransform)
375 {
376 result &= this.CheckUninstallableTransform(entry.Key.ToString(), (Output)entry.Value, tables);
377 }
378
379 if (!result)
380 {
381 throw new WixException(WixErrors.PatchNotRemovable());
382 }
383 }
384 }
385
386 // Finish filling tables with transform-dependent data.
387 // Semicolon delimited list of the product codes that can accept the patch.
388 Table wixPatchTargetTable = patch.Tables["WixPatchTarget"];
389 if (null != wixPatchTargetTable)
390 {
391 Dictionary<string, object> targets = new Dictionary<string, object>();
392 bool replace = true;
393 foreach (Row wixPatchTargetRow in wixPatchTargetTable.Rows)
394 {
395 string target = wixPatchTargetRow[0].ToString();
396 if (0 == String.CompareOrdinal("*", target))
397 {
398 replace = false;
399 }
400 else
401 {
402 targets[target] = null;
403 }
404 }
405
406 // Replace the target ProductCodes with the authored list.
407 if (replace)
408 {
409 productCodes = targets;
410 }
411 else
412 {
413 // Copy the authored target ProductCodes into the list.
414 foreach (string target in targets.Keys)
415 {
416 productCodes[target] = null;
417 }
418 }
419 }
420
421 string[] uniqueProductCodes = new string[productCodes.Keys.Count];
422 productCodes.Keys.CopyTo(uniqueProductCodes, 0);
423
424 Row templateRow = patchSummaryInfo.CreateRow(null);
425 templateRow[0] = (int)SummaryInformation.Patch.ProductCodes;
426 templateRow[1] = String.Join(";", uniqueProductCodes);
427
428 // Semicolon delimited list of transform substorage names in the order they are applied.
429 Row savedbyRow = patchSummaryInfo.CreateRow(null);
430 savedbyRow[0] = (int)SummaryInformation.Patch.TransformNames;
431 savedbyRow[1] = String.Join(";", (string[])transformNames.ToArray(typeof(string)));
432
433 // inspect the patch and filtered transforms
434 foreach (InspectorExtension inspectorExtension in this.inspectorExtensions)
435 {
436 inspectorExtension.Core = inspectorCore;
437 inspectorExtension.InspectOutput(this.patch);
438
439 // reset
440 inspectorExtension.Core = null;
441 }
442 }
443
444 /// <summary>
445 /// Ensure transform is uninstallable.
446 /// </summary>
447 /// <param name="productCode">Product code in transform.</param>
448 /// <param name="transform">Transform generated by torch.</param>
449 /// <param name="tables">Tables to be checked</param>
450 /// <returns>True if the transform is uninstallable</returns>
451 private bool CheckUninstallableTransform(string productCode, Output transform, ArrayList tables)
452 {
453 bool ret = true;
454 foreach (string table in tables)
455 {
456 Table wixTable = transform.Tables[table];
457 if (null != wixTable)
458 {
459 foreach (Row row in wixTable.Rows)
460 {
461 if (row.Operation == RowOperation.Add)
462 {
463 ret = false;
464 string primaryKey = row.GetPrimaryKey('/');
465 if (null == primaryKey)
466 {
467 primaryKey = string.Empty;
468 }
469 this.OnMessage(WixErrors.NewRowAddedInTable(row.SourceLineNumbers, productCode, wixTable.Name, primaryKey));
470 }
471 }
472 }
473 }
474
475 return ret;
476 }
477
478 /// <summary>
479 /// Tables affect patch uninstall.
480 /// </summary>
481 /// <returns>list of tables to be checked</returns>
482 private static ArrayList GetPatchUninstallBreakingTables()
483 {
484 ArrayList tables = new ArrayList();
485 tables.Add("AppId");
486 tables.Add("BindImage");
487 tables.Add("Class");
488 tables.Add("Complus");
489 tables.Add("CreateFolder");
490 tables.Add("DuplicateFile");
491 tables.Add("Environment");
492 tables.Add("Extension");
493 tables.Add("Font");
494 tables.Add("IniFile");
495 tables.Add("IsolatedComponent");
496 tables.Add("LockPermissions");
497 tables.Add("MIME");
498 tables.Add("MoveFile");
499 tables.Add("MsiLockPermissionsEx");
500 tables.Add("MsiServiceConfig");
501 tables.Add("MsiServiceConfigFailureActions");
502 tables.Add("ODBCAttribute");
503 tables.Add("ODBCDataSource");
504 tables.Add("ODBCDriver");
505 tables.Add("ODBCSourceAttribute");
506 tables.Add("ODBCTranslator");
507 tables.Add("ProgId");
508 tables.Add("PublishComponent");
509 tables.Add("RemoveIniFile");
510 tables.Add("SelfReg");
511 tables.Add("ServiceControl");
512 tables.Add("ServiceInstall");
513 tables.Add("TypeLib");
514 tables.Add("Verb");
515
516 return tables;
517 }
518
519 /// <summary>
520 /// Reduce the transform according to the patch references.
521 /// </summary>
522 /// <param name="transform">transform generated by torch.</param>
523 /// <param name="patchRefTable">Table contains patch family filter.</param>
524 /// <returns>true if the transform is not empty</returns>
525 public static bool ReduceTransform(Output transform, Table patchRefTable)
526 {
527 // identify sections to keep
528 Hashtable oldSections = new Hashtable(patchRefTable.Rows.Count);
529 Hashtable newSections = new Hashtable(patchRefTable.Rows.Count);
530 Hashtable tableKeyRows = new Hashtable();
531 ArrayList sequenceList = new ArrayList();
532 Hashtable componentFeatureAddsIndex = new Hashtable();
533 Hashtable customActionTable = new Hashtable();
534 Hashtable directoryTableAdds = new Hashtable();
535 Hashtable featureTableAdds = new Hashtable();
536 Hashtable keptComponents = new Hashtable();
537 Hashtable keptDirectories = new Hashtable();
538 Hashtable keptFeatures = new Hashtable();
539 Hashtable keptLockPermissions = new Hashtable();
540 Hashtable keptMsiLockPermissionExs = new Hashtable();
541
542 Dictionary<string, List<string>> componentCreateFolderIndex = new Dictionary<string, List<string>>();
543 Dictionary<string, List<Row>> directoryLockPermissionsIndex = new Dictionary<string, List<Row>>();
544 Dictionary<string, List<Row>> directoryMsiLockPermissionsExIndex = new Dictionary<string, List<Row>>();
545
546 foreach (Row patchRefRow in patchRefTable.Rows)
547 {
548 string tableName = (string)patchRefRow[0];
549 string key = (string)patchRefRow[1];
550
551 // Short circuit filtering if all changes should be included.
552 if ("*" == tableName && "*" == key)
553 {
554 Patch.RemoveProductCodeFromTransform(transform);
555 return true;
556 }
557
558 Table table = transform.Tables[tableName];
559 if (table == null)
560 {
561 // table not found
562 continue;
563 }
564
565 // index this table
566 if (!tableKeyRows.Contains(tableName))
567 {
568 Hashtable newKeyRows = new Hashtable();
569 foreach (Row newRow in table.Rows)
570 {
571 newKeyRows[newRow.GetPrimaryKey('/')] = newRow;
572 }
573 tableKeyRows[tableName] = newKeyRows;
574 }
575 Hashtable keyRows = (Hashtable)tableKeyRows[tableName];
576
577 Row row = (Row)keyRows[key];
578 if (row == null)
579 {
580 // row not found
581 continue;
582 }
583
584 // Differ.sectionDelimiter
585 string[] sections = row.SectionId.Split('/');
586 oldSections[sections[0]] = row;
587 newSections[sections[1]] = row;
588 }
589
590 // throw away sections not referenced
591 int keptRows = 0;
592 Table directoryTable = null;
593 Table featureTable = null;
594 Table lockPermissionsTable = null;
595 Table msiLockPermissionsTable = null;
596
597 foreach (Table table in transform.Tables)
598 {
599 if ("_SummaryInformation" == table.Name)
600 {
601 continue;
602 }
603
604 if (table.Name == "AdminExecuteSequence"
605 || table.Name == "AdminUISequence"
606 || table.Name == "AdvtExecuteSequence"
607 || table.Name == "InstallUISequence"
608 || table.Name == "InstallExecuteSequence")
609 {
610 sequenceList.Add(table);
611 continue;
612 }
613
614 for (int i = 0; i < table.Rows.Count; i++)
615 {
616 Row row = table.Rows[i];
617
618 if (table.Name == "CreateFolder")
619 {
620 string createFolderComponentId = (string)row[1];
621
622 List<string> directoryList;
623 if (!componentCreateFolderIndex.TryGetValue(createFolderComponentId, out directoryList))
624 {
625 directoryList = new List<string>();
626 componentCreateFolderIndex.Add(createFolderComponentId, directoryList);
627 }
628
629 directoryList.Add((string)row[0]);
630 }
631
632 if (table.Name == "CustomAction")
633 {
634 customActionTable.Add(row[0], row);
635 }
636
637 if (table.Name == "Directory")
638 {
639 directoryTable = table;
640 if (RowOperation.Add == row.Operation)
641 {
642 directoryTableAdds.Add(row[0], row);
643 }
644 }
645
646 if (table.Name == "Feature")
647 {
648 featureTable = table;
649 if (RowOperation.Add == row.Operation)
650 {
651 featureTableAdds.Add(row[0], row);
652 }
653 }
654
655 if (table.Name == "FeatureComponents")
656 {
657 if (RowOperation.Add == row.Operation)
658 {
659 string featureId = (string)row[0];
660 string componentId = (string)row[1];
661
662 if (componentFeatureAddsIndex.ContainsKey(componentId))
663 {
664 ArrayList featureList = (ArrayList)componentFeatureAddsIndex[componentId];
665 featureList.Add(featureId);
666 }
667 else
668 {
669 ArrayList featureList = new ArrayList();
670 componentFeatureAddsIndex.Add(componentId, featureList);
671 featureList.Add(featureId);
672 }
673 }
674 }
675
676 if (table.Name == "LockPermissions")
677 {
678 lockPermissionsTable = table;
679 if ("CreateFolder" == (string)row[1])
680 {
681 string directoryId = (string)row[0];
682
683 List<Row> rowList;
684 if (!directoryLockPermissionsIndex.TryGetValue(directoryId, out rowList))
685 {
686 rowList = new List<Row>();
687 directoryLockPermissionsIndex.Add(directoryId, rowList);
688 }
689
690 rowList.Add(row);
691 }
692 }
693
694 if (table.Name == "MsiLockPermissionsEx")
695 {
696 msiLockPermissionsTable = table;
697 if ("CreateFolder" == (string)row[1])
698 {
699 string directoryId = (string)row[0];
700
701 List<Row> rowList;
702 if (!directoryMsiLockPermissionsExIndex.TryGetValue(directoryId, out rowList))
703 {
704 rowList = new List<Row>();
705 directoryMsiLockPermissionsExIndex.Add(directoryId, rowList);
706 }
707
708 rowList.Add(row);
709 }
710 }
711
712 if (null == row.SectionId)
713 {
714 table.Rows.RemoveAt(i);
715 i--;
716 }
717 else
718 {
719 string[] sections = row.SectionId.Split('/');
720 // ignore the row without section id.
721 if (0 == sections[0].Length && 0 == sections[1].Length)
722 {
723 table.Rows.RemoveAt(i);
724 i--;
725 }
726 else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections))
727 {
728 if ("Component" == table.Name)
729 {
730 keptComponents.Add((string)row[0], row);
731 }
732
733 if ("Directory" == table.Name)
734 {
735 keptDirectories.Add(row[0], row);
736 }
737
738 if ("Feature" == table.Name)
739 {
740 keptFeatures.Add(row[0], row);
741 }
742
743 keptRows++;
744 }
745 else
746 {
747 table.Rows.RemoveAt(i);
748 i--;
749 }
750 }
751 }
752 }
753
754 keptRows += ReduceTransformSequenceTable(sequenceList, oldSections, newSections, customActionTable);
755
756 if (null != directoryTable)
757 {
758 foreach (Row componentRow in keptComponents.Values)
759 {
760 string componentId = (string)componentRow[0];
761
762 if (RowOperation.Add == componentRow.Operation)
763 {
764 // make sure each added component has its required directory and feature heirarchy.
765 string directoryId = (string)componentRow[2];
766 while (null != directoryId && directoryTableAdds.ContainsKey(directoryId))
767 {
768 Row directoryRow = (Row)directoryTableAdds[directoryId];
769
770 if (!keptDirectories.ContainsKey(directoryId))
771 {
772 directoryTable.Rows.Add(directoryRow);
773 keptDirectories.Add(directoryRow[0], null);
774 keptRows++;
775 }
776
777 directoryId = (string)directoryRow[1];
778 }
779
780 if (componentFeatureAddsIndex.ContainsKey(componentId))
781 {
782 foreach (string featureId in (ArrayList)componentFeatureAddsIndex[componentId])
783 {
784 string currentFeatureId = featureId;
785 while (null != currentFeatureId && featureTableAdds.ContainsKey(currentFeatureId))
786 {
787 Row featureRow = (Row)featureTableAdds[currentFeatureId];
788
789 if (!keptFeatures.ContainsKey(currentFeatureId))
790 {
791 featureTable.Rows.Add(featureRow);
792 keptFeatures.Add(featureRow[0], null);
793 keptRows++;
794 }
795
796 currentFeatureId = (string)featureRow[1];
797 }
798 }
799 }
800 }
801
802 // Hook in changes LockPermissions and MsiLockPermissions for folders for each component that has been kept.
803 foreach (string keptComponentId in keptComponents.Keys)
804 {
805 List<string> directoryList;
806 if (componentCreateFolderIndex.TryGetValue(keptComponentId, out directoryList))
807 {
808 foreach (string directoryId in directoryList)
809 {
810 List<Row> lockPermissionsRowList;
811 if (directoryLockPermissionsIndex.TryGetValue(directoryId, out lockPermissionsRowList))
812 {
813 foreach (Row lockPermissionsRow in lockPermissionsRowList)
814 {
815 string key = lockPermissionsRow.GetPrimaryKey('/');
816 if (!keptLockPermissions.ContainsKey(key))
817 {
818 lockPermissionsTable.Rows.Add(lockPermissionsRow);
819 keptLockPermissions.Add(key, null);
820 keptRows++;
821 }
822 }
823 }
824
825 List<Row> msiLockPermissionsExRowList;
826 if (directoryMsiLockPermissionsExIndex.TryGetValue(directoryId, out msiLockPermissionsExRowList))
827 {
828 foreach (Row msiLockPermissionsExRow in msiLockPermissionsExRowList)
829 {
830 string key = msiLockPermissionsExRow.GetPrimaryKey('/');
831 if (!keptMsiLockPermissionExs.ContainsKey(key))
832 {
833 msiLockPermissionsTable.Rows.Add(msiLockPermissionsExRow);
834 keptMsiLockPermissionExs.Add(key, null);
835 keptRows++;
836 }
837 }
838 }
839 }
840 }
841 }
842 }
843 }
844
845 keptRows += ReduceTransformSequenceTable(sequenceList, oldSections, newSections, customActionTable);
846
847 // Delete tables that are empty.
848 ArrayList tablesToDelete = new ArrayList();
849 foreach (Table table in transform.Tables)
850 {
851 if (0 == table.Rows.Count)
852 {
853 tablesToDelete.Add(table.Name);
854 }
855 }
856
857 // delete separately to avoid messing up enumeration
858 foreach (string tableName in tablesToDelete)
859 {
860 transform.Tables.Remove(tableName);
861 }
862
863 return keptRows > 0;
864 }
865
866 /// <summary>
867 /// Remove the ProductCode property from the transform.
868 /// </summary>
869 /// <param name="transform">The transform.</param>
870 /// <remarks>
871 /// Changing the ProductCode is not supported in a patch.
872 /// </remarks>
873 private static void RemoveProductCodeFromTransform(Output transform)
874 {
875 Table propertyTable = transform.Tables["Property"];
876 if (null != propertyTable)
877 {
878 for (int i = 0; i < propertyTable.Rows.Count; ++i)
879 {
880 Row propertyRow = propertyTable.Rows[i];
881 string property = (string)propertyRow[0];
882
883 if ("ProductCode" == property)
884 {
885 propertyTable.Rows.RemoveAt(i);
886 break;
887 }
888 }
889 }
890 }
891
892 /// <summary>
893 /// Check if the section is in a PatchFamily.
894 /// </summary>
895 /// <param name="oldSection">Section id in target wixout</param>
896 /// <param name="newSection">Section id in upgrade wixout</param>
897 /// <param name="oldSections">Hashtable contains section id should be kept in the baseline wixout.</param>
898 /// <param name="newSections">Hashtable contains section id should be kept in the upgrade wixout.</param>
899 /// <returns>true if section in patch family</returns>
900 private static bool IsInPatchFamily(string oldSection, string newSection, Hashtable oldSections, Hashtable newSections)
901 {
902 bool result = false;
903
904 if ((String.IsNullOrEmpty(oldSection) && newSections.Contains(newSection)) || (String.IsNullOrEmpty(newSection) && oldSections.Contains(oldSection)))
905 {
906 result = true;
907 }
908 else if (!String.IsNullOrEmpty(oldSection) && !String.IsNullOrEmpty(newSection) && (oldSections.Contains(oldSection) || newSections.Contains(newSection)))
909 {
910 result = true;
911 }
912
913 return result;
914 }
915
916 /// <summary>
917 /// Reduce the transform sequence tables.
918 /// </summary>
919 /// <param name="sequenceList">ArrayList of tables to be reduced</param>
920 /// <param name="oldSections">Hashtable contains section id should be kept in the baseline wixout.</param>
921 /// <param name="newSections">Hashtable contains section id should be kept in the target wixout.</param>
922 /// <param name="customAction">Hashtable contains all the rows in the CustomAction table.</param>
923 /// <returns>Number of rows left</returns>
924 private static int ReduceTransformSequenceTable(ArrayList sequenceList, Hashtable oldSections, Hashtable newSections, Hashtable customAction)
925 {
926 int keptRows = 0;
927
928 foreach (Table currentTable in sequenceList)
929 {
930 for (int i = 0; i < currentTable.Rows.Count; i++)
931 {
932 Row row = currentTable.Rows[i];
933 string actionName = row.Fields[0].Data.ToString();
934 string[] sections = row.SectionId.Split('/');
935 bool isSectionIdEmpty = (sections[0].Length == 0 && sections[1].Length == 0);
936
937 if (row.Operation == RowOperation.None)
938 {
939 // ignore the rows without section id.
940 if (isSectionIdEmpty)
941 {
942 currentTable.Rows.RemoveAt(i);
943 i--;
944 }
945 else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections))
946 {
947 keptRows++;
948 }
949 else
950 {
951 currentTable.Rows.RemoveAt(i);
952 i--;
953 }
954 }
955 else if (row.Operation == RowOperation.Modify)
956 {
957 bool sequenceChanged = row.Fields[2].Modified;
958 bool conditionChanged = row.Fields[1].Modified;
959
960 if (sequenceChanged && !conditionChanged)
961 {
962 keptRows++;
963 }
964 else if (!sequenceChanged && conditionChanged)
965 {
966 if (isSectionIdEmpty)
967 {
968 currentTable.Rows.RemoveAt(i);
969 i--;
970 }
971 else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections))
972 {
973 keptRows++;
974 }
975 else
976 {
977 currentTable.Rows.RemoveAt(i);
978 i--;
979 }
980 }
981 else if (sequenceChanged && conditionChanged)
982 {
983 if (isSectionIdEmpty)
984 {
985 row.Fields[1].Modified = false;
986 keptRows++;
987 }
988 else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections))
989 {
990 keptRows++;
991 }
992 else
993 {
994 row.Fields[1].Modified = false;
995 keptRows++;
996 }
997 }
998 }
999 else if (row.Operation == RowOperation.Delete)
1000 {
1001 if (isSectionIdEmpty)
1002 {
1003 // it is a stardard action which is added by wix, we should keep this action.
1004 row.Operation = RowOperation.None;
1005 keptRows++;
1006 }
1007 else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections))
1008 {
1009 keptRows++;
1010 }
1011 else
1012 {
1013 if (customAction.ContainsKey(actionName))
1014 {
1015 currentTable.Rows.RemoveAt(i);
1016 i--;
1017 }
1018 else
1019 {
1020 // it is a stardard action, we should keep this action.
1021 row.Operation = RowOperation.None;
1022 keptRows++;
1023 }
1024 }
1025 }
1026 else if (row.Operation == RowOperation.Add)
1027 {
1028 if (isSectionIdEmpty)
1029 {
1030 keptRows++;
1031 }
1032 else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections))
1033 {
1034 keptRows++;
1035 }
1036 else
1037 {
1038 if (customAction.ContainsKey(actionName))
1039 {
1040 currentTable.Rows.RemoveAt(i);
1041 i--;
1042 }
1043 else
1044 {
1045 keptRows++;
1046 }
1047 }
1048 }
1049 }
1050 }
1051
1052 return keptRows;
1053 }
1054
1055 /// <summary>
1056 /// Create the #transform for the given main transform.
1057 /// </summary>
1058 /// <param name="patchId">Patch GUID from patch authoring.</param>
1059 /// <param name="clientPatchId">Easily referenced identity for this patch.</param>
1060 /// <param name="mainTransform">Transform generated by torch.</param>
1061 /// <param name="mediaRow">Media authored into patch.</param>
1062 /// <param name="validationFlags">Transform validation flags for the summary information stream.</param>
1063 /// <param name="productCode">Output string to receive ProductCode.</param>
1064 [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "System.InvalidOperationException.#ctor(System.String)")]
1065 public Output BuildPairedTransform(string patchId, string clientPatchId, Output mainTransform, MediaRow mediaRow, int validationFlags, out string productCode)
1066 {
1067 productCode = null;
1068 Output pairedTransform = new Output(null);
1069 pairedTransform.Type = OutputType.Transform;
1070 pairedTransform.Codepage = mainTransform.Codepage;
1071
1072 // lookup productVersion property to correct summaryInformation
1073 string newProductVersion = null;
1074 Table mainPropertyTable = mainTransform.Tables["Property"];
1075 if (null != mainPropertyTable)
1076 {
1077 foreach (Row row in mainPropertyTable.Rows)
1078 {
1079 if ("ProductVersion" == (string)row[0])
1080 {
1081 newProductVersion = (string)row[1];
1082 }
1083 }
1084 }
1085
1086 // TODO: build class for manipulating SummaryInformation table
1087 Table mainSummaryTable = mainTransform.Tables["_SummaryInformation"];
1088 // add required properties
1089 Hashtable mainSummaryRows = new Hashtable();
1090 foreach (Row mainSummaryRow in mainSummaryTable.Rows)
1091 {
1092 mainSummaryRows[mainSummaryRow[0]] = mainSummaryRow;
1093 }
1094 if (!mainSummaryRows.Contains((int)SummaryInformation.Transform.ValidationFlags))
1095 {
1096 Row mainSummaryRow = mainSummaryTable.CreateRow(null);
1097 mainSummaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags;
1098 mainSummaryRow[1] = validationFlags.ToString(CultureInfo.InvariantCulture);
1099 }
1100
1101 // copy summary information from core transform
1102 Table pairedSummaryTable = pairedTransform.EnsureTable(this.tableDefinitions["_SummaryInformation"]);
1103 foreach (Row mainSummaryRow in mainSummaryTable.Rows)
1104 {
1105 string value = (string)mainSummaryRow[1];
1106 switch ((SummaryInformation.Transform)mainSummaryRow[0])
1107 {
1108 case SummaryInformation.Transform.ProductCodes:
1109 string[] propertyData = value.Split(';');
1110 string oldProductVersion = propertyData[0].Substring(38);
1111 string upgradeCode = propertyData[2];
1112 productCode = propertyData[0].Substring(0, 38);
1113 if (newProductVersion == null)
1114 {
1115 newProductVersion = oldProductVersion;
1116 }
1117
1118 // force mainTranform to old;new;upgrade and pairedTransform to new;new;upgrade
1119 mainSummaryRow[1] = String.Concat(productCode, oldProductVersion, ';', productCode, newProductVersion, ';', upgradeCode);
1120 value = String.Concat(productCode, newProductVersion, ';', productCode, newProductVersion, ';', upgradeCode);
1121 break;
1122 case SummaryInformation.Transform.ValidationFlags:
1123 // use validation flags authored into the patch XML
1124 mainSummaryRow[1] = value = validationFlags.ToString(CultureInfo.InvariantCulture);
1125 break;
1126 }
1127 Row pairedSummaryRow = pairedSummaryTable.CreateRow(null);
1128 pairedSummaryRow[0] = mainSummaryRow[0];
1129 pairedSummaryRow[1] = value;
1130 }
1131
1132 if (productCode == null)
1133 {
1134 throw new InvalidOperationException(WixStrings.EXP_CouldnotDetermineProductCodeFromTransformSummaryInfo);
1135 }
1136
1137 // copy File table
1138 Table mainFileTable = mainTransform.Tables["File"];
1139 if (null != mainFileTable && 0 < mainFileTable.Rows.Count)
1140 {
1141 // We require file source information.
1142 Table mainWixFileTable = mainTransform.Tables["WixFile"];
1143 if (null == mainWixFileTable)
1144 {
1145 throw new WixException(WixErrors.AdminImageRequired(productCode));
1146 }
1147
1148 RowDictionary<FileRow> mainFileRows = new RowDictionary<FileRow>(mainFileTable);
1149
1150 Table pairedFileTable = pairedTransform.EnsureTable(mainFileTable.Definition);
1151 foreach (WixFileRow mainWixFileRow in mainWixFileTable.Rows)
1152 {
1153 FileRow mainFileRow = mainFileRows[mainWixFileRow.File];
1154
1155 // set File.Sequence to non null to satisfy transform bind
1156 mainFileRow.Sequence = 1;
1157
1158 // delete's don't need rows in the paired transform
1159 if (mainFileRow.Operation == RowOperation.Delete)
1160 {
1161 continue;
1162 }
1163
1164 FileRow pairedFileRow = (FileRow)pairedFileTable.CreateRow(null);
1165 pairedFileRow.Operation = RowOperation.Modify;
1166 for (int i = 0; i < mainFileRow.Fields.Length; i++)
1167 {
1168 pairedFileRow[i] = mainFileRow[i];
1169 }
1170
1171 // override authored media for patch bind
1172 mainWixFileRow.DiskId = mediaRow.DiskId;
1173
1174 // suppress any change to File.Sequence to avoid bloat
1175 mainFileRow.Fields[7].Modified = false;
1176
1177 // force File row to appear in the transform
1178 switch (mainFileRow.Operation)
1179 {
1180 case RowOperation.Modify:
1181 case RowOperation.Add:
1182 // set msidbFileAttributesPatchAdded
1183 pairedFileRow.Attributes |= MsiInterop.MsidbFileAttributesPatchAdded;
1184 pairedFileRow.Fields[6].Modified = true;
1185 pairedFileRow.Operation = mainFileRow.Operation;
1186 break;
1187 default:
1188 pairedFileRow.Fields[6].Modified = false;
1189 break;
1190 }
1191 }
1192 }
1193
1194 // add Media row to pairedTransform
1195 Table pairedMediaTable = pairedTransform.EnsureTable(this.tableDefinitions["Media"]);
1196 Row pairedMediaRow = pairedMediaTable.CreateRow(null);
1197 pairedMediaRow.Operation = RowOperation.Add;
1198 for (int i = 0; i < mediaRow.Fields.Length; i++)
1199 {
1200 pairedMediaRow[i] = mediaRow[i];
1201 }
1202
1203 // add PatchPackage for this Media
1204 Table pairedPackageTable = pairedTransform.EnsureTable(this.tableDefinitions["PatchPackage"]);
1205 pairedPackageTable.Operation = TableOperation.Add;
1206 Row pairedPackageRow = pairedPackageTable.CreateRow(null);
1207 pairedPackageRow.Operation = RowOperation.Add;
1208 pairedPackageRow[0] = patchId;
1209 pairedPackageRow[1] = mediaRow.DiskId;
1210
1211 // add property to both identify client patches and whether those patches are removable or not
1212 int allowRemoval = 0;
1213 Table msiPatchMetadataTable = this.patch.Tables["MsiPatchMetadata"];
1214 if (null != msiPatchMetadataTable)
1215 {
1216 foreach (Row msiPatchMetadataRow in msiPatchMetadataTable.Rows)
1217 {
1218 // get the value of the standard AllowRemoval property, if present
1219 string company = (string)msiPatchMetadataRow[0];
1220 if ((null == company || 0 == company.Length) && "AllowRemoval" == (string)msiPatchMetadataRow[1])
1221 {
1222 allowRemoval = Int32.Parse((string)msiPatchMetadataRow[2], CultureInfo.InvariantCulture);
1223 }
1224 }
1225 }
1226
1227 // add the property to the patch transform's Property table
1228 Table pairedPropertyTable = pairedTransform.EnsureTable(this.tableDefinitions["Property"]);
1229 pairedPropertyTable.Operation = TableOperation.Add;
1230 Row pairedPropertyRow = pairedPropertyTable.CreateRow(null);
1231 pairedPropertyRow.Operation = RowOperation.Add;
1232 pairedPropertyRow[0] = string.Concat(clientPatchId, ".AllowRemoval");
1233 pairedPropertyRow[1] = allowRemoval.ToString(CultureInfo.InvariantCulture);
1234
1235 // add this patch code GUID to the patch transform to identify
1236 // which patches are installed, including in multi-patch
1237 // installations.
1238 pairedPropertyRow = pairedPropertyTable.CreateRow(null);
1239 pairedPropertyRow.Operation = RowOperation.Add;
1240 pairedPropertyRow[0] = string.Concat(clientPatchId, ".PatchCode");
1241 pairedPropertyRow[1] = patchId;
1242
1243 // add PATCHNEWPACKAGECODE to apply to admin layouts
1244 pairedPropertyRow = pairedPropertyTable.CreateRow(null);
1245 pairedPropertyRow.Operation = RowOperation.Add;
1246 pairedPropertyRow[0] = "PATCHNEWPACKAGECODE";
1247 pairedPropertyRow[1] = patchId;
1248
1249 // add PATCHNEWSUMMARYCOMMENTS and PATCHNEWSUMMARYSUBJECT to apply to admin layouts
1250 Table _summaryInformationTable = this.patch.Tables["_SummaryInformation"];
1251 if (null != _summaryInformationTable)
1252 {
1253 foreach (Row row in _summaryInformationTable.Rows)
1254 {
1255 if (3 == (int)row[0]) // PID_SUBJECT
1256 {
1257 pairedPropertyRow = pairedPropertyTable.CreateRow(null);
1258 pairedPropertyRow.Operation = RowOperation.Add;
1259 pairedPropertyRow[0] = "PATCHNEWSUMMARYSUBJECT";
1260 pairedPropertyRow[1] = row[1];
1261 }
1262 else if (6 == (int)row[0]) // PID_COMMENTS
1263 {
1264 pairedPropertyRow = pairedPropertyTable.CreateRow(null);
1265 pairedPropertyRow.Operation = RowOperation.Add;
1266 pairedPropertyRow[0] = "PATCHNEWSUMMARYCOMMENTS";
1267 pairedPropertyRow[1] = row[1];
1268 }
1269 }
1270 }
1271
1272 return pairedTransform;
1273 }
1274
1275 /// <summary>
1276 /// Sends a message to the message delegate if there is one.
1277 /// </summary>
1278 /// <param name="mea">Message event arguments.</param>
1279 public void OnMessage(MessageEventArgs mea)
1280 {
1281 Messaging.Instance.OnMessage(mea);
1282 }
1283 }
1284}
diff --git a/src/WixToolset.Core/PatchAPI/PatchInterop.cs b/src/WixToolset.Core/PatchAPI/PatchInterop.cs
new file mode 100644
index 00000000..ce749a33
--- /dev/null
+++ b/src/WixToolset.Core/PatchAPI/PatchInterop.cs
@@ -0,0 +1,1002 @@
1// Copyright (c) .NET Foundation and 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.PatchAPI
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics.CodeAnalysis;
8 using System.Globalization;
9 using System.Runtime.InteropServices;
10
11 /// <summary>
12 /// Interop class for the mspatchc.dll.
13 /// </summary>
14 internal static class PatchInterop
15 {
16 // From WinError.h in the Platform SDK
17 internal const ushort FACILITY_WIN32 = 7;
18
19 /// <summary>
20 /// Parse a number from text in either hex or decimal.
21 /// </summary>
22 /// <param name="source">Source value. Treated as hex if it starts 0x (or 0X), decimal otherwise.</param>
23 /// <returns>Numeric value that source represents.</returns>
24 static internal UInt32 ParseHexOrDecimal(string source)
25 {
26 string value = source.Trim();
27 if (String.Equals(value.Substring(0,2), "0x", StringComparison.OrdinalIgnoreCase))
28 {
29 return UInt32.Parse(value.Substring(2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture.NumberFormat);
30 }
31 else
32 {
33 return UInt32.Parse(value, CultureInfo.InvariantCulture.NumberFormat);
34 }
35 }
36
37 /// <summary>
38 /// Create a binary delta file.
39 /// </summary>
40 /// <param name="deltaFile">Name of the delta file to create.</param>
41 /// <param name="targetFile">Name of updated file.</param>
42 /// <param name="targetSymbolPath">Optional paths to updated file's symbols.</param>
43 /// <param name="targetRetainOffsets">Optional offsets to the delta retain sections in the updated file.</param>
44 /// <param name="basisFiles">Optional array of target files.</param>
45 /// <param name="basisSymbolPaths">Optional array of target files' symbol paths (must match basisFiles array).</param>
46 /// <param name="basisIgnoreLengths">Optional array of target files' delta ignore section lengths (must match basisFiles array)(each entry must match basisIgnoreOffsets entries).</param>
47 /// <param name="basisIgnoreOffsets">Optional array of target files' delta ignore section offsets (must match basisFiles array)(each entry must match basisIgnoreLengths entries).</param>
48 /// <param name="basisRetainLengths">Optional array of target files' delta protect section lengths (must match basisFiles array)(each entry must match basisRetainOffsets and targetRetainOffsets entries).</param>
49 /// <param name="basisRetainOffsets">Optional array of target files' delta protect section offsets (must match basisFiles array)(each entry must match basisRetainLengths and targetRetainOffsets entries).</param>
50 /// <param name="apiPatchingSymbolFlags">ApiPatchingSymbolFlags value.</param>
51 /// <param name="optimizePatchSizeForLargeFiles">OptimizePatchSizeForLargeFiles value.</param>
52 /// <param name="retainRangesIgnored">Flag to indicate retain ranges were ignored due to mismatch.</param>
53 /// <returns>true if delta file was created, false if whole file should be used instead.</returns>
54 static public bool CreateDelta(
55 string deltaFile,
56 string targetFile,
57 string targetSymbolPath,
58 string targetRetainOffsets,
59 string[] basisFiles,
60 string[] basisSymbolPaths,
61 string[] basisIgnoreLengths,
62 string[] basisIgnoreOffsets,
63 string[] basisRetainLengths,
64 string[] basisRetainOffsets,
65 PatchSymbolFlagsType apiPatchingSymbolFlags,
66 bool optimizePatchSizeForLargeFiles,
67 out bool retainRangesIgnored
68 )
69 {
70 retainRangesIgnored = false;
71 if (0 != (apiPatchingSymbolFlags & ~(PatchSymbolFlagsType.PATCH_SYMBOL_NO_IMAGEHLP | PatchSymbolFlagsType.PATCH_SYMBOL_NO_FAILURES | PatchSymbolFlagsType.PATCH_SYMBOL_UNDECORATED_TOO)))
72 {
73 throw new ArgumentOutOfRangeException("apiPatchingSymbolFlags");
74 }
75
76 if (null == deltaFile || 0 == deltaFile.Length)
77 {
78 throw new ArgumentNullException("deltaFile");
79 }
80
81 if (null == targetFile || 0 == targetFile.Length)
82 {
83 throw new ArgumentNullException("targetFile");
84 }
85
86 if (null == basisFiles || 0 == basisFiles.Length)
87 {
88 return false;
89 }
90 uint countOldFiles = (uint) basisFiles.Length;
91
92 if (null != basisSymbolPaths)
93 {
94 if (0 != basisSymbolPaths.Length)
95 {
96 if ((uint) basisSymbolPaths.Length != countOldFiles)
97 {
98 throw new ArgumentOutOfRangeException("basisSymbolPaths");
99 }
100 }
101 }
102 // a null basisSymbolPaths is allowed.
103
104 if (null != basisIgnoreLengths)
105 {
106 if (0 != basisIgnoreLengths.Length)
107 {
108 if ((uint) basisIgnoreLengths.Length != countOldFiles)
109 {
110 throw new ArgumentOutOfRangeException("basisIgnoreLengths");
111 }
112 }
113 }
114 else
115 {
116 basisIgnoreLengths = new string[countOldFiles];
117 }
118
119 if (null != basisIgnoreOffsets)
120 {
121 if (0 != basisIgnoreOffsets.Length)
122 {
123 if ((uint) basisIgnoreOffsets.Length != countOldFiles)
124 {
125 throw new ArgumentOutOfRangeException("basisIgnoreOffsets");
126 }
127 }
128 }
129 else
130 {
131 basisIgnoreOffsets = new string[countOldFiles];
132 }
133
134 if (null != basisRetainLengths)
135 {
136 if (0 != basisRetainLengths.Length)
137 {
138 if ((uint) basisRetainLengths.Length != countOldFiles)
139 {
140 throw new ArgumentOutOfRangeException("basisRetainLengths");
141 }
142 }
143 }
144 else
145 {
146 basisRetainLengths = new string[countOldFiles];
147 }
148
149 if (null != basisRetainOffsets)
150 {
151 if (0 != basisRetainOffsets.Length)
152 {
153 if ((uint) basisRetainOffsets.Length != countOldFiles)
154 {
155 throw new ArgumentOutOfRangeException("basisRetainOffsets");
156 }
157 }
158 }
159 else
160 {
161 basisRetainOffsets = new string[countOldFiles];
162 }
163
164 PatchOptionData pod = new PatchOptionData();
165 pod.symbolOptionFlags = apiPatchingSymbolFlags;
166 pod.newFileSymbolPath = targetSymbolPath;
167 pod.oldFileSymbolPathArray = basisSymbolPaths;
168 pod.extendedOptionFlags = 0;
169 PatchOldFileInfoW[] oldFileInfoArray = new PatchOldFileInfoW[countOldFiles];
170 string[] newRetainOffsetArray = ((null == targetRetainOffsets) ? new string[0] : targetRetainOffsets.Split(','));
171 for (uint i = 0; i < countOldFiles; ++i)
172 {
173 PatchOldFileInfoW ofi = new PatchOldFileInfoW();
174 ofi.oldFileName = basisFiles[i];
175 string[] ignoreLengthArray = ((null == basisIgnoreLengths[i]) ? new string[0] : basisIgnoreLengths[i].Split(','));
176 string[] ignoreOffsetArray = ((null == basisIgnoreOffsets[i]) ? new string[0] : basisIgnoreOffsets[i].Split(','));
177 string[] retainLengthArray = ((null == basisRetainLengths[i]) ? new string[0] : basisRetainLengths[i].Split(','));
178 string[] retainOffsetArray = ((null == basisRetainOffsets[i]) ? new string[0] : basisRetainOffsets[i].Split(','));
179 // Validate inputs
180 if (ignoreLengthArray.Length != ignoreOffsetArray.Length)
181 {
182 throw new ArgumentOutOfRangeException("basisIgnoreLengths");
183 }
184
185 if (retainLengthArray.Length != retainOffsetArray.Length)
186 {
187 throw new ArgumentOutOfRangeException("basisRetainLengths");
188 }
189
190 if (newRetainOffsetArray.Length != retainOffsetArray.Length)
191 {
192 // remove all retain range information
193 retainRangesIgnored = true;
194 for (uint j = 0; j < countOldFiles; ++j)
195 {
196 basisRetainLengths[j] = null;
197 basisRetainOffsets[j] = null;
198 }
199 retainLengthArray = new string[0];
200 retainOffsetArray = new string[0];
201 newRetainOffsetArray = new string[0];
202 for (uint j = 0; j < oldFileInfoArray.Length; ++j)
203 {
204 oldFileInfoArray[j].retainRange = null;
205 }
206 }
207
208 // Populate IgnoreRange structure
209 PatchIgnoreRange[] ignoreArray = null;
210 if (0 != ignoreLengthArray.Length)
211 {
212 ignoreArray = new PatchIgnoreRange[ignoreLengthArray.Length];
213 for (int j = 0; j < ignoreLengthArray.Length; ++j)
214 {
215 PatchIgnoreRange ignoreRange = new PatchIgnoreRange();
216 ignoreRange.offsetInOldFile = ParseHexOrDecimal(ignoreOffsetArray[j]);
217 ignoreRange.lengthInBytes = ParseHexOrDecimal(ignoreLengthArray[j]);
218 ignoreArray[j] = ignoreRange;
219 }
220 ofi.ignoreRange = ignoreArray;
221 }
222
223 PatchRetainRange[] retainArray = null;
224 if (0 != newRetainOffsetArray.Length)
225 {
226 retainArray = new PatchRetainRange[retainLengthArray.Length];
227 for (int j = 0; j < newRetainOffsetArray.Length; ++j)
228 {
229 PatchRetainRange retainRange = new PatchRetainRange();
230 retainRange.offsetInOldFile = ParseHexOrDecimal(retainOffsetArray[j]);
231 retainRange.lengthInBytes = ParseHexOrDecimal(retainLengthArray[j]);
232 retainRange.offsetInNewFile = ParseHexOrDecimal(newRetainOffsetArray[j]);
233 retainArray[j] = retainRange;
234 }
235 ofi.retainRange = retainArray;
236 }
237 oldFileInfoArray[i] = ofi;
238 }
239
240 if (CreatePatchFileExW(
241 countOldFiles,
242 oldFileInfoArray,
243 targetFile,
244 deltaFile,
245 PatchOptionFlags(optimizePatchSizeForLargeFiles),
246 pod,
247 null,
248 IntPtr.Zero))
249 {
250 return true;
251 }
252
253 // determine if this is an error or a need to use whole file.
254 int err = Marshal.GetLastWin32Error();
255 switch(err)
256 {
257 case unchecked((int) ERROR_PATCH_BIGGER_THAN_COMPRESSED):
258 break;
259
260 // too late to exclude this file -- should have been caught before
261 case unchecked((int) ERROR_PATCH_SAME_FILE):
262 default:
263 throw new System.ComponentModel.Win32Exception(err);
264 }
265 return false;
266 }
267
268 /// <summary>
269 /// Extract the delta header.
270 /// </summary>
271 /// <param name="delta">Name of delta file.</param>
272 /// <param name="deltaHeader">Name of file to create with the delta's header.</param>
273 static public void ExtractDeltaHeader(string delta, string deltaHeader)
274 {
275 if (!ExtractPatchHeaderToFileW(delta, deltaHeader))
276 {
277 throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
278 }
279 }
280
281 /// <summary>
282 /// Returns the PatchOptionFlags to use.
283 /// </summary>
284 /// <param name="optimizeForLargeFiles">True if optimizing for large files.</param>
285 /// <returns>PATCH_OPTION_FLAG values</returns>
286 static private UInt32 PatchOptionFlags(bool optimizeForLargeFiles)
287 {
288 UInt32 flags = PATCH_OPTION_FAIL_IF_SAME_FILE | PATCH_OPTION_FAIL_IF_BIGGER | PATCH_OPTION_USE_LZX_BEST;
289 if (optimizeForLargeFiles)
290 {
291 flags |= PATCH_OPTION_USE_LZX_LARGE;
292 }
293 return flags;
294 }
295
296 //---------------------------------------------------------------------
297 // From PatchApi.h
298 //---------------------------------------------------------------------
299
300 //
301 // The following contants can be combined and used as the OptionFlags
302 // parameter in the patch creation apis.
303
304 internal const uint PATCH_OPTION_USE_BEST = 0x00000000; // auto choose best (slower)
305
306 internal const uint PATCH_OPTION_USE_LZX_BEST = 0x00000003; // auto choose best of LXZ A/B (but not large)
307 internal const uint PATCH_OPTION_USE_LZX_A = 0x00000001; // normal
308 internal const uint PATCH_OPTION_USE_LXZ_B = 0x00000002; // better on some x86 binaries
309 internal const uint PATCH_OPTION_USE_LZX_LARGE = 0x00000004; // better support for large files (requires 5.1 or higher applyer)
310
311 internal const uint PATCH_OPTION_NO_BINDFIX = 0x00010000; // PE bound imports
312 internal const uint PATCH_OPTION_NO_LOCKFIX = 0x00020000; // PE smashed locks
313 internal const uint PATCH_OPTION_NO_REBASE = 0x00040000; // PE rebased image
314 internal const uint PATCH_OPTION_FAIL_IF_SAME_FILE = 0x00080000; // don't create if same
315 internal const uint PATCH_OPTION_FAIL_IF_BIGGER = 0x00100000; // fail if patch is larger than simply compressing new file (slower)
316 internal const uint PATCH_OPTION_NO_CHECKSUM = 0x00200000; // PE checksum zero
317 internal const uint PATCH_OPTION_NO_RESTIMEFIX = 0x00400000; // PE resource timestamps
318 internal const uint PATCH_OPTION_NO_TIMESTAMP = 0x00800000; // don't store new file timestamp in patch
319 internal const uint PATCH_OPTION_SIGNATURE_MD5 = 0x01000000; // use MD5 instead of CRC (reserved for future support)
320 internal const uint PATCH_OPTION_INTERLEAVE_FILES = 0x40000000; // better support for large files (requires 5.2 or higher applyer)
321 internal const uint PATCH_OPTION_RESERVED1 = 0x80000000; // (used internally)
322
323 internal const uint PATCH_OPTION_VALID_FLAGS = 0xC0FF0007;
324
325 //
326 // The following flags are used with PATCH_OPTION_DATA SymbolOptionFlags:
327 //
328
329 [Flags]
330 public enum PatchSymbolFlagsType :uint
331 {
332 PATCH_SYMBOL_NO_IMAGEHLP = 0x00000001, // don't use imagehlp.dll
333 PATCH_SYMBOL_NO_FAILURES = 0x00000002, // don't fail patch due to imagehlp failures
334 PATCH_SYMBOL_UNDECORATED_TOO = 0x00000004, // after matching decorated symbols, try to match remaining by undecorated names
335 PATCH_SYMBOL_RESERVED1 = 0x80000000, // (used internally)
336 MaxValue = PATCH_SYMBOL_NO_IMAGEHLP | PATCH_SYMBOL_NO_FAILURES | PATCH_SYMBOL_UNDECORATED_TOO
337 }
338
339 //
340 // The following flags are used with PATCH_OPTION_DATA ExtendedOptionFlags:
341 //
342
343 internal const uint PATCH_TRANSFORM_PE_RESOURCE_2 = 0x00000100; // better handling of PE resources (requires 5.2 or higher applyer)
344 internal const uint PATCH_TRANSFORM_PE_IRELOC_2 = 0x00000200; // better handling of PE stripped relocs (requires 5.2 or higher applyer)
345
346 //
347 // In addition to the standard Win32 error codes, the following error codes may
348 // be returned via GetLastError() when one of the patch APIs fails.
349
350 internal const uint ERROR_PATCH_ENCODE_FAILURE = 0xC00E3101; // create
351 internal const uint ERROR_PATCH_INVALID_OPTIONS = 0xC00E3102; // create
352 internal const uint ERROR_PATCH_SAME_FILE = 0xC00E3103; // create
353 internal const uint ERROR_PATCH_RETAIN_RANGES_DIFFER = 0xC00E3104; // create
354 internal const uint ERROR_PATCH_BIGGER_THAN_COMPRESSED = 0xC00E3105; // create
355 internal const uint ERROR_PATCH_IMAGEHLP_FALURE = 0xC00E3106; // create
356
357 /// <summary>
358 /// Delegate type that the PatchAPI calls for progress notification.
359 /// </summary>
360 /// <param name="context">.</param>
361 /// <param name="currentPosition">.</param>
362 /// <param name="maxPosition">.</param>
363 /// <returns>True for success</returns>
364 public delegate bool PatchProgressCallback(
365 IntPtr context,
366 uint currentPosition,
367 uint maxPosition
368 );
369
370 /// <summary>
371 /// Delegate type that the PatchAPI calls for patch symbol load information.
372 /// </summary>
373 /// <param name="whichFile">.</param>
374 /// <param name="symbolFileName">.</param>
375 /// <param name="symType">.</param>
376 /// <param name="symbolFileCheckSum">.</param>
377 /// <param name="symbolFileTimeDate">.</param>
378 /// <param name="imageFileCheckSum">.</param>
379 /// <param name="imageFileTimeDate">.</param>
380 /// <param name="context">.</param>
381 /// <returns>???</returns>
382 public delegate bool PatchSymloadCallback(
383 uint whichFile, // 0 for new file, 1 for first old file, etc
384 [MarshalAs(UnmanagedType.LPStr)] string symbolFileName,
385 uint symType, // see SYM_TYPE in imagehlp.h
386 uint symbolFileCheckSum,
387 uint symbolFileTimeDate,
388 uint imageFileCheckSum,
389 uint imageFileTimeDate,
390 IntPtr context
391 );
392
393 /// <summary>
394 /// Wraps PATCH_IGNORE_RANGE
395 /// </summary>
396 [StructLayout(LayoutKind.Sequential)]
397 internal class PatchIgnoreRange
398 {
399 public uint offsetInOldFile;
400 public uint lengthInBytes;
401 }
402
403 /// <summary>
404 /// Wraps PATCH_RETAIN_RANGE
405 /// </summary>
406 [StructLayout(LayoutKind.Sequential)]
407 internal class PatchRetainRange
408 {
409 public uint offsetInOldFile;
410 public uint lengthInBytes;
411 public uint offsetInNewFile;
412 }
413
414 /// <summary>
415 /// Wraps PATCH_OLD_FILE_INFO (except for the OldFile~ portion)
416 /// </summary>
417 internal class PatchOldFileInfo
418 {
419 public PatchIgnoreRange[] ignoreRange;
420 public PatchRetainRange[] retainRange;
421 }
422
423 /// <summary>
424 /// Wraps PATCH_OLD_FILE_INFO_W
425 /// </summary>
426 internal class PatchOldFileInfoW : PatchOldFileInfo
427 {
428 public string oldFileName;
429 }
430
431 /// <summary>
432 /// Wraps each PATCH_INTERLEAVE_MAP Range
433 /// </summary>
434 [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses"), StructLayout(LayoutKind.Sequential)]
435 internal class PatchInterleaveMapRange
436 {
437 public uint oldOffset;
438 public uint oldLength;
439 public uint newLength;
440 }
441
442 /// <summary>
443 /// Wraps PATCH_INTERLEAVE_MAP
444 /// </summary>
445 internal class PatchInterleaveMap
446 {
447 public PatchInterleaveMapRange[] ranges = null;
448 }
449
450
451 /// <summary>
452 /// Wraps PATCH_OPTION_DATA
453 /// </summary>
454 [BestFitMapping(false, ThrowOnUnmappableChar = true)]
455 internal class PatchOptionData
456 {
457 public PatchSymbolFlagsType symbolOptionFlags; // PATCH_SYMBOL_xxx flags
458 [MarshalAs(UnmanagedType.LPStr)] public string newFileSymbolPath; // always ANSI, never Unicode
459 [MarshalAs(UnmanagedType.LPStr)] public string[] oldFileSymbolPathArray; // array[ OldFileCount ]
460 public uint extendedOptionFlags;
461 public PatchSymloadCallback symLoadCallback = null;
462 public IntPtr symLoadContext = IntPtr.Zero;
463 public PatchInterleaveMap[] interleaveMapArray = null; // array[ OldFileCount ] (requires 5.2 or higher applyer)
464 public uint maxLzxWindowSize = 0; // limit memory requirements (requires 5.2 or higher applyer)
465 }
466
467 //
468 // Note that PATCH_OPTION_DATA contains LPCSTR paths, and no LPCWSTR (Unicode)
469 // path argument is available, even when used with one of the Unicode APIs
470 // such as CreatePatchFileW. This is because the unlerlying system services
471 // for symbol file handling (IMAGEHLP.DLL) only support ANSI file/path names.
472 //
473
474 //
475 // A note about PATCH_RETAIN_RANGE specifiers with multiple old files:
476 //
477 // Each old version file must have the same RetainRangeCount, and the same
478 // retain range LengthInBytes and OffsetInNewFile values in the same order.
479 // Only the OffsetInOldFile values can differ between old foles for retain
480 // ranges.
481 //
482
483 //
484 // The following prototypes are (some of the) interfaces for creating patches from files.
485 //
486
487 /// <summary>
488 /// Creates a new delta.
489 /// </summary>
490 /// <param name="oldFileCount">Size of oldFileInfoArray.</param>
491 /// <param name="oldFileInfoArray">Target file information.</param>
492 /// <param name="newFileName">Name of updated file.</param>
493 /// <param name="patchFileName">Name of delta to create.</param>
494 /// <param name="optionFlags">PATCH_OPTION_xxx.</param>
495 /// <param name="optionData">Optional PATCH_OPTION_DATA structure.</param>
496 /// <param name="progressCallback">Delegate for progress callbacks.</param>
497 /// <param name="context">Context for progress callback delegate.</param>
498 /// <returns>true if successfull, sets Marshal.GetLastWin32Error() if not.</returns>
499 [DllImport("mspatchc.dll", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true)]
500 [return: MarshalAs(UnmanagedType.Bool)]
501 internal static extern bool CreatePatchFileExW(
502 uint oldFileCount, // maximum 255
503 [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(PatchAPIMarshaler), MarshalCookie="PATCH_OLD_FILE_INFO_W")]
504 PatchOldFileInfoW[] oldFileInfoArray,
505 string newFileName, // input file (required)
506 string patchFileName, // output file (required)
507 uint optionFlags,
508 [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(PatchAPIMarshaler), MarshalCookie="PATCH_OPTION_DATA")]
509 PatchOptionData optionData,
510 [MarshalAs (UnmanagedType.FunctionPtr)]
511 PatchProgressCallback progressCallback,
512 IntPtr context
513 );
514
515 /// <summary>
516 /// Extracts delta header from delta.
517 /// </summary>
518 /// <param name="patchFileName">Name of delta file.</param>
519 /// <param name="patchHeaderFileName">Name of file to create with delta header.</param>
520 /// <returns>true if successfull, sets Marshal.GetLastWin32Error() if not.</returns>
521 [DllImport("mspatchc.dll", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true)]
522 [return: MarshalAs(UnmanagedType.Bool)]
523 internal static extern bool ExtractPatchHeaderToFileW(
524 string patchFileName, // input file
525 string patchHeaderFileName // output file
526 );
527
528 // TODO: Add rest of APIs to enable custom binders to perform more exhaustive checks
529
530 /// <summary>
531 /// Marshals arguments for the CreatePatch~ APIs
532 /// </summary>
533 [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")]
534 internal class PatchAPIMarshaler : ICustomMarshaler
535 {
536 internal static ICustomMarshaler GetInstance(string cookie)
537 {
538 return new PatchAPIMarshaler(cookie);
539 }
540
541 private enum MarshalType
542 {
543 PATCH_OPTION_DATA,
544 PATCH_OLD_FILE_INFO_W
545 };
546 private PatchAPIMarshaler.MarshalType marshalType;
547
548 private PatchAPIMarshaler(string cookie)
549 {
550 this.marshalType = (PatchAPIMarshaler.MarshalType) Enum.Parse(typeof(PatchAPIMarshaler.MarshalType), cookie);
551 }
552
553 //
554 // Summary:
555 // Returns the size of the native data to be marshaled.
556 //
557 // Returns:
558 // The size in bytes of the native data.
559 public int GetNativeDataSize()
560 {
561 return Marshal.SizeOf(typeof(IntPtr));
562 }
563
564 //
565 // Summary:
566 // Performs necessary cleanup of the managed data when it is no longer needed.
567 //
568 // Parameters:
569 // ManagedObj:
570 // The managed object to be destroyed.
571 public void CleanUpManagedData(object ManagedObj)
572 {
573 }
574
575 //
576 // Summary:
577 // Performs necessary cleanup of the unmanaged data when it is no longer needed.
578 //
579 // Parameters:
580 // pNativeData:
581 // A pointer to the unmanaged data to be destroyed.
582 public void CleanUpNativeData(IntPtr pNativeData)
583 {
584 if (IntPtr.Zero == pNativeData)
585 {
586 return;
587 }
588
589 switch (this.marshalType)
590 {
591 case PatchAPIMarshaler.MarshalType.PATCH_OPTION_DATA:
592 this.CleanUpPOD(pNativeData);
593 break;
594 default:
595 this.CleanUpPOFI_A(pNativeData);
596 break;
597 }
598 }
599
600 //
601 // Summary:
602 // Converts the managed data to unmanaged data.
603 //
604 // Parameters:
605 // ManagedObj:
606 // The managed object to be converted.
607 //
608 // Returns:
609 // Returns the COM view of the managed object.
610 public IntPtr MarshalManagedToNative(object ManagedObj)
611 {
612 if (null == ManagedObj)
613 {
614 return IntPtr.Zero;
615 }
616
617 switch(this.marshalType)
618 {
619 case PatchAPIMarshaler.MarshalType.PATCH_OPTION_DATA:
620 return this.MarshalPOD(ManagedObj as PatchOptionData);
621 case PatchAPIMarshaler.MarshalType.PATCH_OLD_FILE_INFO_W:
622 return this.MarshalPOFIW_A(ManagedObj as PatchOldFileInfoW[]);
623 default:
624 throw new InvalidOperationException();
625 }
626 }
627
628
629 //
630 // Summary:
631 // Converts the unmanaged data to managed data.
632 //
633 // Parameters:
634 // pNativeData:
635 // A pointer to the unmanaged data to be wrapped.
636 //
637 // Returns:
638 // Returns the managed view of the COM data.
639 public object MarshalNativeToManaged(IntPtr pNativeData)
640 {
641 return null;
642 }
643
644 // Implementation *************************************************
645
646 // PATCH_OPTION_DATA offsets
647 private static readonly int symbolOptionFlagsOffset = Marshal.SizeOf(typeof(Int32));
648 private static readonly int newFileSymbolPathOffset = 2*Marshal.SizeOf(typeof(Int32));
649 private static readonly int oldFileSymbolPathArrayOffset = 2*Marshal.SizeOf(typeof(Int32)) + Marshal.SizeOf(typeof(IntPtr));
650 private static readonly int extendedOptionFlagsOffset = 2*Marshal.SizeOf(typeof(Int32)) + 2*Marshal.SizeOf(typeof(IntPtr));
651 private static readonly int symLoadCallbackOffset = 3*Marshal.SizeOf(typeof(Int32)) + 2*Marshal.SizeOf(typeof(IntPtr));
652 private static readonly int symLoadContextOffset = 3*Marshal.SizeOf(typeof(Int32)) + 3*Marshal.SizeOf(typeof(IntPtr));
653 private static readonly int interleaveMapArrayOffset = 3*Marshal.SizeOf(typeof(Int32)) + 4*Marshal.SizeOf(typeof(IntPtr));
654 private static readonly int maxLzxWindowSizeOffset = 3*Marshal.SizeOf(typeof(Int32)) + 5*Marshal.SizeOf(typeof(IntPtr));
655 private static readonly int patchOptionDataSize = 4*Marshal.SizeOf(typeof(Int32)) + 5*Marshal.SizeOf(typeof(IntPtr));
656
657 // PATCH_OLD_FILE_INFO offsets
658 private static readonly int oldFileOffset = Marshal.SizeOf(typeof(Int32));
659 private static readonly int ignoreRangeCountOffset = Marshal.SizeOf(typeof(Int32)) + Marshal.SizeOf(typeof(IntPtr));
660 private static readonly int ignoreRangeArrayOffset = 2*Marshal.SizeOf(typeof(Int32)) + Marshal.SizeOf(typeof(IntPtr));
661 private static readonly int retainRangeCountOffset = 2*Marshal.SizeOf(typeof(Int32)) + 2*Marshal.SizeOf(typeof(IntPtr));
662 private static readonly int retainRangeArrayOffset = 3*Marshal.SizeOf(typeof(Int32)) + 2*Marshal.SizeOf(typeof(IntPtr));
663 private static readonly int patchOldFileInfoSize = 3*Marshal.SizeOf(typeof(Int32)) + 3*Marshal.SizeOf(typeof(IntPtr));
664
665 // Methods and data used to preserve data needed for cleanup
666
667 // This dictionary holds the quantity of items internal to each native structure that will need to be freed (the OldFileCount)
668 private static readonly Dictionary<IntPtr, int> OldFileCounts = new Dictionary<IntPtr, int>();
669 private static readonly object OldFileCountsLock = new object();
670
671 private IntPtr CreateMainStruct(int oldFileCount)
672 {
673 int nativeSize;
674 switch(this.marshalType)
675 {
676 case PatchAPIMarshaler.MarshalType.PATCH_OPTION_DATA:
677 nativeSize = patchOptionDataSize;
678 break;
679 case PatchAPIMarshaler.MarshalType.PATCH_OLD_FILE_INFO_W:
680 nativeSize = oldFileCount*patchOldFileInfoSize;
681 break;
682 default:
683 throw new InvalidOperationException();
684 }
685
686 IntPtr native = Marshal.AllocCoTaskMem(nativeSize);
687
688 lock (PatchAPIMarshaler.OldFileCountsLock)
689 {
690 PatchAPIMarshaler.OldFileCounts.Add(native, oldFileCount);
691 }
692
693 return native;
694 }
695
696 private static void ReleaseMainStruct(IntPtr native)
697 {
698 lock (PatchAPIMarshaler.OldFileCountsLock)
699 {
700 PatchAPIMarshaler.OldFileCounts.Remove(native);
701 }
702 Marshal.FreeCoTaskMem(native);
703 }
704
705 private static int GetOldFileCount(IntPtr native)
706 {
707 lock (PatchAPIMarshaler.OldFileCountsLock)
708 {
709 return PatchAPIMarshaler.OldFileCounts[native];
710 }
711 }
712
713 // Helper methods
714
715 private static IntPtr OptionalAnsiString(string managed)
716 {
717 return (null == managed) ? IntPtr.Zero : Marshal.StringToCoTaskMemAnsi(managed);
718 }
719
720 private static IntPtr OptionalUnicodeString(string managed)
721 {
722 return (null == managed) ? IntPtr.Zero : Marshal.StringToCoTaskMemUni(managed);
723 }
724
725 // string array must be of the same length as the number of old files
726 private static IntPtr CreateArrayOfStringA(string[] managed)
727 {
728 if (null == managed)
729 {
730 return IntPtr.Zero;
731 }
732
733 int size = managed.Length * Marshal.SizeOf(typeof(IntPtr));
734 IntPtr native = Marshal.AllocCoTaskMem(size);
735
736 for (int i = 0; i < managed.Length; ++i)
737 {
738 Marshal.WriteIntPtr(native, i*Marshal.SizeOf(typeof(IntPtr)), OptionalAnsiString(managed[i]));
739 }
740
741 return native;
742 }
743
744 // string array must be of the same length as the number of old files
745 private static IntPtr CreateArrayOfStringW(string[] managed)
746 {
747 if (null == managed)
748 {
749 return IntPtr.Zero;
750 }
751
752 int size = managed.Length * Marshal.SizeOf(typeof(IntPtr));
753 IntPtr native = Marshal.AllocCoTaskMem(size);
754
755 for (int i = 0; i < managed.Length; ++i)
756 {
757 Marshal.WriteIntPtr(native, i*Marshal.SizeOf(typeof(IntPtr)), OptionalUnicodeString(managed[i]));
758 }
759
760 return native;
761 }
762
763 private static IntPtr CreateInterleaveMapRange(PatchInterleaveMap managed)
764 {
765 if (null == managed)
766 {
767 return IntPtr.Zero;
768 }
769
770 if (null == managed.ranges)
771 {
772 return IntPtr.Zero;
773 }
774
775 if (0 == managed.ranges.Length)
776 {
777 return IntPtr.Zero;
778 }
779
780 IntPtr native = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(UInt32))
781 + managed.ranges.Length*(Marshal.SizeOf(typeof(PatchInterleaveMap))));
782 WriteUInt32(native, (uint) managed.ranges.Length);
783
784 for (int i = 0; i < managed.ranges.Length; ++i)
785 {
786 Marshal.StructureToPtr(managed.ranges[i], (IntPtr)((Int64)native + i*Marshal.SizeOf(typeof(PatchInterleaveMap))), false);
787 }
788 return native;
789 }
790
791 private static IntPtr CreateInterleaveMap(PatchInterleaveMap[] managed)
792 {
793 if (null == managed)
794 {
795 return IntPtr.Zero;
796 }
797
798 IntPtr native = Marshal.AllocCoTaskMem(managed.Length * Marshal.SizeOf(typeof(IntPtr)));
799
800 for (int i = 0; i < managed.Length; ++i)
801 {
802 Marshal.WriteIntPtr(native, i*Marshal.SizeOf(typeof(IntPtr)), CreateInterleaveMapRange(managed[i]));
803 }
804
805 return native;
806 }
807
808 private static void WriteUInt32(IntPtr native, uint data)
809 {
810 Marshal.WriteInt32(native, unchecked((int) data));
811 }
812
813 private static void WriteUInt32(IntPtr native, int offset, uint data)
814 {
815 Marshal.WriteInt32(native, offset, unchecked((int) data));
816 }
817
818 // Marshal operations
819
820 private IntPtr MarshalPOD(PatchOptionData managed)
821 {
822 if (null == managed)
823 {
824 throw new ArgumentNullException("managed");
825 }
826
827 IntPtr native = this.CreateMainStruct(managed.oldFileSymbolPathArray.Length);
828 Marshal.WriteInt32(native, patchOptionDataSize); // SizeOfThisStruct
829 WriteUInt32(native, symbolOptionFlagsOffset, (uint) managed.symbolOptionFlags);
830 Marshal.WriteIntPtr(native, newFileSymbolPathOffset, PatchAPIMarshaler.OptionalAnsiString(managed.newFileSymbolPath));
831 Marshal.WriteIntPtr(native, oldFileSymbolPathArrayOffset, PatchAPIMarshaler.CreateArrayOfStringA(managed.oldFileSymbolPathArray));
832 WriteUInt32(native, extendedOptionFlagsOffset, managed.extendedOptionFlags);
833
834 // GetFunctionPointerForDelegate() throws an ArgumentNullException if the delegate is null.
835 if (null == managed.symLoadCallback)
836 {
837 Marshal.WriteIntPtr(native, symLoadCallbackOffset, IntPtr.Zero);
838 }
839 else
840 {
841 Marshal.WriteIntPtr(native, symLoadCallbackOffset, Marshal.GetFunctionPointerForDelegate(managed.symLoadCallback));
842 }
843
844 Marshal.WriteIntPtr(native, symLoadContextOffset, managed.symLoadContext);
845 Marshal.WriteIntPtr(native, interleaveMapArrayOffset, PatchAPIMarshaler.CreateInterleaveMap(managed.interleaveMapArray));
846 WriteUInt32(native, maxLzxWindowSizeOffset, managed.maxLzxWindowSize);
847 return native;
848 }
849
850 private IntPtr MarshalPOFIW_A(PatchOldFileInfoW[] managed)
851 {
852 if (null == managed)
853 {
854 throw new ArgumentNullException("managed");
855 }
856
857 if (0 == managed.Length)
858 {
859 return IntPtr.Zero;
860 }
861
862 IntPtr native = this.CreateMainStruct(managed.Length);
863
864 for (int i = 0; i < managed.Length; ++i)
865 {
866 PatchAPIMarshaler.MarshalPOFIW(managed[i], (IntPtr)((Int64)native + i * patchOldFileInfoSize));
867 }
868
869 return native;
870 }
871
872 private static void MarshalPOFIW(PatchOldFileInfoW managed, IntPtr native)
873 {
874 PatchAPIMarshaler.MarshalPOFI(managed, native);
875 Marshal.WriteIntPtr(native, oldFileOffset, PatchAPIMarshaler.OptionalUnicodeString(managed.oldFileName)); // OldFileName
876 }
877
878 private static void MarshalPOFI(PatchOldFileInfo managed, IntPtr native)
879 {
880 Marshal.WriteInt32(native, patchOldFileInfoSize); // SizeOfThisStruct
881 WriteUInt32(native, ignoreRangeCountOffset,
882 (null == managed.ignoreRange) ? 0 : (uint) managed.ignoreRange.Length); // IgnoreRangeCount // maximum 255
883 Marshal.WriteIntPtr(native, ignoreRangeArrayOffset, MarshalPIRArray(managed.ignoreRange)); // IgnoreRangeArray
884 WriteUInt32(native, retainRangeCountOffset,
885 (null == managed.retainRange) ? 0 : (uint) managed.retainRange.Length); // RetainRangeCount // maximum 255
886 Marshal.WriteIntPtr(native, retainRangeArrayOffset, MarshalPRRArray(managed.retainRange)); // RetainRangeArray
887 }
888
889 private static IntPtr MarshalPIRArray(PatchIgnoreRange[] array)
890 {
891 if (null == array)
892 {
893 return IntPtr.Zero;
894 }
895
896 if (0 == array.Length)
897 {
898 return IntPtr.Zero;
899 }
900
901 IntPtr native = Marshal.AllocCoTaskMem(array.Length*Marshal.SizeOf(typeof(PatchIgnoreRange)));
902
903 for (int i = 0; i < array.Length; ++i)
904 {
905 Marshal.StructureToPtr(array[i], (IntPtr)((Int64)native + (i*Marshal.SizeOf(typeof(PatchIgnoreRange)))), false);
906 }
907
908 return native;
909 }
910
911 private static IntPtr MarshalPRRArray(PatchRetainRange[] array)
912 {
913 if (null == array)
914 {
915 return IntPtr.Zero;
916 }
917
918 if (0 == array.Length)
919 {
920 return IntPtr.Zero;
921 }
922
923 IntPtr native = Marshal.AllocCoTaskMem(array.Length*Marshal.SizeOf(typeof(PatchRetainRange)));
924
925 for (int i = 0; i < array.Length; ++i)
926 {
927 Marshal.StructureToPtr(array[i], (IntPtr)((Int64)native + (i*Marshal.SizeOf(typeof(PatchRetainRange)))), false);
928 }
929
930 return native;
931 }
932
933 // CleanUp operations
934
935 private void CleanUpPOD(IntPtr native)
936 {
937 Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, newFileSymbolPathOffset));
938
939 if (IntPtr.Zero != Marshal.ReadIntPtr(native, oldFileSymbolPathArrayOffset))
940 {
941 for (int i = 0; i < GetOldFileCount(native); ++i)
942 {
943 Marshal.FreeCoTaskMem(
944 Marshal.ReadIntPtr(
945 Marshal.ReadIntPtr(native, oldFileSymbolPathArrayOffset),
946 i*Marshal.SizeOf(typeof(IntPtr))));
947 }
948
949 Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, oldFileSymbolPathArrayOffset));
950 }
951
952 if (IntPtr.Zero != Marshal.ReadIntPtr(native, interleaveMapArrayOffset))
953 {
954 for (int i = 0; i < GetOldFileCount(native); ++i)
955 {
956 Marshal.FreeCoTaskMem(
957 Marshal.ReadIntPtr(
958 Marshal.ReadIntPtr(native, interleaveMapArrayOffset),
959 i*Marshal.SizeOf(typeof(IntPtr))));
960 }
961
962 Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, interleaveMapArrayOffset));
963 }
964
965 PatchAPIMarshaler.ReleaseMainStruct(native);
966 }
967
968 private void CleanUpPOFI_A(IntPtr native)
969 {
970 for (int i = 0; i < GetOldFileCount(native); ++i)
971 {
972 PatchAPIMarshaler.CleanUpPOFI((IntPtr)((Int64)native + i*patchOldFileInfoSize));
973 }
974
975 PatchAPIMarshaler.ReleaseMainStruct(native);
976 }
977
978 private static void CleanUpPOFI(IntPtr native)
979 {
980 if (IntPtr.Zero != Marshal.ReadIntPtr(native, oldFileOffset))
981 {
982 Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, oldFileOffset));
983 }
984
985 PatchAPIMarshaler.CleanUpPOFIH(native);
986 }
987
988 private static void CleanUpPOFIH(IntPtr native)
989 {
990 if (IntPtr.Zero != Marshal.ReadIntPtr(native, ignoreRangeArrayOffset))
991 {
992 Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, ignoreRangeArrayOffset));
993 }
994
995 if (IntPtr.Zero != Marshal.ReadIntPtr(native, retainRangeArrayOffset))
996 {
997 Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, retainRangeArrayOffset));
998 }
999 }
1000 }
1001 }
1002}
diff --git a/src/WixToolset.Core/PatchTransform.cs b/src/WixToolset.Core/PatchTransform.cs
new file mode 100644
index 00000000..c87b1a21
--- /dev/null
+++ b/src/WixToolset.Core/PatchTransform.cs
@@ -0,0 +1,274 @@
1// Copyright (c) .NET Foundation and 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.Collections;
7 using System.Globalization;
8 using System.Text;
9 using System.Text.RegularExpressions;
10 using WixToolset.Data;
11 using WixToolset.Extensibility;
12
13 public class PatchTransform : IMessageHandler
14 {
15 private string baseline;
16 private Output transform;
17 private string transformPath;
18
19 public string Baseline
20 {
21 get { return this.baseline; }
22 }
23
24 public Output Transform
25 {
26 get
27 {
28 if (null == this.transform)
29 {
30 this.transform = Output.Load(this.transformPath, false);
31 }
32
33 return this.transform;
34 }
35 }
36
37 public string TransformPath
38 {
39 get { return this.transformPath; }
40 }
41
42 public PatchTransform(string transformPath, string baseline)
43 {
44 this.transformPath = transformPath;
45 this.baseline = baseline;
46 }
47
48 /// <summary>
49 /// Validates that the differences in the transform are valid for patch transforms.
50 /// </summary>
51 public void Validate()
52 {
53 // Changing the ProdocutCode in a patch transform is not recommended.
54 Table propertyTable = this.Transform.Tables["Property"];
55 if (null != propertyTable)
56 {
57 foreach (Row row in propertyTable.Rows)
58 {
59 // Only interested in modified rows; fast check.
60 if (RowOperation.Modify == row.Operation)
61 {
62 if (0 == String.CompareOrdinal("ProductCode", (string)row[0]))
63 {
64 this.OnMessage(WixWarnings.MajorUpgradePatchNotRecommended());
65 }
66 }
67 }
68 }
69
70 // If there is nothing in the component table we can return early because the remaining checks are component based.
71 Table componentTable = this.Transform.Tables["Component"];
72 if (null == componentTable)
73 {
74 return;
75 }
76
77 // Index Feature table row operations
78 Table featureTable = this.Transform.Tables["Feature"];
79 Table featureComponentsTable = this.Transform.Tables["FeatureComponents"];
80 Hashtable featureOps = null;
81 if (null != featureTable)
82 {
83 int capacity = featureTable.Rows.Count;
84 featureOps = new Hashtable(capacity);
85
86 foreach (Row row in featureTable.Rows)
87 {
88 featureOps[(string)row[0]] = row.Operation;
89 }
90 }
91 else
92 {
93 featureOps = new Hashtable();
94 }
95
96 // Index Component table and check for keypath modifications
97 Hashtable deletedComponent = new Hashtable();
98 Hashtable componentKeyPath = new Hashtable();
99 foreach (Row row in componentTable.Rows)
100 {
101 string id = row.Fields[0].Data.ToString();
102 string keypath = (null == row.Fields[5].Data) ? String.Empty : row.Fields[5].Data.ToString();
103
104 componentKeyPath.Add(id, keypath);
105 if (RowOperation.Delete == row.Operation)
106 {
107 deletedComponent.Add(id, row);
108 }
109 else if (RowOperation.Modify == row.Operation)
110 {
111 if (row.Fields[1].Modified)
112 {
113 // Changing the guid of a component is equal to deleting the old one and adding a new one.
114 deletedComponent.Add(id, row);
115 }
116
117 // If the keypath is modified its an error
118 if (row.Fields[5].Modified)
119 {
120 this.OnMessage(WixErrors.InvalidKeypathChange(row.SourceLineNumbers, id, this.transformPath));
121 }
122 }
123 }
124
125 // Verify changes in the file table
126 Table fileTable = this.Transform.Tables["File"];
127 if (null != fileTable)
128 {
129 Hashtable componentWithChangedKeyPath = new Hashtable();
130 foreach (Row row in fileTable.Rows)
131 {
132 if (RowOperation.None != row.Operation)
133 {
134 string fileId = row.Fields[0].Data.ToString();
135 string componentId = row.Fields[1].Data.ToString();
136
137 // If this file is the keypath of a component
138 if (String.Equals((string)componentKeyPath[componentId], fileId, StringComparison.Ordinal))
139 {
140 if (row.Fields[2].Modified)
141 {
142 // You cant change the filename of a file that is the keypath of a component.
143 this.OnMessage(WixErrors.InvalidKeypathChange(row.SourceLineNumbers, componentId, this.transformPath));
144 }
145
146 if (!componentWithChangedKeyPath.ContainsKey(componentId))
147 {
148 componentWithChangedKeyPath.Add(componentId, fileId);
149 }
150 }
151
152 if (RowOperation.Delete == row.Operation)
153 {
154 // If the file is removed from a component that is not deleted.
155 if (!deletedComponent.ContainsKey(componentId))
156 {
157 bool foundRemoveFileEntry = false;
158 string filename = Msi.Installer.GetName((string)row[2], false, true);
159
160 Table removeFileTable = this.Transform.Tables["RemoveFile"];
161 if (null != removeFileTable)
162 {
163 foreach (Row removeFileRow in removeFileTable.Rows)
164 {
165 if (RowOperation.Delete == removeFileRow.Operation)
166 {
167 continue;
168 }
169
170 if (componentId == (string)removeFileRow[1])
171 {
172 // Check if there is a RemoveFile entry for this file
173 if (null != removeFileRow[2])
174 {
175 string removeFileName = Msi.Installer.GetName((string)removeFileRow[2], false, true);
176
177 // Convert the MSI format for a wildcard string to Regex format.
178 removeFileName = removeFileName.Replace('.', '|').Replace('?', '.').Replace("*", ".*").Replace("|", "\\.");
179
180 Regex regex = new Regex(removeFileName, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
181 if (regex.IsMatch(filename))
182 {
183 foundRemoveFileEntry = true;
184 break;
185 }
186 }
187 }
188 }
189 }
190
191 if (!foundRemoveFileEntry)
192 {
193 this.OnMessage(WixWarnings.InvalidRemoveFile(row.SourceLineNumbers, fileId, componentId));
194 }
195 }
196 }
197 }
198 }
199 }
200
201 if (0 < deletedComponent.Count)
202 {
203 // Index FeatureComponents table.
204 Hashtable featureComponents = new Hashtable();
205
206 if (null != featureComponentsTable)
207 {
208 foreach (Row row in featureComponentsTable.Rows)
209 {
210 ArrayList features;
211 string componentId = row.Fields[1].Data.ToString();
212
213 if (featureComponents.Contains(componentId))
214 {
215 features = (ArrayList)featureComponents[componentId];
216 }
217 else
218 {
219 features = new ArrayList();
220 featureComponents.Add(componentId, features);
221 }
222 features.Add(row.Fields[0].Data.ToString());
223 }
224 }
225
226 // Check to make sure if a component was deleted, the feature was too.
227 foreach (DictionaryEntry entry in deletedComponent)
228 {
229 if (featureComponents.Contains(entry.Key.ToString()))
230 {
231 ArrayList features = (ArrayList)featureComponents[entry.Key.ToString()];
232 foreach (string featureId in features)
233 {
234 if (!featureOps.ContainsKey(featureId) || RowOperation.Delete != (RowOperation)featureOps[featureId])
235 {
236 // The feature was not deleted.
237 this.OnMessage(WixErrors.InvalidRemoveComponent(((Row)entry.Value).SourceLineNumbers, entry.Key.ToString(), featureId, this.transformPath));
238 }
239 }
240 }
241 }
242 }
243
244 // Warn if new components are added to existing features
245 if (null != featureComponentsTable)
246 {
247 foreach (Row row in featureComponentsTable.Rows)
248 {
249 if (RowOperation.Add == row.Operation)
250 {
251 // Check if the feature is in the Feature table
252 string feature_ = (string)row[0];
253 string component_ = (string)row[1];
254
255 // Features may not be present if not referenced
256 if (!featureOps.ContainsKey(feature_) || RowOperation.Add != (RowOperation)featureOps[feature_])
257 {
258 this.OnMessage(WixWarnings.NewComponentAddedToExistingFeature(row.SourceLineNumbers, component_, feature_, this.transformPath));
259 }
260 }
261 }
262 }
263 }
264
265 /// <summary>
266 /// Sends a message to the message delegate if there is one.
267 /// </summary>
268 /// <param name="mea">Message event arguments.</param>
269 public void OnMessage(MessageEventArgs e)
270 {
271 Messaging.Instance.OnMessage(e);
272 }
273 }
274}
diff --git a/src/WixToolset.Core/Preprocess/IfContext.cs b/src/WixToolset.Core/Preprocess/IfContext.cs
new file mode 100644
index 00000000..64b5bd91
--- /dev/null
+++ b/src/WixToolset.Core/Preprocess/IfContext.cs
@@ -0,0 +1,110 @@
1// Copyright (c) .NET Foundation and 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.Preprocess
4{
5 using System;
6
7 /// <summary>
8 /// Current state of the if context.
9 /// </summary>
10 internal enum IfState
11 {
12 /// <summary>Context currently in unknown state.</summary>
13 Unknown,
14
15 /// <summary>Context currently inside if statement.</summary>
16 If,
17
18 /// <summary>Context currently inside elseif statement..</summary>
19 ElseIf,
20
21 /// <summary>Conext currently inside else statement.</summary>
22 Else,
23 }
24
25 /// <summary>
26 /// Context for an if statement in the preprocessor.
27 /// </summary>
28 internal sealed class IfContext
29 {
30 private bool active;
31 private bool keep;
32 private bool everKept;
33 private IfState state;
34
35 /// <summary>
36 /// Creates a default if context object, which are used for if's within an inactive preprocessor block
37 /// </summary>
38 public IfContext()
39 {
40 this.active = false;
41 this.keep = false;
42 this.everKept = true;
43 this.state = IfState.If;
44 }
45
46 /// <summary>
47 /// Creates an if context object.
48 /// </summary>
49 /// <param name="active">Flag if context is currently active.</param>
50 /// <param name="keep">Flag if context is currently true.</param>
51 /// <param name="state">State of context to start in.</param>
52 public IfContext(bool active, bool keep, IfState state)
53 {
54 this.active = active;
55 this.keep = keep;
56 this.everKept = keep;
57 this.state = state;
58 }
59
60 /// <summary>
61 /// Gets and sets if this if context is currently active.
62 /// </summary>
63 /// <value>true if context is active.</value>
64 public bool Active
65 {
66 get { return this.active; }
67 set { this.active = value; }
68 }
69
70 /// <summary>
71 /// Gets and sets if context is current true.
72 /// </summary>
73 /// <value>true if context is currently true.</value>
74 public bool IsTrue
75 {
76 get
77 {
78 return this.keep;
79 }
80
81 set
82 {
83 this.keep = value;
84 if (this.keep)
85 {
86 this.everKept = true;
87 }
88 }
89 }
90
91 /// <summary>
92 /// Gets if the context was ever true.
93 /// </summary>
94 /// <value>True if context was ever true.</value>
95 public bool WasEverTrue
96 {
97 get { return this.everKept; }
98 }
99
100 /// <summary>
101 /// Gets the current state of the if context.
102 /// </summary>
103 /// <value>Current state of context.</value>
104 public IfState IfState
105 {
106 get { return this.state; }
107 set { this.state = value; }
108 }
109 }
110}
diff --git a/src/WixToolset.Core/Preprocessor.cs b/src/WixToolset.Core/Preprocessor.cs
new file mode 100644
index 00000000..a9fbcbb7
--- /dev/null
+++ b/src/WixToolset.Core/Preprocessor.cs
@@ -0,0 +1,1598 @@
1// Copyright (c) .NET Foundation and 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.Collections;
7 using System.Collections.Generic;
8 using System.Diagnostics.CodeAnalysis;
9 using System.Globalization;
10 using System.IO;
11 using System.Text;
12 using System.Text.RegularExpressions;
13 using System.Xml;
14 using System.Xml.Linq;
15 using WixToolset.Data;
16 using WixToolset.Extensibility;
17 using WixToolset.Preprocess;
18
19 /// <summary>
20 /// Preprocessor object
21 /// </summary>
22 public sealed class Preprocessor
23 {
24 private readonly Regex defineRegex = new Regex(@"^\s*(?<varName>.+?)\s*(=\s*(?<varValue>.+?)\s*)?$", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture);
25 private readonly Regex pragmaRegex = new Regex(@"^\s*(?<pragmaName>.+?)(?<pragmaValue>[\s\(].+?)?$", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture);
26
27 private readonly XmlReaderSettings DocumentXmlReaderSettings = new XmlReaderSettings()
28 {
29 ValidationFlags = System.Xml.Schema.XmlSchemaValidationFlags.None,
30 XmlResolver = null,
31 };
32 private readonly XmlReaderSettings FragmentXmlReaderSettings = new XmlReaderSettings()
33 {
34 ConformanceLevel = System.Xml.ConformanceLevel.Fragment,
35 ValidationFlags = System.Xml.Schema.XmlSchemaValidationFlags.None,
36 XmlResolver = null,
37 };
38
39 private List<IPreprocessorExtension> extensions;
40 private Dictionary<string, IPreprocessorExtension> extensionsByPrefix;
41 private List<InspectorExtension> inspectorExtensions;
42
43 private SourceLineNumber currentLineNumber;
44 private Stack<SourceLineNumber> sourceStack;
45
46 private PreprocessorCore core;
47 private TextWriter preprocessOut;
48
49 private Stack<bool> includeNextStack;
50 private Stack<string> currentFileStack;
51
52 private Platform currentPlatform;
53
54 /// <summary>
55 /// Creates a new preprocesor.
56 /// </summary>
57 public Preprocessor()
58 {
59 this.IncludeSearchPaths = new List<string>();
60
61 this.extensions = new List<IPreprocessorExtension>();
62 this.extensionsByPrefix = new Dictionary<string, IPreprocessorExtension>();
63 this.inspectorExtensions = new List<InspectorExtension>();
64
65 this.sourceStack = new Stack<SourceLineNumber>();
66
67 this.includeNextStack = new Stack<bool>();
68 this.currentFileStack = new Stack<string>();
69
70 this.currentPlatform = Platform.X86;
71 }
72
73 /// <summary>
74 /// Event for ifdef/ifndef directives.
75 /// </summary>
76 public event IfDefEventHandler IfDef;
77
78 /// <summary>
79 /// Event for included files.
80 /// </summary>
81 public event IncludedFileEventHandler IncludedFile;
82
83 /// <summary>
84 /// Event for preprocessed stream.
85 /// </summary>
86 public event ProcessedStreamEventHandler ProcessedStream;
87
88 /// <summary>
89 /// Event for resolved variables.
90 /// </summary>
91 public event ResolvedVariableEventHandler ResolvedVariable;
92
93 /// <summary>
94 /// Enumeration for preprocessor operations in if statements.
95 /// </summary>
96 private enum PreprocessorOperation
97 {
98 /// <summary>The and operator.</summary>
99 And,
100
101 /// <summary>The or operator.</summary>
102 Or,
103
104 /// <summary>The not operator.</summary>
105 Not
106 }
107
108 /// <summary>
109 /// Gets or sets the platform which the compiler will use when defaulting 64-bit attributes and elements.
110 /// </summary>
111 /// <value>The platform which the compiler will use when defaulting 64-bit attributes and elements.</value>
112 public Platform CurrentPlatform
113 {
114 get { return this.currentPlatform; }
115 set { this.currentPlatform = value; }
116 }
117
118 /// <summary>
119 /// Ordered list of search paths that the precompiler uses to find included files.
120 /// </summary>
121 /// <value>List of ordered search paths to use during precompiling.</value>
122 public IList<string> IncludeSearchPaths { get; private set; }
123
124 /// <summary>
125 /// Specifies the text stream to display the postprocessed data to.
126 /// </summary>
127 /// <value>TextWriter to write preprocessed xml to.</value>
128 public TextWriter PreprocessOut
129 {
130 get { return this.preprocessOut; }
131 set { this.preprocessOut = value; }
132 }
133
134 /// <summary>
135 /// Get the source line information for the current element. The precompiler will insert
136 /// special source line number processing instructions before each element that it
137 /// encounters. This is where those line numbers are read and processed. This function
138 /// may return an array of source line numbers because the element may have come from
139 /// an included file, in which case the chain of imports is expressed in the array.
140 /// </summary>
141 /// <param name="node">Element to get source line information for.</param>
142 /// <returns>Returns the stack of imports used to author the element being processed.</returns>
143 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
144 public static SourceLineNumber GetSourceLineNumbers(XmlNode node)
145 {
146 return null;
147 }
148
149 /// <summary>
150 /// Get the source line information for the current element. The precompiler will insert
151 /// special source line number information for each element that it encounters.
152 /// </summary>
153 /// <param name="node">Element to get source line information for.</param>
154 /// <returns>
155 /// The source line number used to author the element being processed or
156 /// null if the preprocessor did not process the element or the node is
157 /// not an element.
158 /// </returns>
159 public static SourceLineNumber GetSourceLineNumbers(XObject node)
160 {
161 return SourceLineNumber.GetFromXAnnotation(node);
162 }
163
164 /// <summary>
165 /// Adds an extension.
166 /// </summary>
167 /// <param name="extension">The extension to add.</param>
168 public void AddExtension(IPreprocessorExtension extension)
169 {
170 this.extensions.Add(extension);
171
172 if (null != extension.Prefixes)
173 {
174 foreach (string prefix in extension.Prefixes)
175 {
176 IPreprocessorExtension collidingExtension;
177 if (!this.extensionsByPrefix.TryGetValue(prefix, out collidingExtension))
178 {
179 this.extensionsByPrefix.Add(prefix, extension);
180 }
181 else
182 {
183 Messaging.Instance.OnMessage(WixErrors.DuplicateExtensionPreprocessorType(extension.GetType().ToString(), prefix, collidingExtension.GetType().ToString()));
184 }
185 }
186 }
187
188 //if (null != extension.InspectorExtension)
189 //{
190 // this.inspectorExtensions.Add(extension.InspectorExtension);
191 //}
192 }
193
194 /// <summary>
195 /// Preprocesses a file.
196 /// </summary>
197 /// <param name="sourceFile">The file to preprocess.</param>
198 /// <param name="variables">The variables defined prior to preprocessing.</param>
199 /// <returns>XDocument with the postprocessed data.</returns>
200 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
201 public XDocument Process(string sourceFile, IDictionary<string, string> variables)
202 {
203 using (Stream sourceStream = new FileStream(sourceFile, FileMode.Open, FileAccess.Read, FileShare.Read))
204 {
205 InspectorCore inspectorCore = new InspectorCore();
206 foreach (InspectorExtension inspectorExtension in this.inspectorExtensions)
207 {
208 inspectorExtension.Core = inspectorCore;
209 inspectorExtension.InspectSource(sourceStream);
210
211 // reset
212 inspectorExtension.Core = null;
213 sourceStream.Position = 0;
214 }
215
216 if (inspectorCore.EncounteredError)
217 {
218 return null;
219 }
220
221 using (XmlReader reader = XmlReader.Create(sourceFile, DocumentXmlReaderSettings))
222 {
223 return Process(reader, variables, sourceFile);
224 }
225 }
226 }
227
228 /// <summary>
229 /// Preprocesses a file.
230 /// </summary>
231 /// <param name="sourceFile">The file to preprocess.</param>
232 /// <param name="variables">The variables defined prior to preprocessing.</param>
233 /// <returns>XDocument with the postprocessed data.</returns>
234 [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")]
235 public XDocument Process(XmlReader reader, IDictionary<string, string> variables, string sourceFile = null)
236 {
237 if (String.IsNullOrEmpty(sourceFile) && !String.IsNullOrEmpty(reader.BaseURI))
238 {
239 Uri uri = new Uri(reader.BaseURI);
240 sourceFile = uri.AbsolutePath;
241 }
242
243 this.core = new PreprocessorCore(this.extensionsByPrefix, sourceFile, variables);
244 this.core.ResolvedVariableHandler = this.ResolvedVariable;
245 this.core.CurrentPlatform = this.currentPlatform;
246 this.currentLineNumber = new SourceLineNumber(sourceFile);
247 this.currentFileStack.Clear();
248 this.currentFileStack.Push(this.core.GetVariableValue(this.currentLineNumber, "sys", "SOURCEFILEDIR"));
249
250 // Process the reader into the output.
251 XDocument output = new XDocument();
252 try
253 {
254 foreach (PreprocessorExtension extension in this.extensions)
255 {
256 extension.Core = this.core;
257 extension.Initialize();
258 }
259
260 this.PreprocessReader(false, reader, output, 0);
261 }
262 catch (XmlException e)
263 {
264 this.UpdateCurrentLineNumber(reader, 0);
265 throw new WixException(WixErrors.InvalidXml(this.currentLineNumber, "source", e.Message));
266 }
267
268 // Fire event with post-processed document.
269 ProcessedStreamEventArgs args = new ProcessedStreamEventArgs(sourceFile, output);
270 this.OnProcessedStream(args);
271
272 // preprocess the generated XML Document
273 foreach (PreprocessorExtension extension in this.extensions)
274 {
275 extension.PreprocessDocument(output);
276 }
277
278 // finalize the preprocessing
279 foreach (PreprocessorExtension extension in this.extensions)
280 {
281 extension.Finish();
282 extension.Core = null;
283 }
284
285 if (this.core.EncounteredError)
286 {
287 return null;
288 }
289 else
290 {
291 if (null != this.preprocessOut)
292 {
293 output.Save(this.preprocessOut);
294 this.preprocessOut.Flush();
295 }
296
297 return output;
298 }
299 }
300
301 /// <summary>
302 /// Determins if string is an operator.
303 /// </summary>
304 /// <param name="operation">String to check.</param>
305 /// <returns>true if string is an operator.</returns>
306 private static bool IsOperator(string operation)
307 {
308 if (operation == null)
309 {
310 return false;
311 }
312
313 operation = operation.Trim();
314 if (0 == operation.Length)
315 {
316 return false;
317 }
318
319 if ("=" == operation ||
320 "!=" == operation ||
321 "<" == operation ||
322 "<=" == operation ||
323 ">" == operation ||
324 ">=" == operation ||
325 "~=" == operation)
326 {
327 return true;
328 }
329 return false;
330 }
331
332 /// <summary>
333 /// Determines if expression is currently inside quotes.
334 /// </summary>
335 /// <param name="expression">Expression to evaluate.</param>
336 /// <param name="index">Index to start searching in expression.</param>
337 /// <returns>true if expression is inside in quotes.</returns>
338 private static bool InsideQuotes(string expression, int index)
339 {
340 if (index == -1)
341 {
342 return false;
343 }
344
345 int numQuotes = 0;
346 int tmpIndex = 0;
347 while (-1 != (tmpIndex = expression.IndexOf('\"', tmpIndex, index - tmpIndex)))
348 {
349 numQuotes++;
350 tmpIndex++;
351 }
352
353 // found an even number of quotes before the index, so we're not inside
354 if (numQuotes % 2 == 0)
355 {
356 return false;
357 }
358
359 // found an odd number of quotes, so we are inside
360 return true;
361 }
362
363 /// <summary>
364 /// Fires an event when an ifdef/ifndef directive is processed.
365 /// </summary>
366 /// <param name="ea">ifdef/ifndef event arguments.</param>
367 private void OnIfDef(IfDefEventArgs ea)
368 {
369 if (null != this.IfDef)
370 {
371 this.IfDef(this, ea);
372 }
373 }
374
375 /// <summary>
376 /// Fires an event when an included file is processed.
377 /// </summary>
378 /// <param name="ea">Included file event arguments.</param>
379 private void OnIncludedFile(IncludedFileEventArgs ea)
380 {
381 if (null != this.IncludedFile)
382 {
383 this.IncludedFile(this, ea);
384 }
385 }
386
387 /// <summary>
388 /// Fires an event after the file is preprocessed.
389 /// </summary>
390 /// <param name="ea">Included file event arguments.</param>
391 private void OnProcessedStream(ProcessedStreamEventArgs ea)
392 {
393 if (null != this.ProcessedStream)
394 {
395 this.ProcessedStream(this, ea);
396 }
397 }
398
399 /// <summary>
400 /// Tests expression to see if it starts with a keyword.
401 /// </summary>
402 /// <param name="expression">Expression to test.</param>
403 /// <param name="operation">Operation to test for.</param>
404 /// <returns>true if expression starts with a keyword.</returns>
405 private static bool StartsWithKeyword(string expression, PreprocessorOperation operation)
406 {
407 expression = expression.ToUpper(CultureInfo.InvariantCulture);
408 switch (operation)
409 {
410 case PreprocessorOperation.Not:
411 if (expression.StartsWith("NOT ", StringComparison.Ordinal) || expression.StartsWith("NOT(", StringComparison.Ordinal))
412 {
413 return true;
414 }
415 break;
416 case PreprocessorOperation.And:
417 if (expression.StartsWith("AND ", StringComparison.Ordinal) || expression.StartsWith("AND(", StringComparison.Ordinal))
418 {
419 return true;
420 }
421 break;
422 case PreprocessorOperation.Or:
423 if (expression.StartsWith("OR ", StringComparison.Ordinal) || expression.StartsWith("OR(", StringComparison.Ordinal))
424 {
425 return true;
426 }
427 break;
428 default:
429 break;
430 }
431 return false;
432 }
433
434 /// <summary>
435 /// Processes an xml reader into an xml writer.
436 /// </summary>
437 /// <param name="include">Specifies if reader is from an included file.</param>
438 /// <param name="reader">Reader for the source document.</param>
439 /// <param name="container">Node where content should be added.</param>
440 /// <param name="offset">Original offset for the line numbers being processed.</param>
441 private void PreprocessReader(bool include, XmlReader reader, XContainer container, int offset)
442 {
443 XContainer currentContainer = container;
444 Stack<XContainer> containerStack = new Stack<XContainer>();
445
446 IfContext ifContext = new IfContext(true, true, IfState.Unknown); // start by assuming we want to keep the nodes in the source code
447 Stack<IfContext> ifStack = new Stack<IfContext>();
448
449 // process the reader into the writer
450 while (reader.Read())
451 {
452 // update information here in case an error occurs before the next read
453 this.UpdateCurrentLineNumber(reader, offset);
454
455 SourceLineNumber sourceLineNumbers = this.currentLineNumber;
456
457 // check for changes in conditional processing
458 if (XmlNodeType.ProcessingInstruction == reader.NodeType)
459 {
460 bool ignore = false;
461 string name = null;
462
463 switch (reader.LocalName)
464 {
465 case "if":
466 ifStack.Push(ifContext);
467 if (ifContext.IsTrue)
468 {
469 ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, this.EvaluateExpression(reader.Value), IfState.If);
470 }
471 else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true
472 {
473 ifContext = new IfContext();
474 }
475 ignore = true;
476 break;
477
478 case "ifdef":
479 ifStack.Push(ifContext);
480 name = reader.Value.Trim();
481 if (ifContext.IsTrue)
482 {
483 ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, (null != this.core.GetVariableValue(sourceLineNumbers, name, true)), IfState.If);
484 }
485 else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true
486 {
487 ifContext = new IfContext();
488 }
489 ignore = true;
490 OnIfDef(new IfDefEventArgs(sourceLineNumbers, true, ifContext.IsTrue, name));
491 break;
492
493 case "ifndef":
494 ifStack.Push(ifContext);
495 name = reader.Value.Trim();
496 if (ifContext.IsTrue)
497 {
498 ifContext = new IfContext(ifContext.IsTrue & ifContext.Active, (null == this.core.GetVariableValue(sourceLineNumbers, name, true)), IfState.If);
499 }
500 else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true
501 {
502 ifContext = new IfContext();
503 }
504 ignore = true;
505 OnIfDef(new IfDefEventArgs(sourceLineNumbers, false, !ifContext.IsTrue, name));
506 break;
507
508 case "elseif":
509 if (0 == ifStack.Count)
510 {
511 throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(this.currentLineNumber, "if", "elseif"));
512 }
513
514 if (IfState.If != ifContext.IfState && IfState.ElseIf != ifContext.IfState)
515 {
516 throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(this.currentLineNumber, "if", "elseif"));
517 }
518
519 ifContext.IfState = IfState.ElseIf; // we're now in an elseif
520 if (!ifContext.WasEverTrue) // if we've never evaluated the if context to true, then we can try this test
521 {
522 ifContext.IsTrue = this.EvaluateExpression(reader.Value);
523 }
524 else if (ifContext.IsTrue)
525 {
526 ifContext.IsTrue = false;
527 }
528 ignore = true;
529 break;
530
531 case "else":
532 if (0 == ifStack.Count)
533 {
534 throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(this.currentLineNumber, "if", "else"));
535 }
536
537 if (IfState.If != ifContext.IfState && IfState.ElseIf != ifContext.IfState)
538 {
539 throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(this.currentLineNumber, "if", "else"));
540 }
541
542 ifContext.IfState = IfState.Else; // we're now in an else
543 ifContext.IsTrue = !ifContext.WasEverTrue; // if we were never true, we can be true now
544 ignore = true;
545 break;
546
547 case "endif":
548 if (0 == ifStack.Count)
549 {
550 throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(this.currentLineNumber, "if", "endif"));
551 }
552
553 ifContext = (IfContext)ifStack.Pop();
554 ignore = true;
555 break;
556 }
557
558 if (ignore) // ignore this node since we just handled it above
559 {
560 continue;
561 }
562 }
563
564 if (!ifContext.Active || !ifContext.IsTrue) // if our context is not true then skip the rest of the processing and just read the next thing
565 {
566 continue;
567 }
568
569 switch (reader.NodeType)
570 {
571 case XmlNodeType.XmlDeclaration:
572 XDocument document = currentContainer as XDocument;
573 if (null != document)
574 {
575 document.Declaration = new XDeclaration(null, null, null);
576 while (reader.MoveToNextAttribute())
577 {
578 switch (reader.LocalName)
579 {
580 case "version":
581 document.Declaration.Version = reader.Value;
582 break;
583
584 case "encoding":
585 document.Declaration.Encoding = reader.Value;
586 break;
587
588 case "standalone":
589 document.Declaration.Standalone = reader.Value;
590 break;
591 }
592 }
593
594 }
595 //else
596 //{
597 // display an error? Can this happen?
598 //}
599 break;
600
601 case XmlNodeType.ProcessingInstruction:
602 switch (reader.LocalName)
603 {
604 case "define":
605 this.PreprocessDefine(reader.Value);
606 break;
607
608 case "error":
609 this.PreprocessError(reader.Value);
610 break;
611
612 case "warning":
613 this.PreprocessWarning(reader.Value);
614 break;
615
616 case "undef":
617 this.PreprocessUndef(reader.Value);
618 break;
619
620 case "include":
621 this.UpdateCurrentLineNumber(reader, offset);
622 this.PreprocessInclude(reader.Value, currentContainer);
623 break;
624
625 case "foreach":
626 this.PreprocessForeach(reader, currentContainer, offset);
627 break;
628
629 case "endforeach": // endforeach is handled in PreprocessForeach, so seeing it here is an error
630 throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(this.currentLineNumber, "foreach", "endforeach"));
631
632 case "pragma":
633 this.PreprocessPragma(reader.Value, currentContainer);
634 break;
635
636 default:
637 // unknown processing instructions are currently ignored
638 break;
639 }
640 break;
641
642 case XmlNodeType.Element:
643 if (0 < this.includeNextStack.Count && this.includeNextStack.Peek())
644 {
645 if ("Include" != reader.LocalName)
646 {
647 this.core.OnMessage(WixErrors.InvalidDocumentElement(this.currentLineNumber, reader.Name, "include", "Include"));
648 }
649
650 this.includeNextStack.Pop();
651 this.includeNextStack.Push(false);
652 break;
653 }
654
655 bool empty = reader.IsEmptyElement;
656 XNamespace ns = XNamespace.Get(reader.NamespaceURI);
657 XElement element = new XElement(ns + reader.LocalName);
658 currentContainer.Add(element);
659
660 this.UpdateCurrentLineNumber(reader, offset);
661 element.AddAnnotation(this.currentLineNumber);
662
663 while (reader.MoveToNextAttribute())
664 {
665 string value = this.core.PreprocessString(this.currentLineNumber, reader.Value);
666 XNamespace attribNamespace = XNamespace.Get(reader.NamespaceURI);
667 attribNamespace = XNamespace.Xmlns == attribNamespace && reader.LocalName.Equals("xmlns") ? XNamespace.None : attribNamespace;
668 element.Add(new XAttribute(attribNamespace + reader.LocalName, value));
669 }
670
671 if (!empty)
672 {
673 containerStack.Push(currentContainer);
674 currentContainer = element;
675 }
676 break;
677
678 case XmlNodeType.EndElement:
679 if (0 < reader.Depth || !include)
680 {
681 currentContainer = containerStack.Pop();
682 }
683 break;
684
685 case XmlNodeType.Text:
686 string postprocessedText = this.core.PreprocessString(this.currentLineNumber, reader.Value);
687 currentContainer.Add(postprocessedText);
688 break;
689
690 case XmlNodeType.CDATA:
691 string postprocessedValue = this.core.PreprocessString(this.currentLineNumber, reader.Value);
692 currentContainer.Add(new XCData(postprocessedValue));
693 break;
694
695 default:
696 break;
697 }
698 }
699
700 if (0 != ifStack.Count)
701 {
702 throw new WixException(WixErrors.NonterminatedPreprocessorInstruction(this.currentLineNumber, "if", "endif"));
703 }
704
705 // TODO: can this actually happen?
706 if (0 != containerStack.Count)
707 {
708 throw new WixException(WixErrors.NonterminatedPreprocessorInstruction(this.currentLineNumber, "nodes", "nodes"));
709 }
710 }
711
712 /// <summary>
713 /// Processes an error processing instruction.
714 /// </summary>
715 /// <param name="errorMessage">Text from source.</param>
716 private void PreprocessError(string errorMessage)
717 {
718 SourceLineNumber sourceLineNumbers = this.currentLineNumber;
719
720 // resolve other variables in the error message
721 errorMessage = this.core.PreprocessString(sourceLineNumbers, errorMessage);
722
723 throw new WixException(WixErrors.PreprocessorError(sourceLineNumbers, errorMessage));
724 }
725
726 /// <summary>
727 /// Processes a warning processing instruction.
728 /// </summary>
729 /// <param name="warningMessage">Text from source.</param>
730 private void PreprocessWarning(string warningMessage)
731 {
732 SourceLineNumber sourceLineNumbers = this.currentLineNumber;
733
734 // resolve other variables in the warning message
735 warningMessage = this.core.PreprocessString(sourceLineNumbers, warningMessage);
736
737 this.core.OnMessage(WixWarnings.PreprocessorWarning(sourceLineNumbers, warningMessage));
738 }
739
740 /// <summary>
741 /// Processes a define processing instruction and creates the appropriate parameter.
742 /// </summary>
743 /// <param name="originalDefine">Text from source.</param>
744 private void PreprocessDefine(string originalDefine)
745 {
746 Match match = defineRegex.Match(originalDefine);
747 SourceLineNumber sourceLineNumbers = this.currentLineNumber;
748
749 if (!match.Success)
750 {
751 throw new WixException(WixErrors.IllegalDefineStatement(sourceLineNumbers, originalDefine));
752 }
753
754 string defineName = match.Groups["varName"].Value;
755 string defineValue = match.Groups["varValue"].Value;
756
757 // strip off the optional quotes
758 if (1 < defineValue.Length &&
759 ((defineValue.StartsWith("\"", StringComparison.Ordinal) && defineValue.EndsWith("\"", StringComparison.Ordinal))
760 || (defineValue.StartsWith("'", StringComparison.Ordinal) && defineValue.EndsWith("'", StringComparison.Ordinal))))
761 {
762 defineValue = defineValue.Substring(1, defineValue.Length - 2);
763 }
764
765 // resolve other variables in the variable value
766 defineValue = this.core.PreprocessString(sourceLineNumbers, defineValue);
767
768 if (defineName.StartsWith("var.", StringComparison.Ordinal))
769 {
770 this.core.AddVariable(sourceLineNumbers, defineName.Substring(4), defineValue);
771 }
772 else
773 {
774 this.core.AddVariable(sourceLineNumbers, defineName, defineValue);
775 }
776 }
777
778 /// <summary>
779 /// Processes an undef processing instruction and creates the appropriate parameter.
780 /// </summary>
781 /// <param name="originalDefine">Text from source.</param>
782 private void PreprocessUndef(string originalDefine)
783 {
784 SourceLineNumber sourceLineNumbers = this.currentLineNumber;
785 string name = this.core.PreprocessString(sourceLineNumbers, originalDefine.Trim());
786
787 if (name.StartsWith("var.", StringComparison.Ordinal))
788 {
789 this.core.RemoveVariable(sourceLineNumbers, name.Substring(4));
790 }
791 else
792 {
793 this.core.RemoveVariable(sourceLineNumbers, name);
794 }
795 }
796
797 /// <summary>
798 /// Processes an included file.
799 /// </summary>
800 /// <param name="includePath">Path to included file.</param>
801 /// <param name="parent">Parent container for included content.</param>
802 private void PreprocessInclude(string includePath, XContainer parent)
803 {
804 SourceLineNumber sourceLineNumbers = this.currentLineNumber;
805
806 // preprocess variables in the path
807 includePath = this.core.PreprocessString(sourceLineNumbers, includePath);
808
809 string includeFile = this.GetIncludeFile(includePath);
810
811 if (null == includeFile)
812 {
813 throw new WixException(WixErrors.FileNotFound(sourceLineNumbers, includePath, "include"));
814 }
815
816 using (XmlReader reader = XmlReader.Create(includeFile, DocumentXmlReaderSettings))
817 {
818 this.PushInclude(includeFile);
819
820 // process the included reader into the writer
821 try
822 {
823 this.PreprocessReader(true, reader, parent, 0);
824 }
825 catch (XmlException e)
826 {
827 this.UpdateCurrentLineNumber(reader, 0);
828 throw new WixException(WixErrors.InvalidXml(sourceLineNumbers, "source", e.Message));
829 }
830
831 this.OnIncludedFile(new IncludedFileEventArgs(sourceLineNumbers, includeFile));
832
833 this.PopInclude();
834 }
835 }
836
837 /// <summary>
838 /// Preprocess a foreach processing instruction.
839 /// </summary>
840 /// <param name="reader">The xml reader.</param>
841 /// <param name="container">The container where to output processed data.</param>
842 /// <param name="offset">Offset for the line numbers.</param>
843 private void PreprocessForeach(XmlReader reader, XContainer container, int offset)
844 {
845 // find the "in" token
846 int indexOfInToken = reader.Value.IndexOf(" in ", StringComparison.Ordinal);
847 if (0 > indexOfInToken)
848 {
849 throw new WixException(WixErrors.IllegalForeach(this.currentLineNumber, reader.Value));
850 }
851
852 // parse out the variable name
853 string varName = reader.Value.Substring(0, indexOfInToken).Trim();
854 string varValuesString = reader.Value.Substring(indexOfInToken + 4).Trim();
855
856 // preprocess the variable values string because it might be a variable itself
857 varValuesString = this.core.PreprocessString(this.currentLineNumber, varValuesString);
858
859 string[] varValues = varValuesString.Split(';');
860
861 // go through all the empty strings
862 while (reader.Read() && XmlNodeType.Whitespace == reader.NodeType)
863 {
864 }
865
866 // get the offset of this xml fragment (for some reason its always off by 1)
867 IXmlLineInfo lineInfoReader = reader as IXmlLineInfo;
868 if (null != lineInfoReader)
869 {
870 offset += lineInfoReader.LineNumber - 1;
871 }
872
873 XmlTextReader textReader = reader as XmlTextReader;
874 // dump the xml to a string (maintaining whitespace if possible)
875 if (null != textReader)
876 {
877 textReader.WhitespaceHandling = WhitespaceHandling.All;
878 }
879
880 StringBuilder fragmentBuilder = new StringBuilder();
881 int nestedForeachCount = 1;
882 while (nestedForeachCount != 0)
883 {
884 if (reader.NodeType == XmlNodeType.ProcessingInstruction)
885 {
886 switch (reader.LocalName)
887 {
888 case "foreach":
889 ++nestedForeachCount;
890 // Output the foreach statement
891 fragmentBuilder.AppendFormat("<?foreach {0}?>", reader.Value);
892 break;
893
894 case "endforeach":
895 --nestedForeachCount;
896 if (0 != nestedForeachCount)
897 {
898 fragmentBuilder.Append("<?endforeach ?>");
899 }
900 break;
901
902 default:
903 fragmentBuilder.AppendFormat("<?{0} {1}?>", reader.LocalName, reader.Value);
904 break;
905 }
906 }
907 else if (reader.NodeType == XmlNodeType.Element)
908 {
909 fragmentBuilder.Append(reader.ReadOuterXml());
910 continue;
911 }
912 else if (reader.NodeType == XmlNodeType.Whitespace)
913 {
914 // Or output the whitespace
915 fragmentBuilder.Append(reader.Value);
916 }
917 else if (reader.NodeType == XmlNodeType.None)
918 {
919 throw new WixException(WixErrors.ExpectedEndforeach(this.currentLineNumber));
920 }
921
922 reader.Read();
923 }
924
925 using (MemoryStream fragmentStream = new MemoryStream(Encoding.UTF8.GetBytes(fragmentBuilder.ToString())))
926 using (XmlReader loopReader = XmlReader.Create(fragmentStream, FragmentXmlReaderSettings))
927 {
928 // process each iteration, updating the variable's value each time
929 foreach (string varValue in varValues)
930 {
931 // Always overwrite foreach variables.
932 this.core.AddVariable(this.currentLineNumber, varName, varValue, false);
933
934 try
935 {
936 this.PreprocessReader(false, loopReader, container, offset);
937 }
938 catch (XmlException e)
939 {
940 this.UpdateCurrentLineNumber(loopReader, offset);
941 throw new WixException(WixErrors.InvalidXml(this.currentLineNumber, "source", e.Message));
942 }
943
944 fragmentStream.Position = 0; // seek back to the beginning for the next loop.
945 }
946 }
947 }
948
949 /// <summary>
950 /// Processes a pragma processing instruction
951 /// </summary>
952 /// <param name="pragmaText">Text from source.</param>
953 private void PreprocessPragma(string pragmaText, XContainer parent)
954 {
955 Match match = pragmaRegex.Match(pragmaText);
956 SourceLineNumber sourceLineNumbers = this.currentLineNumber;
957
958 if (!match.Success)
959 {
960 throw new WixException(WixErrors.InvalidPreprocessorPragma(sourceLineNumbers, pragmaText));
961 }
962
963 // resolve other variables in the pragma argument(s)
964 string pragmaArgs = this.core.PreprocessString(sourceLineNumbers, match.Groups["pragmaValue"].Value).Trim();
965
966 try
967 {
968 this.core.PreprocessPragma(sourceLineNumbers, match.Groups["pragmaName"].Value.Trim(), pragmaArgs, parent);
969 }
970 catch (Exception e)
971 {
972 throw new WixException(WixErrors.PreprocessorExtensionPragmaFailed(sourceLineNumbers, pragmaText, e.Message));
973 }
974 }
975
976 /// <summary>
977 /// Gets the next token in an expression.
978 /// </summary>
979 /// <param name="originalExpression">Expression to parse.</param>
980 /// <param name="expression">Expression with token removed.</param>
981 /// <param name="stringLiteral">Flag if token is a string literal instead of a variable.</param>
982 /// <returns>Next token.</returns>
983 private string GetNextToken(string originalExpression, ref string expression, out bool stringLiteral)
984 {
985 stringLiteral = false;
986 string token = String.Empty;
987 expression = expression.Trim();
988 if (0 == expression.Length)
989 {
990 return String.Empty;
991 }
992
993 if (expression.StartsWith("\"", StringComparison.Ordinal))
994 {
995 stringLiteral = true;
996 int endingQuotes = expression.IndexOf('\"', 1);
997 if (-1 == endingQuotes)
998 {
999 throw new WixException(WixErrors.UnmatchedQuotesInExpression(this.currentLineNumber, originalExpression));
1000 }
1001
1002 // cut the quotes off the string
1003 token = this.core.PreprocessString(this.currentLineNumber, expression.Substring(1, endingQuotes - 1));
1004
1005 // advance past this string
1006 expression = expression.Substring(endingQuotes + 1).Trim();
1007 }
1008 else if (expression.StartsWith("$(", StringComparison.Ordinal))
1009 {
1010 // Find the ending paren of the expression
1011 int endingParen = -1;
1012 int openedCount = 1;
1013 for (int i = 2; i < expression.Length; i++)
1014 {
1015 if ('(' == expression[i])
1016 {
1017 openedCount++;
1018 }
1019 else if (')' == expression[i])
1020 {
1021 openedCount--;
1022 }
1023
1024 if (openedCount == 0)
1025 {
1026 endingParen = i;
1027 break;
1028 }
1029 }
1030
1031 if (-1 == endingParen)
1032 {
1033 throw new WixException(WixErrors.UnmatchedParenthesisInExpression(this.currentLineNumber, originalExpression));
1034 }
1035 token = expression.Substring(0, endingParen + 1);
1036
1037 // Advance past this variable
1038 expression = expression.Substring(endingParen + 1).Trim();
1039 }
1040 else
1041 {
1042 // Cut the token off at the next equal, space, inequality operator,
1043 // or end of string, whichever comes first
1044 int space = expression.IndexOf(" ", StringComparison.Ordinal);
1045 int equals = expression.IndexOf("=", StringComparison.Ordinal);
1046 int lessThan = expression.IndexOf("<", StringComparison.Ordinal);
1047 int lessThanEquals = expression.IndexOf("<=", StringComparison.Ordinal);
1048 int greaterThan = expression.IndexOf(">", StringComparison.Ordinal);
1049 int greaterThanEquals = expression.IndexOf(">=", StringComparison.Ordinal);
1050 int notEquals = expression.IndexOf("!=", StringComparison.Ordinal);
1051 int equalsNoCase = expression.IndexOf("~=", StringComparison.Ordinal);
1052 int closingIndex;
1053
1054 if (space == -1)
1055 {
1056 space = Int32.MaxValue;
1057 }
1058
1059 if (equals == -1)
1060 {
1061 equals = Int32.MaxValue;
1062 }
1063
1064 if (lessThan == -1)
1065 {
1066 lessThan = Int32.MaxValue;
1067 }
1068
1069 if (lessThanEquals == -1)
1070 {
1071 lessThanEquals = Int32.MaxValue;
1072 }
1073
1074 if (greaterThan == -1)
1075 {
1076 greaterThan = Int32.MaxValue;
1077 }
1078
1079 if (greaterThanEquals == -1)
1080 {
1081 greaterThanEquals = Int32.MaxValue;
1082 }
1083
1084 if (notEquals == -1)
1085 {
1086 notEquals = Int32.MaxValue;
1087 }
1088
1089 if (equalsNoCase == -1)
1090 {
1091 equalsNoCase = Int32.MaxValue;
1092 }
1093
1094 closingIndex = Math.Min(space, Math.Min(equals, Math.Min(lessThan, Math.Min(lessThanEquals, Math.Min(greaterThan, Math.Min(greaterThanEquals, Math.Min(equalsNoCase, notEquals)))))));
1095
1096 if (Int32.MaxValue == closingIndex)
1097 {
1098 closingIndex = expression.Length;
1099 }
1100
1101 // If the index is 0, we hit an operator, so return it
1102 if (0 == closingIndex)
1103 {
1104 // Length 2 operators
1105 if (closingIndex == lessThanEquals || closingIndex == greaterThanEquals || closingIndex == notEquals || closingIndex == equalsNoCase)
1106 {
1107 closingIndex = 2;
1108 }
1109 else // Length 1 operators
1110 {
1111 closingIndex = 1;
1112 }
1113 }
1114
1115 // Cut out the new token
1116 token = expression.Substring(0, closingIndex).Trim();
1117 expression = expression.Substring(closingIndex).Trim();
1118 }
1119
1120 return token;
1121 }
1122
1123 /// <summary>
1124 /// Gets the value for a variable.
1125 /// </summary>
1126 /// <param name="originalExpression">Original expression for error message.</param>
1127 /// <param name="variable">Variable to evaluate.</param>
1128 /// <returns>Value of variable.</returns>
1129 private string EvaluateVariable(string originalExpression, string variable)
1130 {
1131 // By default it's a literal and will only be evaluated if it
1132 // matches the variable format
1133 string varValue = variable;
1134
1135 if (variable.StartsWith("$(", StringComparison.Ordinal))
1136 {
1137 try
1138 {
1139 varValue = this.core.PreprocessString(this.currentLineNumber, variable);
1140 }
1141 catch (ArgumentNullException)
1142 {
1143 // non-existent variables are expected
1144 varValue = null;
1145 }
1146 }
1147 else if (variable.IndexOf("(", StringComparison.Ordinal) != -1 || variable.IndexOf(")", StringComparison.Ordinal) != -1)
1148 {
1149 // make sure it doesn't contain parenthesis
1150 throw new WixException(WixErrors.UnmatchedParenthesisInExpression(this.currentLineNumber, originalExpression));
1151 }
1152 else if (variable.IndexOf("\"", StringComparison.Ordinal) != -1)
1153 {
1154 // shouldn't contain quotes
1155 throw new WixException(WixErrors.UnmatchedQuotesInExpression(this.currentLineNumber, originalExpression));
1156 }
1157
1158 return varValue;
1159 }
1160
1161 /// <summary>
1162 /// Gets the left side value, operator, and right side value of an expression.
1163 /// </summary>
1164 /// <param name="originalExpression">Original expression to evaluate.</param>
1165 /// <param name="expression">Expression modified while processing.</param>
1166 /// <param name="leftValue">Left side value from expression.</param>
1167 /// <param name="operation">Operation in expression.</param>
1168 /// <param name="rightValue">Right side value from expression.</param>
1169 private void GetNameValuePair(string originalExpression, ref string expression, out string leftValue, out string operation, out string rightValue)
1170 {
1171 bool stringLiteral;
1172 leftValue = this.GetNextToken(originalExpression, ref expression, out stringLiteral);
1173
1174 // If it wasn't a string literal, evaluate it
1175 if (!stringLiteral)
1176 {
1177 leftValue = this.EvaluateVariable(originalExpression, leftValue);
1178 }
1179
1180 // Get the operation
1181 operation = this.GetNextToken(originalExpression, ref expression, out stringLiteral);
1182 if (IsOperator(operation))
1183 {
1184 if (stringLiteral)
1185 {
1186 throw new WixException(WixErrors.UnmatchedQuotesInExpression(this.currentLineNumber, originalExpression));
1187 }
1188
1189 rightValue = this.GetNextToken(originalExpression, ref expression, out stringLiteral);
1190
1191 // If it wasn't a string literal, evaluate it
1192 if (!stringLiteral)
1193 {
1194 rightValue = this.EvaluateVariable(originalExpression, rightValue);
1195 }
1196 }
1197 else
1198 {
1199 // Prepend the token back on the expression since it wasn't an operator
1200 // and put the quotes back on the literal if necessary
1201
1202 if (stringLiteral)
1203 {
1204 operation = "\"" + operation + "\"";
1205 }
1206 expression = (operation + " " + expression).Trim();
1207
1208 // If no operator, just check for existence
1209 operation = "";
1210 rightValue = "";
1211 }
1212 }
1213
1214 /// <summary>
1215 /// Evaluates an expression.
1216 /// </summary>
1217 /// <param name="originalExpression">Original expression to evaluate.</param>
1218 /// <param name="expression">Expression modified while processing.</param>
1219 /// <returns>true if expression evaluates to true.</returns>
1220 private bool EvaluateAtomicExpression(string originalExpression, ref string expression)
1221 {
1222 // Quick test to see if the first token is a variable
1223 bool startsWithVariable = expression.StartsWith("$(", StringComparison.Ordinal);
1224
1225 string leftValue;
1226 string rightValue;
1227 string operation;
1228 this.GetNameValuePair(originalExpression, ref expression, out leftValue, out operation, out rightValue);
1229
1230 bool expressionValue = false;
1231
1232 // If the variables don't exist, they were evaluated to null
1233 if (null == leftValue || null == rightValue)
1234 {
1235 if (operation.Length > 0)
1236 {
1237 throw new WixException(WixErrors.ExpectedVariable(this.currentLineNumber, originalExpression));
1238 }
1239
1240 // false expression
1241 }
1242 else if (operation.Length == 0)
1243 {
1244 // There is no right side of the equation.
1245 // If the variable was evaluated, it exists, so the expression is true
1246 if (startsWithVariable)
1247 {
1248 expressionValue = true;
1249 }
1250 else
1251 {
1252 throw new WixException(WixErrors.UnexpectedLiteral(this.currentLineNumber, originalExpression));
1253 }
1254 }
1255 else
1256 {
1257 leftValue = leftValue.Trim();
1258 rightValue = rightValue.Trim();
1259 if ("=" == operation)
1260 {
1261 if (leftValue == rightValue)
1262 {
1263 expressionValue = true;
1264 }
1265 }
1266 else if ("!=" == operation)
1267 {
1268 if (leftValue != rightValue)
1269 {
1270 expressionValue = true;
1271 }
1272 }
1273 else if ("~=" == operation)
1274 {
1275 if (String.Equals(leftValue, rightValue, StringComparison.OrdinalIgnoreCase))
1276 {
1277 expressionValue = true;
1278 }
1279 }
1280 else
1281 {
1282 // Convert the numbers from strings
1283 int rightInt;
1284 int leftInt;
1285 try
1286 {
1287 rightInt = Int32.Parse(rightValue, CultureInfo.InvariantCulture);
1288 leftInt = Int32.Parse(leftValue, CultureInfo.InvariantCulture);
1289 }
1290 catch (FormatException)
1291 {
1292 throw new WixException(WixErrors.IllegalIntegerInExpression(this.currentLineNumber, originalExpression));
1293 }
1294 catch (OverflowException)
1295 {
1296 throw new WixException(WixErrors.IllegalIntegerInExpression(this.currentLineNumber, originalExpression));
1297 }
1298
1299 // Compare the numbers
1300 if ("<" == operation && leftInt < rightInt ||
1301 "<=" == operation && leftInt <= rightInt ||
1302 ">" == operation && leftInt > rightInt ||
1303 ">=" == operation && leftInt >= rightInt)
1304 {
1305 expressionValue = true;
1306 }
1307 }
1308 }
1309
1310 return expressionValue;
1311 }
1312
1313 /// <summary>
1314 /// Gets a sub-expression in parenthesis.
1315 /// </summary>
1316 /// <param name="originalExpression">Original expression to evaluate.</param>
1317 /// <param name="expression">Expression modified while processing.</param>
1318 /// <param name="endSubExpression">Index of end of sub-expression.</param>
1319 /// <returns>Sub-expression in parenthesis.</returns>
1320 private string GetParenthesisExpression(string originalExpression, string expression, out int endSubExpression)
1321 {
1322 endSubExpression = 0;
1323
1324 // if the expression doesn't start with parenthesis, leave it alone
1325 if (!expression.StartsWith("(", StringComparison.Ordinal))
1326 {
1327 return expression;
1328 }
1329
1330 // search for the end of the expression with the matching paren
1331 int openParenIndex = 0;
1332 int closeParenIndex = 1;
1333 while (openParenIndex != -1 && openParenIndex < closeParenIndex)
1334 {
1335 closeParenIndex = expression.IndexOf(')', closeParenIndex);
1336 if (closeParenIndex == -1)
1337 {
1338 throw new WixException(WixErrors.UnmatchedParenthesisInExpression(this.currentLineNumber, originalExpression));
1339 }
1340
1341 if (InsideQuotes(expression, closeParenIndex))
1342 {
1343 // ignore stuff inside quotes (it's a string literal)
1344 }
1345 else
1346 {
1347 // Look to see if there is another open paren before the close paren
1348 // and skip over the open parens while they are in a string literal
1349 do
1350 {
1351 openParenIndex++;
1352 openParenIndex = expression.IndexOf('(', openParenIndex, closeParenIndex - openParenIndex);
1353 }
1354 while (InsideQuotes(expression, openParenIndex));
1355 }
1356
1357 // Advance past the closing paren
1358 closeParenIndex++;
1359 }
1360
1361 endSubExpression = closeParenIndex;
1362
1363 // Return the expression minus the parenthesis
1364 return expression.Substring(1, closeParenIndex - 2);
1365 }
1366
1367 /// <summary>
1368 /// Updates expression based on operation.
1369 /// </summary>
1370 /// <param name="currentValue">State to update.</param>
1371 /// <param name="operation">Operation to apply to current value.</param>
1372 /// <param name="prevResult">Previous result.</param>
1373 private void UpdateExpressionValue(ref bool currentValue, PreprocessorOperation operation, bool prevResult)
1374 {
1375 switch (operation)
1376 {
1377 case PreprocessorOperation.And:
1378 currentValue = currentValue && prevResult;
1379 break;
1380 case PreprocessorOperation.Or:
1381 currentValue = currentValue || prevResult;
1382 break;
1383 case PreprocessorOperation.Not:
1384 currentValue = !currentValue;
1385 break;
1386 default:
1387 throw new WixException(WixErrors.UnexpectedPreprocessorOperator(this.currentLineNumber, operation.ToString()));
1388 }
1389 }
1390
1391 /// <summary>
1392 /// Evaluate an expression.
1393 /// </summary>
1394 /// <param name="expression">Expression to evaluate.</param>
1395 /// <returns>Boolean result of expression.</returns>
1396 private bool EvaluateExpression(string expression)
1397 {
1398 string tmpExpression = expression;
1399 return this.EvaluateExpressionRecurse(expression, ref tmpExpression, PreprocessorOperation.And, true);
1400 }
1401
1402 /// <summary>
1403 /// Recurse through the expression to evaluate if it is true or false.
1404 /// The expression is evaluated left to right.
1405 /// The expression is case-sensitive (converted to upper case) with the
1406 /// following exceptions: variable names and keywords (and, not, or).
1407 /// Comparisons with = and != are string comparisons.
1408 /// Comparisons with inequality operators must be done on valid integers.
1409 ///
1410 /// The operator precedence is:
1411 /// ""
1412 /// ()
1413 /// &lt;, &gt;, &lt;=, &gt;=, =, !=
1414 /// Not
1415 /// And, Or
1416 ///
1417 /// Valid expressions include:
1418 /// not $(var.B) or not $(var.C)
1419 /// (($(var.A))and $(var.B) ="2")or Not((($(var.C))) and $(var.A))
1420 /// (($(var.A)) and $(var.B) = " 3 ") or $(var.C)
1421 /// $(var.A) and $(var.C) = "3" or $(var.C) and $(var.D) = $(env.windir)
1422 /// $(var.A) and $(var.B)>2 or $(var.B) &lt;= 2
1423 /// $(var.A) != "2"
1424 /// </summary>
1425 /// <param name="originalExpression">The original expression</param>
1426 /// <param name="expression">The expression currently being evaluated</param>
1427 /// <param name="prevResultOperation">The operation to apply to this result</param>
1428 /// <param name="prevResult">The previous result to apply to this result</param>
1429 /// <returns>Boolean to indicate if the expression is true or false</returns>
1430 private bool EvaluateExpressionRecurse(string originalExpression, ref string expression, PreprocessorOperation prevResultOperation, bool prevResult)
1431 {
1432 bool expressionValue = false;
1433 expression = expression.Trim();
1434 if (expression.Length == 0)
1435 {
1436 throw new WixException(WixErrors.UnexpectedEmptySubexpression(this.currentLineNumber, originalExpression));
1437 }
1438
1439 // If the expression starts with parenthesis, evaluate it
1440 if (expression.IndexOf('(') == 0)
1441 {
1442 int endSubExpressionIndex;
1443 string subExpression = this.GetParenthesisExpression(originalExpression, expression, out endSubExpressionIndex);
1444 expressionValue = this.EvaluateExpressionRecurse(originalExpression, ref subExpression, PreprocessorOperation.And, true);
1445
1446 // Now get the rest of the expression that hasn't been evaluated
1447 expression = expression.Substring(endSubExpressionIndex).Trim();
1448 }
1449 else
1450 {
1451 // Check for NOT
1452 if (StartsWithKeyword(expression, PreprocessorOperation.Not))
1453 {
1454 expression = expression.Substring(3).Trim();
1455 if (expression.Length == 0)
1456 {
1457 throw new WixException(WixErrors.ExpectedExpressionAfterNot(this.currentLineNumber, originalExpression));
1458 }
1459
1460 expressionValue = this.EvaluateExpressionRecurse(originalExpression, ref expression, PreprocessorOperation.Not, true);
1461 }
1462 else // Expect a literal
1463 {
1464 expressionValue = this.EvaluateAtomicExpression(originalExpression, ref expression);
1465
1466 // Expect the literal that was just evaluated to already be cut off
1467 }
1468 }
1469 this.UpdateExpressionValue(ref expressionValue, prevResultOperation, prevResult);
1470
1471 // If there's still an expression left, it must start with AND or OR.
1472 if (expression.Trim().Length > 0)
1473 {
1474 if (StartsWithKeyword(expression, PreprocessorOperation.And))
1475 {
1476 expression = expression.Substring(3);
1477 return this.EvaluateExpressionRecurse(originalExpression, ref expression, PreprocessorOperation.And, expressionValue);
1478 }
1479 else if (StartsWithKeyword(expression, PreprocessorOperation.Or))
1480 {
1481 expression = expression.Substring(2);
1482 return this.EvaluateExpressionRecurse(originalExpression, ref expression, PreprocessorOperation.Or, expressionValue);
1483 }
1484 else
1485 {
1486 throw new WixException(WixErrors.InvalidSubExpression(this.currentLineNumber, expression, originalExpression));
1487 }
1488 }
1489
1490 return expressionValue;
1491 }
1492
1493 /// <summary>
1494 /// Update the current line number with the reader's current state.
1495 /// </summary>
1496 /// <param name="reader">The xml reader for the preprocessor.</param>
1497 /// <param name="offset">This is the artificial offset of the line numbers from the reader. Used for the foreach processing.</param>
1498 private void UpdateCurrentLineNumber(XmlReader reader, int offset)
1499 {
1500 IXmlLineInfo lineInfoReader = reader as IXmlLineInfo;
1501 if (null != lineInfoReader)
1502 {
1503 int newLine = lineInfoReader.LineNumber + offset;
1504
1505 if (this.currentLineNumber.LineNumber != newLine)
1506 {
1507 this.currentLineNumber = new SourceLineNumber(this.currentLineNumber.FileName, newLine);
1508 }
1509 }
1510 }
1511
1512 /// <summary>
1513 /// Pushes a file name on the stack of included files.
1514 /// </summary>
1515 /// <param name="fileName">Name to push on to the stack of included files.</param>
1516 private void PushInclude(string fileName)
1517 {
1518 if (1023 < this.currentFileStack.Count)
1519 {
1520 throw new WixException(WixErrors.TooDeeplyIncluded(this.currentLineNumber, this.currentFileStack.Count));
1521 }
1522
1523 this.currentFileStack.Push(fileName);
1524 this.sourceStack.Push(this.currentLineNumber);
1525 this.currentLineNumber = new SourceLineNumber(fileName);
1526 this.includeNextStack.Push(true);
1527 }
1528
1529 /// <summary>
1530 /// Pops a file name from the stack of included files.
1531 /// </summary>
1532 private void PopInclude()
1533 {
1534 this.currentLineNumber = this.sourceStack.Pop();
1535
1536 this.currentFileStack.Pop();
1537 this.includeNextStack.Pop();
1538 }
1539
1540 /// <summary>
1541 /// Go through search paths, looking for a matching include file.
1542 /// Start the search in the directory of the source file, then go
1543 /// through the search paths in the order given on the command line
1544 /// (leftmost first, ...).
1545 /// </summary>
1546 /// <param name="includePath">User-specified path to the included file (usually just the file name).</param>
1547 /// <returns>Returns a FileInfo for the found include file, or null if the file cannot be found.</returns>
1548 private string GetIncludeFile(string includePath)
1549 {
1550 string finalIncludePath = null;
1551
1552 includePath = includePath.Trim();
1553
1554 // remove quotes (only if they match)
1555 if ((includePath.StartsWith("\"", StringComparison.Ordinal) && includePath.EndsWith("\"", StringComparison.Ordinal)) ||
1556 (includePath.StartsWith("'", StringComparison.Ordinal) && includePath.EndsWith("'", StringComparison.Ordinal)))
1557 {
1558 includePath = includePath.Substring(1, includePath.Length - 2);
1559 }
1560
1561 // check if the include file is a full path
1562 if (Path.IsPathRooted(includePath))
1563 {
1564 if (File.Exists(includePath))
1565 {
1566 finalIncludePath = includePath;
1567 }
1568 }
1569 else // relative path
1570 {
1571 // build a string to test the directory containing the source file first
1572 string currentFolder = this.currentFileStack.Peek();
1573 string includeTestPath = Path.Combine(Path.GetDirectoryName(currentFolder) ?? String.Empty, includePath);
1574
1575 // test the source file directory
1576 if (File.Exists(includeTestPath))
1577 {
1578 finalIncludePath = includeTestPath;
1579 }
1580 else // test all search paths in the order specified on the command line
1581 {
1582 foreach (string includeSearchPath in this.IncludeSearchPaths)
1583 {
1584 // if the path exists, we have found the final string
1585 includeTestPath = Path.Combine(includeSearchPath, includePath);
1586 if (File.Exists(includeTestPath))
1587 {
1588 finalIncludePath = includeTestPath;
1589 break;
1590 }
1591 }
1592 }
1593 }
1594
1595 return finalIncludePath;
1596 }
1597 }
1598}
diff --git a/src/WixToolset.Core/PreprocessorCore.cs b/src/WixToolset.Core/PreprocessorCore.cs
new file mode 100644
index 00000000..b58fc80c
--- /dev/null
+++ b/src/WixToolset.Core/PreprocessorCore.cs
@@ -0,0 +1,560 @@
1// Copyright (c) .NET Foundation and 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.Collections.Generic;
7 using System.IO;
8 using System.Text;
9 using System.Xml.Linq;
10 using WixToolset.Data;
11 using WixToolset.Extensibility;
12
13 /// <summary>
14 /// The preprocessor core.
15 /// </summary>
16 internal class PreprocessorCore : IPreprocessorCore
17 {
18 private static readonly char[] variableSplitter = new char[] { '.' };
19 private static readonly char[] argumentSplitter = new char[] { ',' };
20
21 private Platform currentPlatform;
22 private Dictionary<string, IPreprocessorExtension> extensionsByPrefix;
23 private string sourceFile;
24 private IDictionary<string, string> variables;
25
26 /// <summary>
27 /// Instantiate a new PreprocessorCore.
28 /// </summary>
29 /// <param name="extensionsByPrefix">The extensions indexed by their prefixes.</param>
30 /// <param name="messageHandler">The message handler.</param>
31 /// <param name="sourceFile">The source file being preprocessed.</param>
32 /// <param name="variables">The variables defined prior to preprocessing.</param>
33 internal PreprocessorCore(Dictionary<string, IPreprocessorExtension> extensionsByPrefix, string sourceFile, IDictionary<string, string> variables)
34 {
35 this.extensionsByPrefix = extensionsByPrefix;
36 this.sourceFile = String.IsNullOrEmpty(sourceFile) ? null : Path.GetFullPath(sourceFile);
37
38 this.variables = new Dictionary<string, string>();
39 foreach (var entry in variables)
40 {
41 this.AddVariable(null, entry.Key, entry.Value);
42 }
43 }
44
45 /// <summary>
46 /// Event for resolved variables.
47 /// </summary>
48 private event ResolvedVariableEventHandler ResolvedVariable;
49
50 /// <summary>
51 /// Sets event for ResolvedVariableEventHandler.
52 /// </summary>
53 public ResolvedVariableEventHandler ResolvedVariableHandler
54 {
55 set { this.ResolvedVariable = value; }
56 }
57
58 /// <summary>
59 /// Gets or sets the platform which the compiler will use when defaulting 64-bit attributes and elements.
60 /// </summary>
61 /// <value>The platform which the compiler will use when defaulting 64-bit attributes and elements.</value>
62 public Platform CurrentPlatform
63 {
64 get { return this.currentPlatform; }
65 set { this.currentPlatform = value; }
66 }
67
68 /// <summary>
69 /// Gets whether the core encountered an error while processing.
70 /// </summary>
71 /// <value>Flag if core encountered an error during processing.</value>
72 public bool EncounteredError
73 {
74 get { return Messaging.Instance.EncounteredError; }
75 }
76
77 /// <summary>
78 /// Replaces parameters in the source text.
79 /// </summary>
80 /// <param name="sourceLineNumbers">The source line information for the function.</param>
81 /// <param name="value">Text that may contain parameters to replace.</param>
82 /// <returns>Text after parameters have been replaced.</returns>
83 public string PreprocessString(SourceLineNumber sourceLineNumbers, string value)
84 {
85 StringBuilder sb = new StringBuilder();
86 int currentPosition = 0;
87 int end = 0;
88
89 while (-1 != (currentPosition = value.IndexOf('$', end)))
90 {
91 if (end < currentPosition)
92 {
93 sb.Append(value, end, currentPosition - end);
94 }
95
96 end = currentPosition + 1;
97 string remainder = value.Substring(end);
98 if (remainder.StartsWith("$", StringComparison.Ordinal))
99 {
100 sb.Append("$");
101 end++;
102 }
103 else if (remainder.StartsWith("(loc.", StringComparison.Ordinal))
104 {
105 currentPosition = remainder.IndexOf(')');
106 if (-1 == currentPosition)
107 {
108 this.OnMessage(WixErrors.InvalidPreprocessorVariable(sourceLineNumbers, remainder));
109 break;
110 }
111
112 sb.Append("$"); // just put the resource reference back as was
113 sb.Append(remainder, 0, currentPosition + 1);
114
115 end += currentPosition + 1;
116 }
117 else if (remainder.StartsWith("(", StringComparison.Ordinal))
118 {
119 int openParenCount = 1;
120 int closingParenCount = 0;
121 bool isFunction = false;
122 bool foundClosingParen = false;
123
124 // find the closing paren
125 int closingParenPosition;
126 for (closingParenPosition = 1; closingParenPosition < remainder.Length; closingParenPosition++)
127 {
128 switch (remainder[closingParenPosition])
129 {
130 case '(':
131 openParenCount++;
132 isFunction = true;
133 break;
134 case ')':
135 closingParenCount++;
136 break;
137 }
138 if (openParenCount == closingParenCount)
139 {
140 foundClosingParen = true;
141 break;
142 }
143 }
144
145 // move the currentPosition to the closing paren
146 currentPosition += closingParenPosition;
147
148 if (!foundClosingParen)
149 {
150 if (isFunction)
151 {
152 this.OnMessage(WixErrors.InvalidPreprocessorFunction(sourceLineNumbers, remainder));
153 break;
154 }
155 else
156 {
157 this.OnMessage(WixErrors.InvalidPreprocessorVariable(sourceLineNumbers, remainder));
158 break;
159 }
160 }
161
162 string subString = remainder.Substring(1, closingParenPosition - 1);
163 string result = null;
164 if (isFunction)
165 {
166 result = this.EvaluateFunction(sourceLineNumbers, subString);
167 }
168 else
169 {
170 result = this.GetVariableValue(sourceLineNumbers, subString, false);
171 }
172
173 if (null == result)
174 {
175 if (isFunction)
176 {
177 this.OnMessage(WixErrors.UndefinedPreprocessorFunction(sourceLineNumbers, subString));
178 break;
179 }
180 else
181 {
182 this.OnMessage(WixErrors.UndefinedPreprocessorVariable(sourceLineNumbers, subString));
183 break;
184 }
185 }
186 else
187 {
188 if (!isFunction)
189 {
190 this.OnResolvedVariable(new ResolvedVariableEventArgs(sourceLineNumbers, subString, result));
191 }
192 }
193 sb.Append(result);
194 end += closingParenPosition + 1;
195 }
196 else // just a floating "$" so put it in the final string (i.e. leave it alone) and keep processing
197 {
198 sb.Append('$');
199 }
200 }
201
202 if (end < value.Length)
203 {
204 sb.Append(value.Substring(end));
205 }
206
207 return sb.ToString();
208 }
209
210 /// <summary>
211 /// Evaluate a Pragma.
212 /// </summary>
213 /// <param name="sourceLineNumbers">The source line information for the function.</param>
214 /// <param name="pragmaName">The pragma's full name (<prefix>.<pragma>).</param>
215 /// <param name="args">The arguments to the pragma.</param>
216 /// <param name="parent">The parent element of the pragma.</param>
217 public void PreprocessPragma(SourceLineNumber sourceLineNumbers, string pragmaName, string args, XContainer parent)
218 {
219 string[] prefixParts = pragmaName.Split(variableSplitter, 2);
220 // Check to make sure there are 2 parts and neither is an empty string.
221 if (2 != prefixParts.Length)
222 {
223 throw new WixException(WixErrors.InvalidPreprocessorPragma(sourceLineNumbers, pragmaName));
224 }
225 string prefix = prefixParts[0];
226 string pragma = prefixParts[1];
227
228 if (String.IsNullOrEmpty(prefix) || String.IsNullOrEmpty(pragma))
229 {
230 throw new WixException(WixErrors.InvalidPreprocessorPragma(sourceLineNumbers, pragmaName));
231 }
232
233 switch (prefix)
234 {
235 case "wix":
236 switch (pragma)
237 {
238 // Add any core defined pragmas here
239 default:
240 this.OnMessage(WixWarnings.PreprocessorUnknownPragma(sourceLineNumbers, pragmaName));
241 break;
242 }
243 break;
244 default:
245 PreprocessorExtension extension = (PreprocessorExtension)this.extensionsByPrefix[prefix];
246 if (null == extension || !extension.ProcessPragma(sourceLineNumbers, prefix, pragma, args, parent))
247 {
248 this.OnMessage(WixWarnings.PreprocessorUnknownPragma(sourceLineNumbers, pragmaName));
249 }
250 break;
251 }
252 }
253
254 /// <summary>
255 /// Evaluate a function.
256 /// </summary>
257 /// <param name="sourceLineNumbers">The source line information for the function.</param>
258 /// <param name="function">The function expression including the prefix and name.</param>
259 /// <returns>The function value.</returns>
260 public string EvaluateFunction(SourceLineNumber sourceLineNumbers, string function)
261 {
262 string[] prefixParts = function.Split(variableSplitter, 2);
263 // Check to make sure there are 2 parts and neither is an empty string.
264 if (2 != prefixParts.Length || 0 >= prefixParts[0].Length || 0 >= prefixParts[1].Length)
265 {
266 throw new WixException(WixErrors.InvalidPreprocessorFunction(sourceLineNumbers, function));
267 }
268 string prefix = prefixParts[0];
269
270 string[] functionParts = prefixParts[1].Split(new char[] { '(' }, 2);
271 // Check to make sure there are 2 parts, neither is an empty string, and the second part ends with a closing paren.
272 if (2 != functionParts.Length || 0 >= functionParts[0].Length || 0 >= functionParts[1].Length || !functionParts[1].EndsWith(")", StringComparison.Ordinal))
273 {
274 throw new WixException(WixErrors.InvalidPreprocessorFunction(sourceLineNumbers, function));
275 }
276 string functionName = functionParts[0];
277
278 // Remove the trailing closing paren.
279 string allArgs = functionParts[1].Substring(0, functionParts[1].Length - 1);
280
281 // Parse the arguments and preprocess them.
282 string[] args = allArgs.Split(argumentSplitter);
283 for (int i = 0; i < args.Length; i++)
284 {
285 args[i] = this.PreprocessString(sourceLineNumbers, args[i].Trim());
286 }
287
288 string result = this.EvaluateFunction(sourceLineNumbers, prefix, functionName, args);
289
290 // If the function didn't evaluate, try to evaluate the original value as a variable to support
291 // the use of open and closed parens inside variable names. Example: $(env.ProgramFiles(x86)) should resolve.
292 if (null == result)
293 {
294 result = this.GetVariableValue(sourceLineNumbers, function, false);
295 }
296
297 return result;
298 }
299
300 /// <summary>
301 /// Evaluate a function.
302 /// </summary>
303 /// <param name="sourceLineNumbers">The source line information for the function.</param>
304 /// <param name="prefix">The function prefix.</param>
305 /// <param name="function">The function name.</param>
306 /// <param name="args">The arguments for the function.</param>
307 /// <returns>The function value or null if the function is not defined.</returns>
308 public string EvaluateFunction(SourceLineNumber sourceLineNumbers, string prefix, string function, string[] args)
309 {
310 if (String.IsNullOrEmpty(prefix))
311 {
312 throw new ArgumentNullException("prefix");
313 }
314
315 if (String.IsNullOrEmpty(function))
316 {
317 throw new ArgumentNullException("function");
318 }
319
320 switch (prefix)
321 {
322 case "fun":
323 switch (function)
324 {
325 case "AutoVersion":
326 // Make sure the base version is specified
327 if (args.Length == 0 || String.IsNullOrEmpty(args[0]))
328 {
329 throw new WixException(WixErrors.InvalidPreprocessorFunctionAutoVersion(sourceLineNumbers));
330 }
331
332 // Build = days since 1/1/2000; Revision = seconds since midnight / 2
333 DateTime now = DateTime.UtcNow;
334 TimeSpan build = now - new DateTime(2000, 1, 1);
335 TimeSpan revision = now - new DateTime(now.Year, now.Month, now.Day);
336
337 return String.Join(".", args[0], (int)build.TotalDays, (int)(revision.TotalSeconds / 2));
338
339 default:
340 return null;
341 }
342 default:
343 PreprocessorExtension extension = (PreprocessorExtension)this.extensionsByPrefix[prefix];
344 if (null != extension)
345 {
346 try
347 {
348 return extension.EvaluateFunction(prefix, function, args);
349 }
350 catch (Exception e)
351 {
352 throw new WixException(WixErrors.PreprocessorExtensionEvaluateFunctionFailed(sourceLineNumbers, prefix, function, String.Join(",", args), e.Message));
353 }
354 }
355 else
356 {
357 return null;
358 }
359 }
360 }
361
362 /// <summary>
363 /// Get the value of a variable expression like var.name.
364 /// </summary>
365 /// <param name="sourceLineNumbers">The source line information for the variable.</param>
366 /// <param name="variable">The variable expression including the optional prefix and name.</param>
367 /// <param name="allowMissingPrefix">true to allow the variable prefix to be missing.</param>
368 /// <returns>The variable value.</returns>
369 public string GetVariableValue(SourceLineNumber sourceLineNumbers, string variable, bool allowMissingPrefix)
370 {
371 // Strip the "$(" off the front.
372 if (variable.StartsWith("$(", StringComparison.Ordinal))
373 {
374 variable = variable.Substring(2);
375 }
376
377 string[] parts = variable.Split(variableSplitter, 2);
378
379 if (1 == parts.Length) // missing prefix
380 {
381 if (allowMissingPrefix)
382 {
383 return this.GetVariableValue(sourceLineNumbers, "var", parts[0]);
384 }
385 else
386 {
387 throw new WixException(WixErrors.InvalidPreprocessorVariable(sourceLineNumbers, variable));
388 }
389 }
390 else
391 {
392 // check for empty variable name
393 if (0 < parts[1].Length)
394 {
395 string result = this.GetVariableValue(sourceLineNumbers, parts[0], parts[1]);
396
397 // 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
398 if (null == result && allowMissingPrefix && variable.Contains("."))
399 {
400 result = this.GetVariableValue(sourceLineNumbers, "var", variable);
401 }
402
403 return result;
404 }
405 else
406 {
407 throw new WixException(WixErrors.InvalidPreprocessorVariable(sourceLineNumbers, variable));
408 }
409 }
410 }
411
412 /// <summary>
413 /// Get the value of a variable.
414 /// </summary>
415 /// <param name="sourceLineNumbers">The source line information for the function.</param>
416 /// <param name="prefix">The variable prefix.</param>
417 /// <param name="name">The variable name.</param>
418 /// <returns>The variable value or null if the variable is not set.</returns>
419 public string GetVariableValue(SourceLineNumber sourceLineNumbers, string prefix, string name)
420 {
421 if (String.IsNullOrEmpty(prefix))
422 {
423 throw new ArgumentNullException("prefix");
424 }
425
426 if (String.IsNullOrEmpty(name))
427 {
428 throw new ArgumentNullException("name");
429 }
430
431 switch (prefix)
432 {
433 case "env":
434 return Environment.GetEnvironmentVariable(name);
435 case "sys":
436 switch (name)
437 {
438 case "CURRENTDIR":
439 return String.Concat(Directory.GetCurrentDirectory(), Path.DirectorySeparatorChar);
440 case "SOURCEFILEDIR":
441 return String.Concat(Path.GetDirectoryName(sourceLineNumbers.FileName), Path.DirectorySeparatorChar);
442 case "SOURCEFILEPATH":
443 return sourceLineNumbers.FileName;
444 case "PLATFORM":
445 this.OnMessage(WixWarnings.DeprecatedPreProcVariable(sourceLineNumbers, "$(sys.PLATFORM)", "$(sys.BUILDARCH)"));
446
447 goto case "BUILDARCH";
448
449 case "BUILDARCH":
450 switch (this.currentPlatform)
451 {
452 case Platform.X86:
453 return "x86";
454 case Platform.X64:
455 return "x64";
456 case Platform.IA64:
457 return "ia64";
458 case Platform.ARM:
459 return "arm";
460 default:
461 throw new ArgumentException(WixStrings.EXP_UnknownPlatformEnum, this.currentPlatform.ToString());
462 }
463 default:
464 return null;
465 }
466 case "var":
467 string result = null;
468 return this.variables.TryGetValue(name, out result) ? result : null;
469 default:
470 PreprocessorExtension extension = (PreprocessorExtension)this.extensionsByPrefix[prefix];
471 if (null != extension)
472 {
473 try
474 {
475 return extension.GetVariableValue(prefix, name);
476 }
477 catch (Exception e)
478 {
479 throw new WixException(WixErrors.PreprocessorExtensionGetVariableValueFailed(sourceLineNumbers, prefix, name, e.Message));
480 }
481 }
482 else
483 {
484 return null;
485 }
486 }
487 }
488
489 /// <summary>
490 /// Sends a message to the message delegate if there is one.
491 /// </summary>
492 /// <param name="mea">Message event arguments.</param>
493 public void OnMessage(MessageEventArgs e)
494 {
495 Messaging.Instance.OnMessage(e);
496 }
497
498 /// <summary>
499 /// Sends resolved variable to delegate if there is one.
500 /// </summary>
501 /// <param name="mea">Message event arguments.</param>
502 public void OnResolvedVariable(ResolvedVariableEventArgs mea)
503 {
504 if (null != this.ResolvedVariable)
505 {
506 this.ResolvedVariable(this, mea);
507 }
508 }
509
510 /// <summary>
511 /// Add a variable.
512 /// </summary>
513 /// <param name="sourceLineNumbers">The source line information of the variable.</param>
514 /// <param name="name">The variable name.</param>
515 /// <param name="value">The variable value.</param>
516 internal void AddVariable(SourceLineNumber sourceLineNumbers, string name, string value)
517 {
518 this.AddVariable(sourceLineNumbers, name, value, true);
519 }
520
521 /// <summary>
522 /// Add a variable.
523 /// </summary>
524 /// <param name="sourceLineNumbers">The source line information of the variable.</param>
525 /// <param name="name">The variable name.</param>
526 /// <param name="value">The variable value.</param>
527 /// <param name="overwrite">Set to true to show variable overwrite warning.</param>
528 internal void AddVariable(SourceLineNumber sourceLineNumbers, string name, string value, bool showWarning)
529 {
530 string currentValue = this.GetVariableValue(sourceLineNumbers, "var", name);
531
532 if (null == currentValue)
533 {
534 this.variables.Add(name, value);
535 }
536 else
537 {
538 if (showWarning)
539 {
540 this.OnMessage(WixWarnings.VariableDeclarationCollision(sourceLineNumbers, name, value, currentValue));
541 }
542
543 this.variables[name] = value;
544 }
545 }
546
547 /// <summary>
548 /// Remove a variable.
549 /// </summary>
550 /// <param name="sourceLineNumbers">The source line information of the variable.</param>
551 /// <param name="name">The variable name.</param>
552 internal void RemoveVariable(SourceLineNumber sourceLineNumbers, string name)
553 {
554 if (!this.variables.Remove(name))
555 {
556 this.OnMessage(WixErrors.CannotReundefineVariable(sourceLineNumbers, name));
557 }
558 }
559 }
560}
diff --git a/src/WixToolset.Core/ProcessedStreamEventHandler.cs b/src/WixToolset.Core/ProcessedStreamEventHandler.cs
new file mode 100644
index 00000000..de6b5d1f
--- /dev/null
+++ b/src/WixToolset.Core/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
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="ea">Arguments for the preprocessed stream event.</param>
13 public delegate void ProcessedStreamEventHandler(object sender, ProcessedStreamEventArgs e);
14
15 /// <summary>
16 /// Event args for preprocessed stream event.
17 /// </summary>
18 public 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; private set; }
36
37 /// <summary>
38 /// Gets the preprocessed output stream.
39 /// </summary>
40 /// <value>The the preprocessed output stream.</value>
41 public XDocument Document { get; private set; }
42 }
43}
diff --git a/src/WixToolset.Core/ProvidesDependency.cs b/src/WixToolset.Core/ProvidesDependency.cs
new file mode 100644
index 00000000..ea96b5c8
--- /dev/null
+++ b/src/WixToolset.Core/ProvidesDependency.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
4{
5 using System;
6 using System.Xml;
7 using WixToolset.Data;
8
9 /// <summary>
10 /// Represents an authored or imported dependency provider.
11 /// </summary>
12 internal sealed class ProvidesDependency
13 {
14 /// <summary>
15 /// Creates a new instance of the <see cref="ProviderDependency"/> class from a <see cref="Row"/>.
16 /// </summary>
17 /// <param name="row">The <see cref="Row"/> from which data is imported.</param>
18 internal ProvidesDependency(Row row)
19 : this((string)row[2], (string)row[3], (string)row[4], (int?)row[5])
20 {
21 }
22
23 /// <summary>
24 /// Creates a new instance of the <see cref="ProviderDependency"/> class.
25 /// </summary>
26 /// <param name="key">The unique key of the dependency.</param>
27 /// <param name="attributes">Additional attributes for the dependency.</param>
28 internal ProvidesDependency(string key, string version, string displayName, int? attributes)
29 {
30 this.Key = key;
31 this.Version = version;
32 this.DisplayName = displayName;
33 this.Attributes = attributes;
34 }
35
36 /// <summary>
37 /// Gets or sets the unique key of the package provider.
38 /// </summary>
39 internal string Key { get; set; }
40
41 /// <summary>
42 /// Gets or sets the version of the package provider.
43 /// </summary>
44 internal string Version { get; set; }
45
46 /// <summary>
47 /// Gets or sets the display name of the package provider.
48 /// </summary>
49 internal string DisplayName { get; set; }
50
51 /// <summary>
52 /// Gets or sets the attributes for the dependency.
53 /// </summary>
54 internal int? Attributes { get; set; }
55
56 /// <summary>
57 /// Gets or sets whether the dependency was imported from the package.
58 /// </summary>
59 internal bool Imported { get; set; }
60
61 /// <summary>
62 /// Gets whether certain properties are the same.
63 /// </summary>
64 /// <param name="other">Another <see cref="ProvidesDependency"/> to compare.</param>
65 /// <remarks>This is not the same as object equality, but only checks a subset of properties
66 /// to determine if the objects are similar and could be merged into a collection.</remarks>
67 /// <returns>True if certain properties are the same.</returns>
68 internal bool Equals(ProvidesDependency other)
69 {
70 if (null != other)
71 {
72 return this.Key == other.Key &&
73 this.Version == other.Version &&
74 this.DisplayName == other.DisplayName;
75 }
76
77 return false;
78 }
79
80 /// <summary>
81 /// Writes the dependency to the bundle XML manifest.
82 /// </summary>
83 /// <param name="writer">The <see cref="XmlTextWriter"/> for the bundle XML manifest.</param>
84 internal void WriteXml(XmlTextWriter writer)
85 {
86 writer.WriteStartElement("Provides");
87 writer.WriteAttributeString("Key", this.Key);
88
89 if (!String.IsNullOrEmpty(this.Version))
90 {
91 writer.WriteAttributeString("Version", this.Version);
92 }
93
94 if (!String.IsNullOrEmpty(this.DisplayName))
95 {
96 writer.WriteAttributeString("DisplayName", this.DisplayName);
97 }
98
99 if (this.Imported)
100 {
101 // The package dependency was explicitly authored into the manifest.
102 writer.WriteAttributeString("Imported", "yes");
103 }
104
105 writer.WriteEndElement();
106 }
107 }
108}
diff --git a/src/WixToolset.Core/ProvidesDependencyCollection.cs b/src/WixToolset.Core/ProvidesDependencyCollection.cs
new file mode 100644
index 00000000..a777afb0
--- /dev/null
+++ b/src/WixToolset.Core/ProvidesDependencyCollection.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 WixToolset
4{
5 using System;
6 using System.Collections.ObjectModel;
7
8 /// <summary>
9 /// A case-insensitive collection of unique <see cref="ProvidesDependency"/> objects.
10 /// </summary>
11 internal sealed class ProvidesDependencyCollection : KeyedCollection<string, ProvidesDependency>
12 {
13 /// <summary>
14 /// Creates a case-insensitive collection of unique <see cref="ProvidesDependency"/> objects.
15 /// </summary>
16 internal ProvidesDependencyCollection()
17 : base(StringComparer.InvariantCultureIgnoreCase)
18 {
19 }
20
21 /// <summary>
22 /// Adds the <see cref="ProvidesDependency"/> to the collection if it doesn't already exist.
23 /// </summary>
24 /// <param name="dependency">The <see cref="ProvidesDependency"/> to add to the collection.</param>
25 /// <returns>True if the <see cref="ProvidesDependency"/> was added to the collection; otherwise, false.</returns>
26 /// <exception cref="ArgumentNullException">The <paramref name="dependency"/> parameter is null.</exception>
27 internal bool Merge(ProvidesDependency dependency)
28 {
29 if (null == dependency)
30 {
31 throw new ArgumentNullException("dependency");
32 }
33
34 // If the dependency key is already in the collection, verify equality for a subset of properties.
35 if (this.Contains(dependency.Key))
36 {
37 ProvidesDependency current = this[dependency.Key];
38 if (!current.Equals(dependency))
39 {
40 return false;
41 }
42 }
43
44 base.Add(dependency);
45 return true;
46 }
47
48 /// <summary>
49 /// Gets the <see cref="ProvidesDependency.Key"/> for the <paramref name="dependency"/>.
50 /// </summary>
51 /// <param name="dependency">The dependency to index.</param>
52 /// <exception cref="ArgumentNullException">The <paramref name="dependency"/> parameter is null.</exception>
53 /// <returns>The <see cref="ProvidesDependency.Key"/> for the <paramref name="dependency"/>.</returns>
54 protected override string GetKeyForItem(ProvidesDependency dependency)
55 {
56 if (null == dependency)
57 {
58 throw new ArgumentNullException("dependency");
59 }
60
61 return dependency.Key;
62 }
63 }
64}
diff --git a/src/WixToolset.Core/ResolvedVariableEventHandler.cs b/src/WixToolset.Core/ResolvedVariableEventHandler.cs
new file mode 100644
index 00000000..232ad9e4
--- /dev/null
+++ b/src/WixToolset.Core/ResolvedVariableEventHandler.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
4{
5 using System;
6 using System.Text;
7 using WixToolset.Data;
8
9 public delegate void ResolvedVariableEventHandler(object sender, ResolvedVariableEventArgs e);
10
11 public class ResolvedVariableEventArgs : EventArgs
12 {
13 private SourceLineNumber sourceLineNumbers;
14 private string variableName;
15 private string variableValue;
16
17 public ResolvedVariableEventArgs(SourceLineNumber sourceLineNumbers, string variableName, string variableValue)
18 {
19 this.sourceLineNumbers = sourceLineNumbers;
20 this.variableName = variableName;
21 this.variableValue = variableValue;
22 }
23
24 public SourceLineNumber SourceLineNumbers
25 {
26 get { return this.sourceLineNumbers; }
27 }
28
29 public string VariableName
30 {
31 get { return this.variableName; }
32 }
33
34 public string VariableValue
35 {
36 get { return this.variableValue; }
37 }
38 }
39}
diff --git a/src/WixToolset.Core/SourceFile.cs b/src/WixToolset.Core/SourceFile.cs
new file mode 100644
index 00000000..3b1e386a
--- /dev/null
+++ b/src/WixToolset.Core/SourceFile.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 System.IO;
6
7 internal class SourceFile
8 {
9 public SourceFile(string sourcePath, string outputPath)
10 {
11 this.SourcePath = sourcePath;
12 this.OutputPath = outputPath;
13 }
14
15 public string OutputPath { get; set; }
16
17 public string SourcePath { get; set; }
18
19 public Stream Stream { get; set; }
20 }
21}
diff --git a/src/WixToolset.Core/Unbinder.cs b/src/WixToolset.Core/Unbinder.cs
new file mode 100644
index 00000000..744d5536
--- /dev/null
+++ b/src/WixToolset.Core/Unbinder.cs
@@ -0,0 +1,1491 @@
1// Copyright (c) .NET Foundation and 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.ComponentModel;
11 using System.Globalization;
12 using System.IO;
13 using System.Linq;
14 using System.Text.RegularExpressions;
15 using WixToolset.Bind;
16 using WixToolset.Bind.Bundles;
17 using WixToolset.Cab;
18 using WixToolset.Data;
19 using WixToolset.Data.Rows;
20 using WixToolset.Extensibility;
21 using WixToolset.Msi;
22 using WixToolset.Core.Native;
23 using WixToolset.Ole32;
24
25 /// <summary>
26 /// Unbinder core of the WiX toolset.
27 /// </summary>
28 public sealed class Unbinder : IMessageHandler
29 {
30 private string emptyFile;
31 private bool isAdminImage;
32 private int sectionCount;
33 private bool suppressDemodularization;
34 private bool suppressExtractCabinets;
35 private TableDefinitionCollection tableDefinitions;
36 private ArrayList unbinderExtensions;
37 // private TempFileCollection tempFiles;
38
39 /// <summary>
40 /// Creates a new unbinder object with a default set of table definitions.
41 /// </summary>
42 public Unbinder()
43 {
44 this.tableDefinitions = new TableDefinitionCollection(WindowsInstallerStandard.GetTableDefinitions());
45 this.unbinderExtensions = new ArrayList();
46 }
47
48 /// <summary>
49 /// Gets or sets whether the input msi is an admin image.
50 /// </summary>
51 /// <value>Set to true if the input msi is part of an admin image.</value>
52 public bool IsAdminImage
53 {
54 get { return this.isAdminImage; }
55 set { this.isAdminImage = value; }
56 }
57
58 /// <summary>
59 /// Gets or sets the option to suppress demodularizing values.
60 /// </summary>
61 /// <value>The option to suppress demodularizing values.</value>
62 public bool SuppressDemodularization
63 {
64 get { return this.suppressDemodularization; }
65 set { this.suppressDemodularization = value; }
66 }
67
68 /// <summary>
69 /// Gets or sets the option to suppress extracting cabinets.
70 /// </summary>
71 /// <value>The option to suppress extracting cabinets.</value>
72 public bool SuppressExtractCabinets
73 {
74 get { return this.suppressExtractCabinets; }
75 set { this.suppressExtractCabinets = value; }
76 }
77
78 /// <summary>
79 /// Gets or sets the temporary path for the Binder. If left null, the binder
80 /// will use %TEMP% environment variable.
81 /// </summary>
82 /// <value>Path to temp files.</value>
83 public string TempFilesLocation
84 {
85 get
86 {
87 // return null == this.tempFiles ? String.Empty : this.tempFiles.BasePath;
88 return Path.GetTempPath();
89 }
90
91 // set
92 // {
93 // if (null == value)
94 // {
95 // this.tempFiles = new TempFileCollection();
96 // }
97 // else
98 // {
99 // this.tempFiles = new TempFileCollection(value);
100 // }
101 // }
102 }
103
104 /// <summary>
105 /// Adds extension data.
106 /// </summary>
107 /// <param name="data">The extension data to add.</param>
108 public void AddExtensionData(IExtensionData data)
109 {
110 if (null != data.TableDefinitions)
111 {
112 foreach (TableDefinition tableDefinition in data.TableDefinitions)
113 {
114 if (!this.tableDefinitions.Contains(tableDefinition.Name))
115 {
116 this.tableDefinitions.Add(tableDefinition);
117 }
118 else
119 {
120 Messaging.Instance.OnMessage(WixErrors.DuplicateExtensionTable(data.GetType().ToString(), tableDefinition.Name));
121 }
122 }
123 }
124 }
125
126 /// <summary>
127 /// Adds an extension.
128 /// </summary>
129 /// <param name="extension">The extension to add.</param>
130 public void AddExtension(IUnbinderExtension extension)
131 {
132 this.unbinderExtensions.Add(extension);
133 }
134
135 /// <summary>
136 /// Unbind a Windows Installer file.
137 /// </summary>
138 /// <param name="file">The Windows Installer file.</param>
139 /// <param name="outputType">The type of output to create.</param>
140 /// <param name="exportBasePath">The path where files should be exported.</param>
141 /// <returns>The output representing the database.</returns>
142 public Output Unbind(string file, OutputType outputType, string exportBasePath)
143 {
144 if (!File.Exists(file))
145 {
146 if (OutputType.Transform == outputType)
147 {
148 throw new WixException(WixErrors.FileNotFound(null, file, "Transform"));
149 }
150 else
151 {
152 throw new WixException(WixErrors.FileNotFound(null, file, "Database"));
153 }
154 }
155
156 // if we don't have the temporary files object yet, get one
157 Directory.CreateDirectory(this.TempFilesLocation); // ensure the base path is there
158
159 if (OutputType.Patch == outputType)
160 {
161 return this.UnbindPatch(file, exportBasePath);
162 }
163 else if (OutputType.Transform == outputType)
164 {
165 return this.UnbindTransform(file, exportBasePath);
166 }
167 else if (OutputType.Bundle == outputType)
168 {
169 return this.UnbindBundle(file, exportBasePath);
170 }
171 else // other database types
172 {
173 return this.UnbindDatabase(file, outputType, exportBasePath);
174 }
175 }
176
177 /// <summary>
178 /// Cleans up the temp files used by the Decompiler.
179 /// </summary>
180 /// <returns>True if all files were deleted, false otherwise.</returns>
181 /// <remarks>
182 /// This should be called after every call to Decompile to ensure there
183 /// are no conflicts between each decompiled database.
184 /// </remarks>
185 public bool DeleteTempFiles()
186 {
187#if REDO_IN_NETCORE
188 bool deleted = Common.DeleteTempFiles(this.tempFiles.BasePath, this);
189
190 if (deleted)
191 {
192 this.tempFiles = null; // temp files have been deleted, no need to remember this now
193 }
194
195 return deleted;
196#endif
197 return true;
198 }
199
200 /// <summary>
201 /// Sends a message to the message delegate if there is one.
202 /// </summary>
203 /// <param name="mea">Message event arguments.</param>
204 public void OnMessage(MessageEventArgs e)
205 {
206 Messaging.Instance.OnMessage(e);
207 }
208
209 /// <summary>
210 /// Unbind an MSI database file.
211 /// </summary>
212 /// <param name="databaseFile">The database file.</param>
213 /// <param name="outputType">The output type.</param>
214 /// <param name="exportBasePath">The path where files should be exported.</param>
215 /// <returns>The unbound database.</returns>
216 private Output UnbindDatabase(string databaseFile, OutputType outputType, string exportBasePath)
217 {
218 Output output;
219
220 try
221 {
222 using (Database database = new Database(databaseFile, OpenDatabase.ReadOnly))
223 {
224 output = this.UnbindDatabase(databaseFile, database, outputType, exportBasePath, false);
225
226 // extract the files from the cabinets
227 if (null != exportBasePath && !this.suppressExtractCabinets)
228 {
229 this.ExtractCabinets(output, database, databaseFile, exportBasePath);
230 }
231 }
232 }
233 catch (Win32Exception e)
234 {
235 if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED
236 {
237 throw new WixException(WixErrors.OpenDatabaseFailed(databaseFile));
238 }
239
240 throw;
241 }
242
243 return output;
244 }
245
246 /// <summary>
247 /// Unbind an MSI database file.
248 /// </summary>
249 /// <param name="databaseFile">The database file.</param>
250 /// <param name="database">The opened database.</param>
251 /// <param name="outputType">The type of output to create.</param>
252 /// <param name="exportBasePath">The path where files should be exported.</param>
253 /// <param name="skipSummaryInfo">Option to skip unbinding the _SummaryInformation table.</param>
254 /// <returns>The output representing the database.</returns>
255 private Output UnbindDatabase(string databaseFile, Database database, OutputType outputType, string exportBasePath, bool skipSummaryInfo)
256 {
257 string modularizationGuid = null;
258 Output output = new Output(new SourceLineNumber(databaseFile));
259 View validationView = null;
260
261 // set the output type
262 output.Type = outputType;
263
264 // get the codepage
265 database.Export("_ForceCodepage", this.TempFilesLocation, "_ForceCodepage.idt");
266 using (StreamReader sr = File.OpenText(Path.Combine(this.TempFilesLocation, "_ForceCodepage.idt")))
267 {
268 string line;
269
270 while (null != (line = sr.ReadLine()))
271 {
272 string[] data = line.Split('\t');
273
274 if (2 == data.Length)
275 {
276 output.Codepage = Convert.ToInt32(data[0], CultureInfo.InvariantCulture);
277 }
278 }
279 }
280
281 // get the summary information table if it exists; it won't if unbinding a transform
282 if (!skipSummaryInfo)
283 {
284 using (SummaryInformation summaryInformation = new SummaryInformation(database))
285 {
286 Table table = new Table(null, this.tableDefinitions["_SummaryInformation"]);
287
288 for (int i = 1; 19 >= i; i++)
289 {
290 string value = summaryInformation.GetProperty(i);
291
292 if (0 < value.Length)
293 {
294 Row row = table.CreateRow(output.SourceLineNumbers);
295 row[0] = i;
296 row[1] = value;
297 }
298 }
299
300 output.Tables.Add(table);
301 }
302 }
303
304 try
305 {
306 // open a view on the validation table if it exists
307 if (database.TableExists("_Validation"))
308 {
309 validationView = database.OpenView("SELECT * FROM `_Validation` WHERE `Table` = ? AND `Column` = ?");
310 }
311
312 // get the normal tables
313 using (View tablesView = database.OpenExecuteView("SELECT * FROM _Tables"))
314 {
315 while (true)
316 {
317 using (Record tableRecord = tablesView.Fetch())
318 {
319 if (null == tableRecord)
320 {
321 break;
322 }
323
324 string tableName = tableRecord.GetString(1);
325
326 using (View tableView = database.OpenExecuteView(String.Format(CultureInfo.InvariantCulture, "SELECT * FROM `{0}`", tableName)))
327 {
328 List<ColumnDefinition> columns;
329 using (Record columnNameRecord = tableView.GetColumnInfo(MsiInterop.MSICOLINFONAMES),
330 columnTypeRecord = tableView.GetColumnInfo(MsiInterop.MSICOLINFOTYPES))
331 {
332 // index the primary keys
333 HashSet<string> tablePrimaryKeys = new HashSet<string>();
334 using (Record primaryKeysRecord = database.PrimaryKeys(tableName))
335 {
336 int primaryKeysFieldCount = primaryKeysRecord.GetFieldCount();
337
338 for (int i = 1; i <= primaryKeysFieldCount; i++)
339 {
340 tablePrimaryKeys.Add(primaryKeysRecord.GetString(i));
341 }
342 }
343
344 int columnCount = columnNameRecord.GetFieldCount();
345 columns = new List<ColumnDefinition>(columnCount);
346 for (int i = 1; i <= columnCount; i++)
347 {
348 string columnName = columnNameRecord.GetString(i);
349 string idtType = columnTypeRecord.GetString(i);
350
351 ColumnType columnType;
352 int length;
353 bool nullable;
354
355 ColumnCategory columnCategory = ColumnCategory.Unknown;
356 ColumnModularizeType columnModularizeType = ColumnModularizeType.None;
357 bool primary = tablePrimaryKeys.Contains(columnName);
358 bool minValueSet = false;
359 int minValue = -1;
360 bool maxValueSet = false;
361 int maxValue = -1;
362 string keyTable = null;
363 bool keyColumnSet = false;
364 int keyColumn = -1;
365 string category = null;
366 string set = null;
367 string description = null;
368
369 // get the column type, length, and whether its nullable
370 switch (Char.ToLower(idtType[0], CultureInfo.InvariantCulture))
371 {
372 case 'i':
373 columnType = ColumnType.Number;
374 break;
375 case 'l':
376 columnType = ColumnType.Localized;
377 break;
378 case 's':
379 columnType = ColumnType.String;
380 break;
381 case 'v':
382 columnType = ColumnType.Object;
383 break;
384 default:
385 // TODO: error
386 columnType = ColumnType.Unknown;
387 break;
388 }
389 length = Convert.ToInt32(idtType.Substring(1), CultureInfo.InvariantCulture);
390 nullable = Char.IsUpper(idtType[0]);
391
392 // try to get validation information
393 if (null != validationView)
394 {
395 using (Record validationRecord = new Record(2))
396 {
397 validationRecord.SetString(1, tableName);
398 validationRecord.SetString(2, columnName);
399
400 validationView.Execute(validationRecord);
401 }
402
403 using (Record validationRecord = validationView.Fetch())
404 {
405 if (null != validationRecord)
406 {
407 string validationNullable = validationRecord.GetString(3);
408 minValueSet = !validationRecord.IsNull(4);
409 minValue = (minValueSet ? validationRecord.GetInteger(4) : -1);
410 maxValueSet = !validationRecord.IsNull(5);
411 maxValue = (maxValueSet ? validationRecord.GetInteger(5) : -1);
412 keyTable = (!validationRecord.IsNull(6) ? validationRecord.GetString(6) : null);
413 keyColumnSet = !validationRecord.IsNull(7);
414 keyColumn = (keyColumnSet ? validationRecord.GetInteger(7) : -1);
415 category = (!validationRecord.IsNull(8) ? validationRecord.GetString(8) : null);
416 set = (!validationRecord.IsNull(9) ? validationRecord.GetString(9) : null);
417 description = (!validationRecord.IsNull(10) ? validationRecord.GetString(10) : null);
418
419 // check the validation nullable value against the column definition
420 if (null == validationNullable)
421 {
422 // TODO: warn for illegal validation nullable column
423 }
424 else if ((nullable && "Y" != validationNullable) || (!nullable && "N" != validationNullable))
425 {
426 // TODO: warn for mismatch between column definition and validation nullable
427 }
428
429 // convert category to ColumnCategory
430 if (null != category)
431 {
432 try
433 {
434 columnCategory = (ColumnCategory)Enum.Parse(typeof(ColumnCategory), category, true);
435 }
436 catch (ArgumentException)
437 {
438 columnCategory = ColumnCategory.Unknown;
439 }
440 }
441 }
442 else
443 {
444 // TODO: warn about no validation information
445 }
446 }
447 }
448
449 // guess the modularization type
450 if ("Icon" == keyTable && 1 == keyColumn)
451 {
452 columnModularizeType = ColumnModularizeType.Icon;
453 }
454 else if ("Condition" == columnName)
455 {
456 columnModularizeType = ColumnModularizeType.Condition;
457 }
458 else if (ColumnCategory.Formatted == columnCategory || ColumnCategory.FormattedSDDLText == columnCategory)
459 {
460 columnModularizeType = ColumnModularizeType.Property;
461 }
462 else if (ColumnCategory.Identifier == columnCategory)
463 {
464 columnModularizeType = ColumnModularizeType.Column;
465 }
466
467 columns.Add(new ColumnDefinition(columnName, columnType, length, primary, nullable, columnModularizeType, (ColumnType.Localized == columnType), minValueSet, minValue, maxValueSet, maxValue, keyTable, keyColumnSet, keyColumn, columnCategory, set, description, true, true));
468 }
469 }
470
471 TableDefinition tableDefinition = new TableDefinition(tableName, columns, false, false);
472
473 // use our table definitions if core properties are the same; this allows us to take advantage
474 // of wix concepts like localizable columns which current code assumes
475 if (this.tableDefinitions.Contains(tableName) && 0 == tableDefinition.CompareTo(this.tableDefinitions[tableName]))
476 {
477 tableDefinition = this.tableDefinitions[tableName];
478 }
479
480 Table table = new Table(null, tableDefinition);
481
482 while (true)
483 {
484 using (Record rowRecord = tableView.Fetch())
485 {
486 if (null == rowRecord)
487 {
488 break;
489 }
490
491 int recordCount = rowRecord.GetFieldCount();
492 Row row = table.CreateRow(output.SourceLineNumbers);
493
494 for (int i = 0; recordCount > i && row.Fields.Length > i; i++)
495 {
496 if (rowRecord.IsNull(i + 1))
497 {
498 if (!row.Fields[i].Column.Nullable)
499 {
500 // TODO: display an error for a null value in a non-nullable field OR
501 // display a warning and put an empty string in the value to let the compiler handle it
502 // (the second option is risky because the later code may make certain assumptions about
503 // the contents of a row value)
504 }
505 }
506 else
507 {
508 switch (row.Fields[i].Column.Type)
509 {
510 case ColumnType.Number:
511 bool success = false;
512 int intValue = rowRecord.GetInteger(i + 1);
513 if (row.Fields[i].Column.IsLocalizable)
514 {
515 success = row.BestEffortSetField(i, Convert.ToString(intValue, CultureInfo.InvariantCulture));
516 }
517 else
518 {
519 success = row.BestEffortSetField(i, intValue);
520 }
521
522 if (!success)
523 {
524 this.OnMessage(WixWarnings.BadColumnDataIgnored(row.SourceLineNumbers, Convert.ToString(intValue, CultureInfo.InvariantCulture), tableName, row.Fields[i].Column.Name));
525 }
526 break;
527 case ColumnType.Object:
528 string sourceFile = "FILE NOT EXPORTED, USE THE dark.exe -x OPTION TO EXPORT BINARIES";
529
530 if (null != exportBasePath)
531 {
532 string relativeSourceFile = Path.Combine(tableName, row.GetPrimaryKey('.'));
533 sourceFile = Path.Combine(exportBasePath, relativeSourceFile);
534
535 // ensure the parent directory exists
536 System.IO.Directory.CreateDirectory(Path.Combine(exportBasePath, tableName));
537
538 using (FileStream fs = System.IO.File.Create(sourceFile))
539 {
540 int bytesRead;
541 byte[] buffer = new byte[512];
542
543 while (0 != (bytesRead = rowRecord.GetStream(i + 1, buffer, buffer.Length)))
544 {
545 fs.Write(buffer, 0, bytesRead);
546 }
547 }
548 }
549
550 row[i] = sourceFile;
551 break;
552 default:
553 string value = rowRecord.GetString(i + 1);
554
555 switch (row.Fields[i].Column.Category)
556 {
557 case ColumnCategory.Guid:
558 value = value.ToUpper(CultureInfo.InvariantCulture);
559 break;
560 }
561
562 // de-modularize
563 if (!this.suppressDemodularization && OutputType.Module == output.Type && ColumnModularizeType.None != row.Fields[i].Column.ModularizeType)
564 {
565 Regex modularization = new Regex(@"\.[0-9A-Fa-f]{8}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{12}");
566
567 if (null == modularizationGuid)
568 {
569 Match match = modularization.Match(value);
570 if (match.Success)
571 {
572 modularizationGuid = String.Concat('{', match.Value.Substring(1).Replace('_', '-'), '}');
573 }
574 }
575
576 value = modularization.Replace(value, String.Empty);
577 }
578
579 // escape "$(" for the preprocessor
580 value = value.Replace("$(", "$$(");
581
582 // escape things that look like wix variables
583 MatchCollection matches = Common.WixVariableRegex.Matches(value);
584 for (int j = matches.Count - 1; 0 <= j; j--)
585 {
586 value = value.Insert(matches[j].Index, "!");
587 }
588
589 row[i] = value;
590 break;
591 }
592 }
593 }
594 }
595 }
596
597 output.Tables.Add(table);
598 }
599
600 }
601 }
602 }
603 }
604 finally
605 {
606 if (null != validationView)
607 {
608 validationView.Close();
609 }
610 }
611
612 // set the modularization guid as the PackageCode
613 if (null != modularizationGuid)
614 {
615 Table table = output.Tables["_SummaryInformation"];
616
617 foreach (Row row in table.Rows)
618 {
619 if (9 == (int)row[0]) // PID_REVNUMBER
620 {
621 row[1] = modularizationGuid;
622 }
623 }
624 }
625
626 if (this.isAdminImage)
627 {
628 GenerateWixFileTable(databaseFile, output);
629 GenerateSectionIds(output);
630 }
631
632 return output;
633 }
634
635 /// <summary>
636 /// Creates section ids on rows which form logical groupings of resources.
637 /// </summary>
638 /// <param name="output">The Output that represents the msi database.</param>
639 private void GenerateSectionIds(Output output)
640 {
641 // First assign and index section ids for the tables that are in their own sections.
642 AssignSectionIdsToTable(output.Tables["Binary"], 0);
643 Hashtable componentSectionIdIndex = AssignSectionIdsToTable(output.Tables["Component"], 0);
644 Hashtable customActionSectionIdIndex = AssignSectionIdsToTable(output.Tables["CustomAction"], 0);
645 AssignSectionIdsToTable(output.Tables["Directory"], 0);
646 Hashtable featureSectionIdIndex = AssignSectionIdsToTable(output.Tables["Feature"], 0);
647 AssignSectionIdsToTable(output.Tables["Icon"], 0);
648 Hashtable digitalCertificateSectionIdIndex = AssignSectionIdsToTable(output.Tables["MsiDigitalCertificate"], 0);
649 AssignSectionIdsToTable(output.Tables["Property"], 0);
650
651 // Now handle all the tables that rely on the first set of indexes but also produce their own indexes. Order matters here.
652 Hashtable fileSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["File"], componentSectionIdIndex, 1, 0);
653 Hashtable appIdSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["Class"], componentSectionIdIndex, 2, 5);
654 Hashtable odbcDataSourceSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["ODBCDataSource"], componentSectionIdIndex, 1, 0);
655 Hashtable odbcDriverSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["ODBCDriver"], componentSectionIdIndex, 1, 0);
656 Hashtable registrySectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["Registry"], componentSectionIdIndex, 5, 0);
657 Hashtable serviceInstallSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["ServiceInstall"], componentSectionIdIndex, 11, 0);
658
659 // Now handle all the tables which only rely on previous indexes and order does not matter.
660 foreach (Table table in output.Tables)
661 {
662 switch (table.Name)
663 {
664 case "WixFile":
665 case "MsiFileHash":
666 ConnectTableToSection(table, fileSectionIdIndex, 0);
667 break;
668 case "MsiAssembly":
669 case "MsiAssemblyName":
670 ConnectTableToSection(table, componentSectionIdIndex, 0);
671 break;
672 case "MsiPackageCertificate":
673 case "MsiPatchCertificate":
674 ConnectTableToSection(table, digitalCertificateSectionIdIndex, 1);
675 break;
676 case "CreateFolder":
677 case "FeatureComponents":
678 case "MoveFile":
679 case "ReserveCost":
680 case "ODBCTranslator":
681 ConnectTableToSection(table, componentSectionIdIndex, 1);
682 break;
683 case "TypeLib":
684 ConnectTableToSection(table, componentSectionIdIndex, 2);
685 break;
686 case "Shortcut":
687 case "Environment":
688 ConnectTableToSection(table, componentSectionIdIndex, 3);
689 break;
690 case "RemoveRegistry":
691 ConnectTableToSection(table, componentSectionIdIndex, 4);
692 break;
693 case "ServiceControl":
694 ConnectTableToSection(table, componentSectionIdIndex, 5);
695 break;
696 case "IniFile":
697 case "RemoveIniFile":
698 ConnectTableToSection(table, componentSectionIdIndex, 7);
699 break;
700 case "AppId":
701 ConnectTableToSection(table, appIdSectionIdIndex, 0);
702 break;
703 case "Condition":
704 ConnectTableToSection(table, featureSectionIdIndex, 0);
705 break;
706 case "ODBCSourceAttribute":
707 ConnectTableToSection(table, odbcDataSourceSectionIdIndex, 0);
708 break;
709 case "ODBCAttribute":
710 ConnectTableToSection(table, odbcDriverSectionIdIndex, 0);
711 break;
712 case "AdminExecuteSequence":
713 case "AdminUISequence":
714 case "AdvtExecuteSequence":
715 case "AdvtUISequence":
716 case "InstallExecuteSequence":
717 case "InstallUISequence":
718 ConnectTableToSection(table, customActionSectionIdIndex, 0);
719 break;
720 case "LockPermissions":
721 case "MsiLockPermissions":
722 foreach (Row row in table.Rows)
723 {
724 string lockObject = (string)row[0];
725 string tableName = (string)row[1];
726 switch (tableName)
727 {
728 case "File":
729 row.SectionId = (string)fileSectionIdIndex[lockObject];
730 break;
731 case "Registry":
732 row.SectionId = (string)registrySectionIdIndex[lockObject];
733 break;
734 case "ServiceInstall":
735 row.SectionId = (string)serviceInstallSectionIdIndex[lockObject];
736 break;
737 }
738 }
739 break;
740 }
741 }
742
743 // Now pass the output to each unbinder extension to allow them to analyze the output and determine thier proper section ids.
744 foreach (IUnbinderExtension extension in this.unbinderExtensions)
745 {
746 extension.GenerateSectionIds(output);
747 }
748 }
749
750 /// <summary>
751 /// Creates new section ids on all the rows in a table.
752 /// </summary>
753 /// <param name="table">The table to add sections to.</param>
754 /// <param name="rowPrimaryKeyIndex">The index of the column which is used by other tables to reference this table.</param>
755 /// <returns>A Hashtable containing the tables key for each row paired with its assigned section id.</returns>
756 private Hashtable AssignSectionIdsToTable(Table table, int rowPrimaryKeyIndex)
757 {
758 Hashtable hashtable = new Hashtable();
759 if (null != table)
760 {
761 foreach (Row row in table.Rows)
762 {
763 row.SectionId = GetNewSectionId();
764 hashtable.Add(row[rowPrimaryKeyIndex], row.SectionId);
765 }
766 }
767 return hashtable;
768 }
769
770 /// <summary>
771 /// Connects a table's rows to an already sectioned table.
772 /// </summary>
773 /// <param name="table">The table containing rows that need to be connected to sections.</param>
774 /// <param name="sectionIdIndex">A hashtable containing keys to map table to its section.</param>
775 /// <param name="rowIndex">The index of the column which is used as the foreign key in to the sectionIdIndex.</param>
776 private static void ConnectTableToSection(Table table, Hashtable sectionIdIndex, int rowIndex)
777 {
778 if (null != table)
779 {
780 foreach (Row row in table.Rows)
781 {
782 if (sectionIdIndex.ContainsKey(row[rowIndex]))
783 {
784 row.SectionId = (string)sectionIdIndex[row[rowIndex]];
785 }
786 }
787 }
788 }
789
790 /// <summary>
791 /// Connects a table's rows to an already sectioned table and produces an index for other tables to connect to it.
792 /// </summary>
793 /// <param name="table">The table containing rows that need to be connected to sections.</param>
794 /// <param name="sectionIdIndex">A hashtable containing keys to map table to its section.</param>
795 /// <param name="rowIndex">The index of the column which is used as the foreign key in to the sectionIdIndex.</param>
796 /// <param name="rowPrimaryKeyIndex">The index of the column which is used by other tables to reference this table.</param>
797 /// <returns>A Hashtable containing the tables key for each row paired with its assigned section id.</returns>
798 private static Hashtable ConnectTableToSectionAndIndex(Table table, Hashtable sectionIdIndex, int rowIndex, int rowPrimaryKeyIndex)
799 {
800 Hashtable newHashTable = new Hashtable();
801 if (null != table)
802 {
803 foreach (Row row in table.Rows)
804 {
805 if (!sectionIdIndex.ContainsKey(row[rowIndex]))
806 {
807 continue;
808 }
809
810 row.SectionId = (string)sectionIdIndex[row[rowIndex]];
811 if (null != row[rowPrimaryKeyIndex])
812 {
813 newHashTable.Add(row[rowPrimaryKeyIndex], row.SectionId);
814 }
815 }
816 }
817 return newHashTable;
818 }
819
820 /// <summary>
821 /// Creates a new section identifier to be used when adding a section to an output.
822 /// </summary>
823 /// <returns>A string representing a new section id.</returns>
824 private string GetNewSectionId()
825 {
826 this.sectionCount++;
827 return "wix.section." + this.sectionCount.ToString(CultureInfo.InvariantCulture);
828 }
829
830 /// <summary>
831 /// Generates the WixFile table based on a path to an admin image msi and an Output.
832 /// </summary>
833 /// <param name="databaseFile">The path to the msi database file in an admin image.</param>
834 /// <param name="output">The Output that represents the msi database.</param>
835 private void GenerateWixFileTable(string databaseFile, Output output)
836 {
837 string adminRootPath = Path.GetDirectoryName(databaseFile);
838
839 Hashtable componentDirectoryIndex = new Hashtable();
840 Table componentTable = output.Tables["Component"];
841 foreach (Row row in componentTable.Rows)
842 {
843 componentDirectoryIndex.Add(row[0], row[2]);
844 }
845
846 // Index full source paths for all directories
847 Hashtable directoryDirectoryParentIndex = new Hashtable();
848 Hashtable directoryFullPathIndex = new Hashtable();
849 Hashtable directorySourceNameIndex = new Hashtable();
850 Table directoryTable = output.Tables["Directory"];
851 foreach (Row row in directoryTable.Rows)
852 {
853 directoryDirectoryParentIndex.Add(row[0], row[1]);
854 if (null == row[1])
855 {
856 directoryFullPathIndex.Add(row[0], adminRootPath);
857 }
858 else
859 {
860 directorySourceNameIndex.Add(row[0], GetAdminSourceName((string)row[2]));
861 }
862 }
863
864 foreach (DictionaryEntry directoryEntry in directoryDirectoryParentIndex)
865 {
866 if (!directoryFullPathIndex.ContainsKey(directoryEntry.Key))
867 {
868 GetAdminFullPath((string)directoryEntry.Key, directoryDirectoryParentIndex, directorySourceNameIndex, directoryFullPathIndex);
869 }
870 }
871
872 Table fileTable = output.Tables["File"];
873 Table wixFileTable = output.EnsureTable(this.tableDefinitions["WixFile"]);
874 foreach (Row row in fileTable.Rows)
875 {
876 WixFileRow wixFileRow = new WixFileRow(null, this.tableDefinitions["WixFile"]);
877 wixFileRow.File = (string)row[0];
878 wixFileRow.Directory = (string)componentDirectoryIndex[(string)row[1]];
879 wixFileRow.Source = Path.Combine((string)directoryFullPathIndex[wixFileRow.Directory], GetAdminSourceName((string)row[2]));
880
881 if (!File.Exists(wixFileRow.Source))
882 {
883 throw new WixException(WixErrors.WixFileNotFound(wixFileRow.Source));
884 }
885
886 wixFileTable.Rows.Add(wixFileRow);
887 }
888 }
889
890 /// <summary>
891 /// 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.
892 /// </summary>
893 /// <param name="directory">The directory identifier.</param>
894 /// <param name="directoryDirectoryParentIndex">The Hashtable containing all the directory to directory parent mapping.</param>
895 /// <param name="directorySourceNameIndex">The Hashtable containing all the directory to source name mapping.</param>
896 /// <param name="directoryFullPathIndex">The Hashtable containing a mapping between all of the directories and their previously calculated full paths.</param>
897 /// <returns>The full path to the directory.</returns>
898 private string GetAdminFullPath(string directory, Hashtable directoryDirectoryParentIndex, Hashtable directorySourceNameIndex, Hashtable directoryFullPathIndex)
899 {
900 string parent = (string)directoryDirectoryParentIndex[directory];
901 string sourceName = (string)directorySourceNameIndex[directory];
902
903 string parentFullPath;
904 if (directoryFullPathIndex.ContainsKey(parent))
905 {
906 parentFullPath = (string)directoryFullPathIndex[parent];
907 }
908 else
909 {
910 parentFullPath = GetAdminFullPath(parent, directoryDirectoryParentIndex, directorySourceNameIndex, directoryFullPathIndex);
911 }
912
913 if (null == sourceName)
914 {
915 sourceName = String.Empty;
916 }
917
918 string fullPath = Path.Combine(parentFullPath, sourceName);
919 directoryFullPathIndex.Add(directory, fullPath);
920
921 return fullPath;
922 }
923
924 /// <summary>
925 /// Get the source name in an admin image.
926 /// </summary>
927 /// <param name="value">The Filename value.</param>
928 /// <returns>The source name of the directory in an admin image.</returns>
929 private static string GetAdminSourceName(string value)
930 {
931 string name = null;
932 string[] names;
933 string shortname = null;
934 string shortsourcename = null;
935 string sourcename = null;
936
937 names = Installer.GetNames(value);
938
939 if (null != names[0] && "." != names[0])
940 {
941 if (null != names[1])
942 {
943 shortname = names[0];
944 }
945 else
946 {
947 name = names[0];
948 }
949 }
950
951 if (null != names[1])
952 {
953 name = names[1];
954 }
955
956 if (null != names[2])
957 {
958 if (null != names[3])
959 {
960 shortsourcename = names[2];
961 }
962 else
963 {
964 sourcename = names[2];
965 }
966 }
967
968 if (null != names[3])
969 {
970 sourcename = names[3];
971 }
972
973 if (null != sourcename)
974 {
975 return sourcename;
976 }
977 else if (null != shortsourcename)
978 {
979 return shortsourcename;
980 }
981 else if (null != name)
982 {
983 return name;
984 }
985 else
986 {
987 return shortname;
988 }
989 }
990
991 /// <summary>
992 /// Unbind an MSP patch file.
993 /// </summary>
994 /// <param name="patchFile">The patch file.</param>
995 /// <param name="exportBasePath">The path where files should be exported.</param>
996 /// <returns>The unbound patch.</returns>
997 private Output UnbindPatch(string patchFile, string exportBasePath)
998 {
999 Output patch;
1000
1001 // patch files are essentially database files (use a special flag to let the API know its a patch file)
1002 try
1003 {
1004 using (Database database = new Database(patchFile, OpenDatabase.ReadOnly | OpenDatabase.OpenPatchFile))
1005 {
1006 patch = this.UnbindDatabase(patchFile, database, OutputType.Patch, exportBasePath, false);
1007 }
1008 }
1009 catch (Win32Exception e)
1010 {
1011 if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED
1012 {
1013 throw new WixException(WixErrors.OpenDatabaseFailed(patchFile));
1014 }
1015
1016 throw;
1017 }
1018
1019 // retrieve the transforms (they are in substorages)
1020 using (Storage storage = Storage.Open(patchFile, StorageMode.Read | StorageMode.ShareDenyWrite))
1021 {
1022 Table summaryInformationTable = patch.Tables["_SummaryInformation"];
1023 foreach (Row row in summaryInformationTable.Rows)
1024 {
1025 if (8 == (int)row[0]) // PID_LASTAUTHOR
1026 {
1027 string value = (string)row[1];
1028
1029 foreach (string decoratedSubStorageName in value.Split(';'))
1030 {
1031 string subStorageName = decoratedSubStorageName.Substring(1);
1032 string transformFile = Path.Combine(this.TempFilesLocation, String.Concat("Transform", Path.DirectorySeparatorChar, subStorageName, ".mst"));
1033
1034 // ensure the parent directory exists
1035 System.IO.Directory.CreateDirectory(Path.GetDirectoryName(transformFile));
1036
1037 // copy the substorage to a new storage for the transform file
1038 using (Storage subStorage = storage.OpenStorage(subStorageName))
1039 {
1040 using (Storage transformStorage = Storage.CreateDocFile(transformFile, StorageMode.ReadWrite | StorageMode.ShareExclusive | StorageMode.Create))
1041 {
1042 subStorage.CopyTo(transformStorage);
1043 }
1044 }
1045
1046 // unbind the transform
1047 Output transform = this.UnbindTransform(transformFile, (null == exportBasePath ? null : Path.Combine(exportBasePath, subStorageName)));
1048 patch.SubStorages.Add(new SubStorage(subStorageName, transform));
1049 }
1050
1051 break;
1052 }
1053 }
1054 }
1055
1056 // extract the files from the cabinets
1057 // TODO: use per-transform export paths for support of multi-product patches
1058 if (null != exportBasePath && !this.suppressExtractCabinets)
1059 {
1060 using (Database database = new Database(patchFile, OpenDatabase.ReadOnly | OpenDatabase.OpenPatchFile))
1061 {
1062 foreach (SubStorage subStorage in patch.SubStorages)
1063 {
1064 // only patch transforms should carry files
1065 if (subStorage.Name.StartsWith("#", StringComparison.Ordinal))
1066 {
1067 this.ExtractCabinets(subStorage.Data, database, patchFile, exportBasePath);
1068 }
1069 }
1070 }
1071 }
1072
1073 return patch;
1074 }
1075
1076 /// <summary>
1077 /// Unbind an MSI transform file.
1078 /// </summary>
1079 /// <param name="transformFile">The transform file.</param>
1080 /// <param name="exportBasePath">The path where files should be exported.</param>
1081 /// <returns>The unbound transform.</returns>
1082 private Output UnbindTransform(string transformFile, string exportBasePath)
1083 {
1084 Output transform = new Output(new SourceLineNumber(transformFile));
1085 transform.Type = OutputType.Transform;
1086
1087 // get the summary information table
1088 using (SummaryInformation summaryInformation = new SummaryInformation(transformFile))
1089 {
1090 Table table = transform.EnsureTable(this.tableDefinitions["_SummaryInformation"]);
1091
1092 for (int i = 1; 19 >= i; i++)
1093 {
1094 string value = summaryInformation.GetProperty(i);
1095
1096 if (0 < value.Length)
1097 {
1098 Row row = table.CreateRow(transform.SourceLineNumbers);
1099 row[0] = i;
1100 row[1] = value;
1101 }
1102 }
1103 }
1104
1105 // create a schema msi which hopefully matches the table schemas in the transform
1106 Output schemaOutput = new Output(null);
1107 string msiDatabaseFile = Path.Combine(this.TempFilesLocation, "schema.msi");
1108 foreach (TableDefinition tableDefinition in this.tableDefinitions)
1109 {
1110 // skip unreal tables and the Patch table
1111 if (!tableDefinition.Unreal && "Patch" != tableDefinition.Name)
1112 {
1113 schemaOutput.EnsureTable(tableDefinition);
1114 }
1115 }
1116
1117 Hashtable addedRows = new Hashtable();
1118 Table transformViewTable;
1119
1120 // Bind the schema msi.
1121 this.GenerateDatabase(schemaOutput, msiDatabaseFile);
1122
1123 // apply the transform to the database and retrieve the modifications
1124 using (Database msiDatabase = new Database(msiDatabaseFile, OpenDatabase.Transact))
1125 {
1126 // apply the transform with the ViewTransform option to collect all the modifications
1127 msiDatabase.ApplyTransform(transformFile, TransformErrorConditions.All | TransformErrorConditions.ViewTransform);
1128
1129 // unbind the database
1130 Output transformViewOutput = this.UnbindDatabase(msiDatabaseFile, msiDatabase, OutputType.Product, exportBasePath, true);
1131
1132 // index the added and possibly modified rows (added rows may also appears as modified rows)
1133 transformViewTable = transformViewOutput.Tables["_TransformView"];
1134 Hashtable modifiedRows = new Hashtable();
1135 foreach (Row row in transformViewTable.Rows)
1136 {
1137 string tableName = (string)row[0];
1138 string columnName = (string)row[1];
1139 string primaryKeys = (string)row[2];
1140
1141 if ("INSERT" == columnName)
1142 {
1143 string index = String.Concat(tableName, ':', primaryKeys);
1144
1145 addedRows.Add(index, null);
1146 }
1147 else if ("CREATE" != columnName && "DELETE" != columnName && "DROP" != columnName && null != primaryKeys) // modified row
1148 {
1149 string index = String.Concat(tableName, ':', primaryKeys);
1150
1151 modifiedRows[index] = row;
1152 }
1153 }
1154
1155 // create placeholder rows for modified rows to make the transform insert the updated values when its applied
1156 foreach (Row row in modifiedRows.Values)
1157 {
1158 string tableName = (string)row[0];
1159 string columnName = (string)row[1];
1160 string primaryKeys = (string)row[2];
1161
1162 string index = String.Concat(tableName, ':', primaryKeys);
1163
1164 // ignore information for added rows
1165 if (!addedRows.Contains(index))
1166 {
1167 Table table = schemaOutput.Tables[tableName];
1168 this.CreateRow(table, primaryKeys, true);
1169 }
1170 }
1171 }
1172
1173 // Re-bind the schema output with the placeholder rows.
1174 this.GenerateDatabase(schemaOutput, msiDatabaseFile);
1175
1176 // apply the transform to the database and retrieve the modifications
1177 using (Database msiDatabase = new Database(msiDatabaseFile, OpenDatabase.Transact))
1178 {
1179 try
1180 {
1181 // apply the transform
1182 msiDatabase.ApplyTransform(transformFile, TransformErrorConditions.All);
1183
1184 // commit the database to guard against weird errors with streams
1185 msiDatabase.Commit();
1186 }
1187 catch (Win32Exception ex)
1188 {
1189 if (0x65B == ex.NativeErrorCode)
1190 {
1191 // this commonly happens when the transform was built
1192 // against a database schema different from the internal
1193 // table definitions
1194 throw new WixException(WixErrors.TransformSchemaMismatch());
1195 }
1196 }
1197
1198 // unbind the database
1199 Output output = this.UnbindDatabase(msiDatabaseFile, msiDatabase, OutputType.Product, exportBasePath, true);
1200
1201 // index all the rows to easily find modified rows
1202 Hashtable rows = new Hashtable();
1203 foreach (Table table in output.Tables)
1204 {
1205 foreach (Row row in table.Rows)
1206 {
1207 rows.Add(String.Concat(table.Name, ':', row.GetPrimaryKey('\t', " ")), row);
1208 }
1209 }
1210
1211 // process the _TransformView rows into transform rows
1212 foreach (Row row in transformViewTable.Rows)
1213 {
1214 string tableName = (string)row[0];
1215 string columnName = (string)row[1];
1216 string primaryKeys = (string)row[2];
1217
1218 Table table = transform.EnsureTable(this.tableDefinitions[tableName]);
1219
1220 if ("CREATE" == columnName) // added table
1221 {
1222 table.Operation = TableOperation.Add;
1223 }
1224 else if ("DELETE" == columnName) // deleted row
1225 {
1226 Row deletedRow = this.CreateRow(table, primaryKeys, false);
1227 deletedRow.Operation = RowOperation.Delete;
1228 }
1229 else if ("DROP" == columnName) // dropped table
1230 {
1231 table.Operation = TableOperation.Drop;
1232 }
1233 else if ("INSERT" == columnName) // added row
1234 {
1235 string index = String.Concat(tableName, ':', primaryKeys);
1236 Row addedRow = (Row)rows[index];
1237 addedRow.Operation = RowOperation.Add;
1238 table.Rows.Add(addedRow);
1239 }
1240 else if (null != primaryKeys) // modified row
1241 {
1242 string index = String.Concat(tableName, ':', primaryKeys);
1243
1244 // the _TransformView table includes information for added rows
1245 // that looks like modified rows so it sometimes needs to be ignored
1246 if (!addedRows.Contains(index))
1247 {
1248 Row modifiedRow = (Row)rows[index];
1249
1250 // mark the field as modified
1251 int indexOfModifiedValue = -1;
1252 for (int i = 0; i < modifiedRow.TableDefinition.Columns.Count; ++i)
1253 {
1254 if (columnName.Equals(modifiedRow.TableDefinition.Columns[i].Name, StringComparison.Ordinal))
1255 {
1256 indexOfModifiedValue = i;
1257 break;
1258 }
1259 }
1260 modifiedRow.Fields[indexOfModifiedValue].Modified = true;
1261
1262 // move the modified row into the transform the first time its encountered
1263 if (RowOperation.None == modifiedRow.Operation)
1264 {
1265 modifiedRow.Operation = RowOperation.Modify;
1266 table.Rows.Add(modifiedRow);
1267 }
1268 }
1269 }
1270 else // added column
1271 {
1272 ColumnDefinition column = table.Definition.Columns.Single(c => c.Name.Equals(columnName, StringComparison.Ordinal));
1273 column.Added = true;
1274 }
1275 }
1276 }
1277
1278 return transform;
1279 }
1280
1281 private void GenerateDatabase(Output output, string databaseFile)
1282 {
1283 GenerateDatabaseCommand command = new GenerateDatabaseCommand();
1284 command.Extensions = Enumerable.Empty<IBinderExtension>();
1285 command.FileManagers = Enumerable.Empty<IBinderFileManager>();
1286 command.Output = output;
1287 command.OutputPath = databaseFile;
1288 command.KeepAddedColumns = true;
1289 command.UseSubDirectory = false;
1290 command.SuppressAddingValidationRows = true;
1291 command.TableDefinitions = this.tableDefinitions;
1292 command.TempFilesLocation = this.TempFilesLocation;
1293 command.Codepage = -1;
1294 command.Execute();
1295 }
1296
1297 /// <summary>
1298 /// Unbind a bundle.
1299 /// </summary>
1300 /// <param name="bundleFile">The bundle file.</param>
1301 /// <param name="exportBasePath">The path where files should be exported.</param>
1302 /// <returns>The unbound bundle.</returns>
1303 private Output UnbindBundle(string bundleFile, string exportBasePath)
1304 {
1305 string uxExtractPath = Path.Combine(exportBasePath, "UX");
1306 string acExtractPath = Path.Combine(exportBasePath, "AttachedContainer");
1307
1308 using (BurnReader reader = BurnReader.Open(bundleFile))
1309 {
1310 reader.ExtractUXContainer(uxExtractPath, this.TempFilesLocation);
1311 reader.ExtractAttachedContainer(acExtractPath, this.TempFilesLocation);
1312 }
1313
1314 return null;
1315 }
1316
1317 /// <summary>
1318 /// Create a deleted or modified row.
1319 /// </summary>
1320 /// <param name="table">The table containing the row.</param>
1321 /// <param name="primaryKeys">The primary keys of the row.</param>
1322 /// <param name="setRequiredFields">Option to set all required fields with placeholder values.</param>
1323 /// <returns>The new row.</returns>
1324 private Row CreateRow(Table table, string primaryKeys, bool setRequiredFields)
1325 {
1326 Row row = table.CreateRow(null);
1327
1328 string[] primaryKeyParts = primaryKeys.Split('\t');
1329 int primaryKeyPartIndex = 0;
1330
1331 for (int i = 0; i < table.Definition.Columns.Count; i++)
1332 {
1333 ColumnDefinition columnDefinition = table.Definition.Columns[i];
1334
1335 if (columnDefinition.PrimaryKey)
1336 {
1337 if (ColumnType.Number == columnDefinition.Type && !columnDefinition.IsLocalizable)
1338 {
1339 row[i] = Convert.ToInt32(primaryKeyParts[primaryKeyPartIndex++], CultureInfo.InvariantCulture);
1340 }
1341 else
1342 {
1343 row[i] = primaryKeyParts[primaryKeyPartIndex++];
1344 }
1345 }
1346 else if (setRequiredFields)
1347 {
1348 if (ColumnType.Number == columnDefinition.Type && !columnDefinition.IsLocalizable)
1349 {
1350 row[i] = 1;
1351 }
1352 else if (ColumnType.Object == columnDefinition.Type)
1353 {
1354 if (null == this.emptyFile)
1355 {
1356 this.emptyFile = Path.GetTempFileName() + ".empty";
1357 using (FileStream fileStream = File.Create(this.emptyFile))
1358 {
1359 }
1360 }
1361
1362 row[i] = this.emptyFile;
1363 }
1364 else
1365 {
1366 row[i] = "1";
1367 }
1368 }
1369 }
1370
1371 return row;
1372 }
1373
1374 /// <summary>
1375 /// Extract the cabinets from a database.
1376 /// </summary>
1377 /// <param name="output">The output to use when finding cabinets.</param>
1378 /// <param name="database">The database containing the cabinets.</param>
1379 /// <param name="databaseFile">The location of the database file.</param>
1380 /// <param name="exportBasePath">The path where the files should be exported.</param>
1381 private void ExtractCabinets(Output output, Database database, string databaseFile, string exportBasePath)
1382 {
1383 string databaseBasePath = Path.GetDirectoryName(databaseFile);
1384 StringCollection cabinetFiles = new StringCollection();
1385 SortedList embeddedCabinets = new SortedList();
1386
1387 // index all of the cabinet files
1388 if (OutputType.Module == output.Type)
1389 {
1390 embeddedCabinets.Add(0, "MergeModule.CABinet");
1391 }
1392 else if (null != output.Tables["Media"])
1393 {
1394 foreach (MediaRow mediaRow in output.Tables["Media"].Rows)
1395 {
1396 if (null != mediaRow.Cabinet)
1397 {
1398 if (OutputType.Product == output.Type ||
1399 (OutputType.Transform == output.Type && RowOperation.Add == mediaRow.Operation))
1400 {
1401 if (mediaRow.Cabinet.StartsWith("#", StringComparison.Ordinal))
1402 {
1403 embeddedCabinets.Add(mediaRow.DiskId, mediaRow.Cabinet.Substring(1));
1404 }
1405 else
1406 {
1407 cabinetFiles.Add(Path.Combine(databaseBasePath, mediaRow.Cabinet));
1408 }
1409 }
1410 }
1411 }
1412 }
1413
1414 // extract the embedded cabinet files from the database
1415 if (0 < embeddedCabinets.Count)
1416 {
1417 using (View streamsView = database.OpenView("SELECT `Data` FROM `_Streams` WHERE `Name` = ?"))
1418 {
1419 foreach (int diskId in embeddedCabinets.Keys)
1420 {
1421 using (Record record = new Record(1))
1422 {
1423 record.SetString(1, (string)embeddedCabinets[diskId]);
1424 streamsView.Execute(record);
1425 }
1426
1427 using (Record record = streamsView.Fetch())
1428 {
1429 if (null != record)
1430 {
1431 // since the cabinets are stored in case-sensitive streams inside the msi, but the file system is not case-sensitive,
1432 // embedded cabinets must be extracted to a canonical file name (like their diskid) to ensure extraction will always work
1433 string cabinetFile = Path.Combine(this.TempFilesLocation, String.Concat("Media", Path.DirectorySeparatorChar, diskId.ToString(CultureInfo.InvariantCulture), ".cab"));
1434
1435 // ensure the parent directory exists
1436 System.IO.Directory.CreateDirectory(Path.GetDirectoryName(cabinetFile));
1437
1438 using (FileStream fs = System.IO.File.Create(cabinetFile))
1439 {
1440 int bytesRead;
1441 byte[] buffer = new byte[512];
1442
1443 while (0 != (bytesRead = record.GetStream(1, buffer, buffer.Length)))
1444 {
1445 fs.Write(buffer, 0, bytesRead);
1446 }
1447 }
1448
1449 cabinetFiles.Add(cabinetFile);
1450 }
1451 else
1452 {
1453 // TODO: warning about missing embedded cabinet
1454 }
1455 }
1456 }
1457 }
1458 }
1459
1460 // extract the cabinet files
1461 if (0 < cabinetFiles.Count)
1462 {
1463 string fileDirectory = Path.Combine(exportBasePath, "File");
1464
1465 // delete the directory and its files to prevent cab extraction due to an existing file
1466 if (Directory.Exists(fileDirectory))
1467 {
1468 Directory.Delete(fileDirectory, true);
1469 }
1470
1471 // ensure the directory exists or extraction will fail
1472 Directory.CreateDirectory(fileDirectory);
1473
1474 foreach (string cabinetFile in cabinetFiles)
1475 {
1476 using (WixExtractCab extractCab = new WixExtractCab())
1477 {
1478 try
1479 {
1480 extractCab.Extract(cabinetFile, fileDirectory);
1481 }
1482 catch (FileNotFoundException)
1483 {
1484 throw new WixException(WixErrors.FileNotFound(new SourceLineNumber(databaseFile), cabinetFile));
1485 }
1486 }
1487 }
1488 }
1489 }
1490 }
1491}
diff --git a/src/WixToolset.Core/Util.cs b/src/WixToolset.Core/Util.cs
new file mode 100644
index 00000000..5950fe0a
--- /dev/null
+++ b/src/WixToolset.Core/Util.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
4{
5 using System;
6
7 /// <summary>
8 /// Common Wix utility methods and types.
9 /// </summary>
10 public sealed class Util
11 {
12 /// <summary>
13 /// Set by WixToolTasks to indicate WIX is running inside MSBuild
14 /// </summary>
15 public static bool RunningInMsBuild { get; set; }
16 }
17}
diff --git a/src/WixToolset.Core/Uuid.cs b/src/WixToolset.Core/Uuid.cs
new file mode 100644
index 00000000..2e599793
--- /dev/null
+++ b/src/WixToolset.Core/Uuid.cs
@@ -0,0 +1,89 @@
1// Copyright (c) .NET Foundation and 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.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 sealed class Uuid
14 {
15 /// <summary>
16 /// Protect the constructor.
17 /// </summary>
18 private Uuid()
19 {
20 }
21
22 /// <summary>
23 /// Creates a version 3 name-based UUID.
24 /// </summary>
25 /// <param name="namespaceGuid">The namespace UUID.</param>
26 /// <param name="value">The value.</param>
27 /// <param name="backwardsCompatible">Flag to say to use MD5 instead of better SHA1.</param>
28 /// <returns>The UUID for the given namespace and value.</returns>
29 public static Guid NewUuid(Guid namespaceGuid, string value)
30 {
31 byte[] namespaceBytes = namespaceGuid.ToByteArray();
32 short uuidVersion = (short)0x5000;
33
34 // get the fields of the guid which are in host byte ordering
35 int timeLow = BitConverter.ToInt32(namespaceBytes, 0);
36 short timeMid = BitConverter.ToInt16(namespaceBytes, 4);
37 short timeHiAndVersion = BitConverter.ToInt16(namespaceBytes, 6);
38
39 // convert to network byte ordering
40 timeLow = IPAddress.HostToNetworkOrder(timeLow);
41 timeMid = IPAddress.HostToNetworkOrder(timeMid);
42 timeHiAndVersion = IPAddress.HostToNetworkOrder(timeHiAndVersion);
43
44 // get the bytes from the value
45 byte[] valueBytes = Encoding.Unicode.GetBytes(value);
46
47 // fill-in the hash input buffer
48 byte[] buffer = new byte[namespaceBytes.Length + valueBytes.Length];
49 Buffer.BlockCopy(BitConverter.GetBytes(timeLow), 0, buffer, 0, 4);
50 Buffer.BlockCopy(BitConverter.GetBytes(timeMid), 0, buffer, 4, 2);
51 Buffer.BlockCopy(BitConverter.GetBytes(timeHiAndVersion), 0, buffer, 6, 2);
52 Buffer.BlockCopy(namespaceBytes, 8, buffer, 8, 8);
53 Buffer.BlockCopy(valueBytes, 0, buffer, 16, valueBytes.Length);
54
55 // perform the appropriate hash of the namespace and value
56 byte[] hash;
57 using (SHA1 sha1 = SHA1.Create())
58 {
59 hash = sha1.ComputeHash(buffer);
60 }
61
62 // get the fields of the hash which are in network byte ordering
63 timeLow = BitConverter.ToInt32(hash, 0);
64 timeMid = BitConverter.ToInt16(hash, 4);
65 timeHiAndVersion = BitConverter.ToInt16(hash, 6);
66
67 // convert to network byte ordering
68 timeLow = IPAddress.NetworkToHostOrder(timeLow);
69 timeMid = IPAddress.NetworkToHostOrder(timeMid);
70 timeHiAndVersion = IPAddress.NetworkToHostOrder(timeHiAndVersion);
71
72 // set the version and variant bits
73 timeHiAndVersion &= 0x0FFF;
74 timeHiAndVersion += uuidVersion;
75 hash[8] &= 0x3F;
76 hash[8] |= 0x80;
77
78 // put back the converted values into a 128-bit value
79 byte[] guidBits = new byte[16];
80 Buffer.BlockCopy(hash, 0, guidBits, 0, 16);
81
82 Buffer.BlockCopy(BitConverter.GetBytes(timeLow), 0, guidBits, 0, 4);
83 Buffer.BlockCopy(BitConverter.GetBytes(timeMid), 0, guidBits, 4, 2);
84 Buffer.BlockCopy(BitConverter.GetBytes(timeHiAndVersion), 0, guidBits, 6, 2);
85
86 return new Guid(guidBits);
87 }
88 }
89}
diff --git a/src/WixToolset.Core/Validator.cs b/src/WixToolset.Core/Validator.cs
new file mode 100644
index 00000000..6420b9b7
--- /dev/null
+++ b/src/WixToolset.Core/Validator.cs
@@ -0,0 +1,401 @@
1// Copyright (c) .NET Foundation and 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.Collections.Generic;
7 using System.Collections.Specialized;
8 using System.ComponentModel;
9 using System.Diagnostics.CodeAnalysis;
10 using System.Globalization;
11 using System.IO;
12 using System.Threading;
13 using WixToolset.Data;
14 using WixToolset.Extensibility;
15 using WixToolset.Msi;
16 using WixToolset.Core.Native;
17
18 /// <summary>
19 /// Runs internal consistency evaluators (ICEs) from cub files against a database.
20 /// </summary>
21 public sealed class Validator : IMessageHandler
22 {
23 private string actionName;
24 private StringCollection cubeFiles;
25 private ValidatorExtension extension;
26 private string[] ices;
27 private Output output;
28 private string[] suppressedICEs;
29 private InstallUIHandler validationUIHandler;
30 private bool validationSessionComplete;
31
32 /// <summary>
33 /// Instantiate a new Validator.
34 /// </summary>
35 public Validator()
36 {
37 this.cubeFiles = new StringCollection();
38 this.extension = new ValidatorExtension();
39 this.validationUIHandler = new InstallUIHandler(this.ValidationUIHandler);
40 }
41
42 /// <summary>
43 /// Gets or sets a <see cref="ValidatorExtension"/> that directs messages from the validator.
44 /// </summary>
45 /// <value>A <see cref="ValidatorExtension"/> that directs messages from the validator.</value>
46 public ValidatorExtension Extension
47 {
48 get { return this.extension; }
49 set { this.extension = value; }
50 }
51
52 /// <summary>
53 /// Gets or sets the list of ICEs to run.
54 /// </summary>
55 /// <value>The list of ICEs.</value>
56 [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
57 public string[] ICEs
58 {
59 get { return this.ices; }
60 set { this.ices = value; }
61 }
62
63 /// <summary>
64 /// Gets or sets the output used for finding source line information.
65 /// </summary>
66 /// <value>The output used for finding source line information.</value>
67 public Output Output
68 {
69 // cache Output object until validation for changes in extension
70 get { return this.output; }
71 set { this.output = value; }
72 }
73
74 /// <summary>
75 /// Gets or sets the suppressed ICEs.
76 /// </summary>
77 /// <value>The suppressed ICEs.</value>
78 [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
79 public string[] SuppressedICEs
80 {
81 get { return this.suppressedICEs; }
82 set { this.suppressedICEs = value; }
83 }
84
85 /// <summary>
86 /// Sets the temporary path for the Binder.
87 /// </summary>
88 public string TempFilesLocation { private get; set; }
89
90 /// <summary>
91 /// Add a cube file to the validation run.
92 /// </summary>
93 /// <param name="cubeFile">A cube file.</param>
94 public void AddCubeFile(string cubeFile)
95 {
96 this.cubeFiles.Add(cubeFile);
97 }
98
99 /// <summary>
100 /// Validate a database.
101 /// </summary>
102 /// <param name="databaseFile">The database to validate.</param>
103 /// <returns>true if validation succeeded; false otherwise.</returns>
104 public void Validate(string databaseFile)
105 {
106 Dictionary<string, string> indexedICEs = new Dictionary<string, string>();
107 Dictionary<string, string> indexedSuppressedICEs = new Dictionary<string, string>();
108 int previousUILevel = (int)InstallUILevels.Basic;
109 IntPtr previousHwnd = IntPtr.Zero;
110 InstallUIHandler previousUIHandler = null;
111
112 if (null == databaseFile)
113 {
114 throw new ArgumentNullException("databaseFile");
115 }
116
117 // initialize the validator extension
118 this.extension.DatabaseFile = databaseFile;
119 this.extension.Output = this.output;
120 this.extension.InitializeValidator();
121
122 // Ensure the temporary files can be created.
123 Directory.CreateDirectory(this.TempFilesLocation);
124
125 // index the ICEs
126 if (null != this.ices)
127 {
128 foreach (string ice in this.ices)
129 {
130 indexedICEs[ice] = null;
131 }
132 }
133
134 // index the suppressed ICEs
135 if (null != this.suppressedICEs)
136 {
137 foreach (string suppressedICE in this.suppressedICEs)
138 {
139 indexedSuppressedICEs[suppressedICE] = null;
140 }
141 }
142
143 // copy the database to a temporary location so it can be manipulated
144 string tempDatabaseFile = Path.Combine(this.TempFilesLocation, Path.GetFileName(databaseFile));
145 File.Copy(databaseFile, tempDatabaseFile);
146
147 // remove the read-only property from the temporary database
148 FileAttributes attributes = File.GetAttributes(tempDatabaseFile);
149 File.SetAttributes(tempDatabaseFile, attributes & ~FileAttributes.ReadOnly);
150
151 Mutex mutex = new Mutex(false, "WixValidator");
152 try
153 {
154 if (!mutex.WaitOne(0, false))
155 {
156 this.OnMessage(WixVerboses.ValidationSerialized());
157 mutex.WaitOne();
158 }
159
160 using (Database database = new Database(tempDatabaseFile, OpenDatabase.Direct))
161 {
162 bool propertyTableExists = database.TableExists("Property");
163 string productCode = null;
164
165 // remove the product code from the database before opening a session to prevent opening an installed product
166 if (propertyTableExists)
167 {
168 using (View view = database.OpenExecuteView("SELECT `Value` FROM `Property` WHERE Property = 'ProductCode'"))
169 {
170 using (Record record = view.Fetch())
171 {
172 if (null != record)
173 {
174 productCode = record.GetString(1);
175
176 using (View dropProductCodeView = database.OpenExecuteView("DELETE FROM `Property` WHERE `Property` = 'ProductCode'"))
177 {
178 }
179 }
180 }
181 }
182 }
183
184 // merge in the cube databases
185 foreach (string cubeFile in this.cubeFiles)
186 {
187 try
188 {
189 using (Database cubeDatabase = new Database(cubeFile, OpenDatabase.ReadOnly))
190 {
191 try
192 {
193 database.Merge(cubeDatabase, "MergeConflicts");
194 }
195 catch
196 {
197 // ignore merge errors since they are expected in the _Validation table
198 }
199 }
200 }
201 catch (Win32Exception e)
202 {
203 if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED
204 {
205 throw new WixException(WixErrors.CubeFileNotFound(cubeFile));
206 }
207
208 throw;
209 }
210 }
211
212 // commit the database before proceeding to ensure the streams don't get confused
213 database.Commit();
214
215 // the property table may have been added to the database
216 // from a cub database without the proper validation rows
217 if (!propertyTableExists)
218 {
219 using (View view = database.OpenExecuteView("DROP table `Property`"))
220 {
221 }
222 }
223
224 // get all the action names for ICEs which have not been suppressed
225 List<string> actions = new List<string>();
226 using (View view = database.OpenExecuteView("SELECT `Action` FROM `_ICESequence` ORDER BY `Sequence`"))
227 {
228 while (true)
229 {
230 using (Record record = view.Fetch())
231 {
232 if (null == record)
233 {
234 break;
235 }
236
237 string action = record.GetString(1);
238
239 if (!indexedSuppressedICEs.ContainsKey(action))
240 {
241 actions.Add(action);
242 }
243 }
244 }
245 }
246
247 if (0 != indexedICEs.Count)
248 {
249 // Walk backwards and remove those that arent in the list
250 for (int i = actions.Count - 1; 0 <= i; i--)
251 {
252 if (!indexedICEs.ContainsKey(actions[i]))
253 {
254 actions.RemoveAt(i);
255 }
256 }
257 }
258
259 // disable the internal UI handler and set an external UI handler
260 previousUILevel = Installer.SetInternalUI((int)InstallUILevels.None, ref previousHwnd);
261 previousUIHandler = Installer.SetExternalUI(this.validationUIHandler, (int)InstallLogModes.Error | (int)InstallLogModes.Warning | (int)InstallLogModes.User, IntPtr.Zero);
262
263 // create a session for running the ICEs
264 this.validationSessionComplete = false;
265 using (Session session = new Session(database))
266 {
267 // add the product code back into the database
268 if (null != productCode)
269 {
270 // some CUBs erroneously have a ProductCode property, so delete it if we just picked one up
271 using (View dropProductCodeView = database.OpenExecuteView("DELETE FROM `Property` WHERE `Property` = 'ProductCode'"))
272 {
273 }
274
275 using (View view = database.OpenExecuteView(String.Format(CultureInfo.InvariantCulture, "INSERT INTO `Property` (`Property`, `Value`) VALUES ('ProductCode', '{0}')", productCode)))
276 {
277 }
278 }
279
280 foreach (string action in actions)
281 {
282 this.actionName = action;
283 try
284 {
285 session.DoAction(action);
286 }
287 catch (Win32Exception e)
288 {
289 if (!Messaging.Instance.EncounteredError)
290 {
291 throw e;
292 }
293 // TODO: Review why this was clearing the error state when an exception had happened but an error was already encountered. That's weird.
294 //else
295 //{
296 // this.encounteredError = false;
297 //}
298 }
299 this.actionName = null;
300 }
301
302 // Mark the validation session complete so we ignore any messages that MSI may fire
303 // during session clean-up.
304 this.validationSessionComplete = true;
305 }
306 }
307 }
308 catch (Win32Exception e)
309 {
310 // avoid displaying errors twice since one may have already occurred in the UI handler
311 if (!Messaging.Instance.EncounteredError)
312 {
313 if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED
314 {
315 // databaseFile is not passed since during light
316 // this would be the temporary copy and there would be
317 // no final output since the error occured; during smoke
318 // they should know the path passed into smoke
319 this.OnMessage(WixErrors.ValidationFailedToOpenDatabase());
320 }
321 else if (0x64D == e.NativeErrorCode)
322 {
323 this.OnMessage(WixErrors.ValidationFailedDueToLowMsiEngine());
324 }
325 else if (0x654 == e.NativeErrorCode)
326 {
327 this.OnMessage(WixErrors.ValidationFailedDueToInvalidPackage());
328 }
329 else if (0x658 == e.NativeErrorCode)
330 {
331 this.OnMessage(WixErrors.ValidationFailedDueToMultilanguageMergeModule());
332 }
333 else if (0x659 == e.NativeErrorCode)
334 {
335 this.OnMessage(WixWarnings.ValidationFailedDueToSystemPolicy());
336 }
337 else
338 {
339 string msgTemp = e.Message;
340
341 if (null != this.actionName)
342 {
343 msgTemp = String.Concat("Action - '", this.actionName, "' ", e.Message);
344 }
345
346 this.OnMessage(WixErrors.Win32Exception(e.NativeErrorCode, msgTemp));
347 }
348 }
349 }
350 finally
351 {
352 Installer.SetExternalUI(previousUIHandler, 0, IntPtr.Zero);
353 Installer.SetInternalUI(previousUILevel, ref previousHwnd);
354
355 this.validationSessionComplete = false; // no validation session at this point, so reset the completion flag.
356
357 mutex.ReleaseMutex();
358 this.cubeFiles.Clear();
359 this.extension.FinalizeValidator();
360 }
361 }
362
363 /// <summary>
364 /// Sends a message to the message delegate if there is one.
365 /// </summary>
366 /// <param name="mea">Message event arguments.</param>
367 public void OnMessage(MessageEventArgs e)
368 {
369 Messaging.Instance.OnMessage(e);
370 this.extension.OnMessage(e);
371 }
372
373 /// <summary>
374 /// The validation external UI handler.
375 /// </summary>
376 /// <param name="context">Pointer to an application context.
377 /// This parameter can be used for error checking.</param>
378 /// <param name="messageType">Specifies a combination of one message box style,
379 /// one message box icon type, one default button, and one installation message type.</param>
380 /// <param name="message">Specifies the message text.</param>
381 /// <returns>-1 for an error, 0 if no action was taken, 1 if OK, 3 to abort.</returns>
382 private int ValidationUIHandler(IntPtr context, uint messageType, string message)
383 {
384 try
385 {
386 // If we're getting messges during the validation session, send them to
387 // the extension. Otherwise, ignore the messages.
388 if (!this.validationSessionComplete)
389 {
390 this.extension.Log(message, this.actionName);
391 }
392 }
393 catch (WixException ex)
394 {
395 this.OnMessage(ex.Error);
396 }
397
398 return 1;
399 }
400 }
401}
diff --git a/src/WixToolset.Core/VerifyInterop.cs b/src/WixToolset.Core/VerifyInterop.cs
new file mode 100644
index 00000000..81fbec65
--- /dev/null
+++ b/src/WixToolset.Core/VerifyInterop.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
4{
5 using System;
6 using System.Collections;
7 using System.Runtime.CompilerServices;
8 using System.Runtime.InteropServices;
9
10 internal class VerifyInterop
11 {
12 internal const string GenericVerify2 = "00AAC56B-CD44-11d0-8CC2-00C04FC295EE";
13 internal const uint WTD_UI_NONE = 2;
14 internal const uint WTD_REVOKE_NONE = 0;
15 internal const uint WTD_CHOICE_CATALOG = 2;
16 internal const uint WTD_STATEACTION_VERIFY = 1;
17 internal const uint WTD_REVOCATION_CHECK_NONE = 0x10;
18 internal const int ErrorInsufficientBuffer = 122;
19
20 [StructLayout(LayoutKind.Sequential)]
21 internal struct WinTrustData
22 {
23 internal uint cbStruct;
24 internal IntPtr pPolicyCallbackData;
25 internal IntPtr pSIPClientData;
26 internal uint dwUIChoice;
27 internal uint fdwRevocationChecks;
28 internal uint dwUnionChoice;
29 internal IntPtr pCatalog;
30 internal uint dwStateAction;
31 internal IntPtr hWVTStateData;
32 [MarshalAs(UnmanagedType.LPWStr)]
33 internal string pwszURLReference;
34 internal uint dwProvFlags;
35 internal uint dwUIContext;
36 }
37
38 [StructLayout(LayoutKind.Sequential)]
39 internal struct WinTrustCatalogInfo
40 {
41 internal uint cbStruct;
42 internal uint dwCatalogVersion;
43 [MarshalAs(UnmanagedType.LPWStr)]
44 internal string pcwszCatalogFilePath;
45 [MarshalAs(UnmanagedType.LPWStr)]
46 internal string pcwszMemberTag;
47 [MarshalAs(UnmanagedType.LPWStr)]
48 internal string pcwszMemberFilePath;
49 internal IntPtr hMemberFile;
50 internal IntPtr pbCalculatedFileHash;
51 internal uint cbCalculatedFileHash;
52 internal IntPtr pcCatalogContext;
53 }
54
55 [DllImport("wintrust.dll", SetLastError = true)]
56 internal static extern long WinVerifyTrust(IntPtr windowHandle, ref Guid actionGuid, ref WinTrustData trustData);
57
58 [DllImport("wintrust.dll", SetLastError = true)]
59 [return: MarshalAs(UnmanagedType.Bool)]
60 internal static extern bool CryptCATAdminCalcHashFromFileHandle(
61 IntPtr fileHandle,
62 [In, Out]
63 ref uint hashSize,
64 [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]
65 byte[] hashBytes,
66 uint flags);
67 }
68}
diff --git a/src/WixToolset.Core/WixComponentSearchInfo.cs b/src/WixToolset.Core/WixComponentSearchInfo.cs
new file mode 100644
index 00000000..dfd5d8ba
--- /dev/null
+++ b/src/WixToolset.Core/WixComponentSearchInfo.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 WixToolset
4{
5 using System;
6 using System.Xml;
7 using WixToolset.Data;
8
9 /// <summary>
10 /// Utility class for all WixComponentSearches.
11 /// </summary>
12 internal class WixComponentSearchInfo : WixSearchInfo
13 {
14 public WixComponentSearchInfo(Row row)
15 : this((string)row[0], (string)row[1], (string)row[2], (int)row[3])
16 {
17 }
18
19 public WixComponentSearchInfo(string id, string guid, string productCode, int attributes)
20 : base(id)
21 {
22 this.Guid = guid;
23 this.ProductCode = productCode;
24 this.Attributes = (WixComponentSearchAttributes)attributes;
25 }
26
27 public string Guid { get; private set; }
28 public string ProductCode { get; private set; }
29 public WixComponentSearchAttributes Attributes { get; private set; }
30
31 /// <summary>
32 /// Generates Burn manifest and ParameterInfo-style markup for a component search.
33 /// </summary>
34 /// <param name="writer"></param>
35 public override void WriteXml(XmlTextWriter writer)
36 {
37 writer.WriteStartElement("MsiComponentSearch");
38 this.WriteWixSearchAttributes(writer);
39
40 writer.WriteAttributeString("ComponentId", this.Guid);
41
42 if (!String.IsNullOrEmpty(this.ProductCode))
43 {
44 writer.WriteAttributeString("ProductCode", this.ProductCode);
45 }
46
47 if (0 != (this.Attributes & WixComponentSearchAttributes.KeyPath))
48 {
49 writer.WriteAttributeString("Type", "keyPath");
50 }
51 else if (0 != (this.Attributes & WixComponentSearchAttributes.State))
52 {
53 writer.WriteAttributeString("Type", "state");
54 }
55 else if (0 != (this.Attributes & WixComponentSearchAttributes.WantDirectory))
56 {
57 writer.WriteAttributeString("Type", "directory");
58 }
59
60 writer.WriteEndElement();
61 }
62 }
63
64}
diff --git a/src/WixToolset.Core/WixDistribution.cs b/src/WixToolset.Core/WixDistribution.cs
new file mode 100644
index 00000000..4ceb8982
--- /dev/null
+++ b/src/WixToolset.Core/WixDistribution.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
3using System;
4using System.Diagnostics;
5using System.Reflection;
6using System.Resources;
7
8[assembly: NeutralResourcesLanguage("en-US")]
9
10namespace WixToolset
11{
12 /// <summary>
13 /// Distribution specific strings.
14 /// </summary>
15 internal static class WixDistribution
16 {
17 /// <summary>
18 /// News URL for the distribution.
19 /// </summary>
20 public static string NewsUrl = "http://wixtoolset.org/news/";
21
22 /// <summary>
23 /// Short product name for the distribution.
24 /// </summary>
25 public static string ShortProduct = "WiX Toolset";
26
27 /// <summary>
28 /// Support URL for the distribution.
29 /// </summary>
30 public static string SupportUrl = "http://wixtoolset.org/";
31
32 /// <summary>
33 /// Telemetry URL format for the distribution.
34 /// </summary>
35 public static string TelemetryUrlFormat = "http://wixtoolset.org/telemetry/v{0}/?r={1}";
36
37 /// <summary>
38 /// VS Extensions Landing page Url for the distribution.
39 /// </summary>
40 public static string VSExtensionsLandingUrl = "http://wixtoolset.org/releases/";
41
42 public static string ReplacePlaceholders(string original, Assembly assembly)
43 {
44 if (null != assembly)
45 {
46 FileVersionInfo fileVersion = FileVersionInfo.GetVersionInfo(assembly.Location);
47
48 original = original.Replace("[FileComments]", fileVersion.Comments);
49 original = original.Replace("[FileCopyright]", fileVersion.LegalCopyright);
50 original = original.Replace("[FileProductName]", fileVersion.ProductName);
51 original = original.Replace("[FileVersion]", fileVersion.FileVersion);
52
53 if (original.Contains("[FileVersionMajorMinor]"))
54 {
55 Version version = new Version(fileVersion.FileVersion);
56 original = original.Replace("[FileVersionMajorMinor]", String.Concat(version.Major, ".", version.Minor));
57 }
58
59 AssemblyCompanyAttribute company;
60 if (WixDistribution.TryGetAttribute(assembly, out company))
61 {
62 original = original.Replace("[AssemblyCompany]", company.Company);
63 }
64
65 AssemblyCopyrightAttribute copyright;
66 if (WixDistribution.TryGetAttribute(assembly, out copyright))
67 {
68 original = original.Replace("[AssemblyCopyright]", copyright.Copyright);
69 }
70
71 AssemblyDescriptionAttribute description;
72 if (WixDistribution.TryGetAttribute(assembly, out description))
73 {
74 original = original.Replace("[AssemblyDescription]", description.Description);
75 }
76
77 AssemblyProductAttribute product;
78 if (WixDistribution.TryGetAttribute(assembly, out product))
79 {
80 original = original.Replace("[AssemblyProduct]", product.Product);
81 }
82
83 AssemblyTitleAttribute title;
84 if (WixDistribution.TryGetAttribute(assembly, out title))
85 {
86 original = original.Replace("[AssemblyTitle]", title.Title);
87 }
88 }
89
90 original = original.Replace("[NewsUrl]", WixDistribution.NewsUrl);
91 original = original.Replace("[ShortProduct]", WixDistribution.ShortProduct);
92 original = original.Replace("[SupportUrl]", WixDistribution.SupportUrl);
93 return original;
94 }
95
96 private static bool TryGetAttribute<T>(Assembly assembly, out T attribute) where T : Attribute
97 {
98 attribute = null;
99
100 object[] customAttributes = assembly.GetCustomAttributes(typeof(T), false);
101 if (null != customAttributes && 0 < customAttributes.Length)
102 {
103 attribute = customAttributes[0] as T;
104 }
105
106 return null != attribute;
107 }
108 }
109}
diff --git a/src/WixToolset.Core/WixDistributionSpecificStrings.Designer.cs b/src/WixToolset.Core/WixDistributionSpecificStrings.Designer.cs
new file mode 100644
index 00000000..841f6ca8
--- /dev/null
+++ b/src/WixToolset.Core/WixDistributionSpecificStrings.Designer.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 {
4 using System;
5
6
7 /// <summary>
8 /// A strongly-typed resource class, for looking up localized strings, etc.
9 /// </summary>
10 // This class was auto-generated by the StronglyTypedResourceBuilder
11 // class via a tool like ResGen or Visual Studio.
12 // To add or remove a member, edit your .ResX file then rerun ResGen
13 // with the /str option, or rebuild your VS project.
14 [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "2.0.0.0")]
15 [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
16 [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
17 internal class WixDistributionSpecificStrings {
18
19 private static global::System.Resources.ResourceManager resourceMan;
20
21 private static global::System.Globalization.CultureInfo resourceCulture;
22
23 [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
24 internal WixDistributionSpecificStrings() {
25 }
26
27 /// <summary>
28 /// Returns the cached ResourceManager instance used by this class.
29 /// </summary>
30 [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
31 internal static global::System.Resources.ResourceManager ResourceManager {
32 get {
33 if (object.ReferenceEquals(resourceMan, null)) {
34 global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("WixToolset.WixDistributionSpecificStrings", typeof(WixDistributionSpecificStrings).Assembly);
35 resourceMan = temp;
36 }
37 return resourceMan;
38 }
39 }
40
41 /// <summary>
42 /// Overrides the current thread's CurrentUICulture property for all
43 /// resource lookups using this strongly typed resource class.
44 /// </summary>
45 [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
46 internal static global::System.Globalization.CultureInfo Culture {
47 get {
48 return resourceCulture;
49 }
50 set {
51 resourceCulture = value;
52 }
53 }
54
55 /// <summary>
56 /// Looks up a localized string similar to
57 ///For more information see: http://wix.sourceforge.net
58 ///.
59 /// </summary>
60 internal static string ToolsetHelpFooter {
61 get {
62 return ResourceManager.GetString("ToolsetHelpFooter", resourceCulture);
63 }
64 }
65
66 /// <summary>
67 /// Looks up a localized string similar to WiX {0} version {1}
68 ///Copyright (C) .NET Foundation and contributors. All rights reserved.
69 ///.
70 /// </summary>
71 internal static string ToolsetHelpHeader {
72 get {
73 return ResourceManager.GetString("ToolsetHelpHeader", resourceCulture);
74 }
75 }
76 }
77}
diff --git a/src/WixToolset.Core/WixDistributionSpecificStrings.resx b/src/WixToolset.Core/WixDistributionSpecificStrings.resx
new file mode 100644
index 00000000..7cca2d4e
--- /dev/null
+++ b/src/WixToolset.Core/WixDistributionSpecificStrings.resx
@@ -0,0 +1,130 @@
1<?xml version="1.0" encoding="utf-8"?>
2<root>
3 <!--
4 Microsoft ResX Schema
5
6 Version 2.0
7
8 The primary goals of this format is to allow a simple XML format
9 that is mostly human readable. The generation and parsing of the
10 various data types are done through the TypeConverter classes
11 associated with the data types.
12
13 Example:
14
15 ... ado.net/XML headers & schema ...
16 <resheader name="resmimetype">text/microsoft-resx</resheader>
17 <resheader name="version">2.0</resheader>
18 <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
19 <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
20 <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
21 <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
22 <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
23 <value>[base64 mime encoded serialized .NET Framework object]</value>
24 </data>
25 <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
26 <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
27 <comment>This is a comment</comment>
28 </data>
29
30 There are any number of "resheader" rows that contain simple
31 name/value pairs.
32
33 Each data row contains a name, and value. The row also contains a
34 type or mimetype. Type corresponds to a .NET class that support
35 text/value conversion through the TypeConverter architecture.
36 Classes that don't support this are serialized and stored with the
37 mimetype set.
38
39 The mimetype is used for serialized objects, and tells the
40 ResXResourceReader how to depersist the object. This is currently not
41 extensible. For a given mimetype the value must be set accordingly:
42
43 Note - application/x-microsoft.net.object.binary.base64 is the format
44 that the ResXResourceWriter will generate, however the reader can
45 read any of the formats listed below.
46
47 mimetype: application/x-microsoft.net.object.binary.base64
48 value : The object must be serialized with
49 : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
50 : and then encoded with base64 encoding.
51
52 mimetype: application/x-microsoft.net.object.soap.base64
53 value : The object must be serialized with
54 : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
55 : and then encoded with base64 encoding.
56
57 mimetype: application/x-microsoft.net.object.bytearray.base64
58 value : The object must be serialized into a byte array
59 : using a System.ComponentModel.TypeConverter
60 : and then encoded with base64 encoding.
61 -->
62 <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
63 <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
64 <xsd:element name="root" msdata:IsDataSet="true">
65 <xsd:complexType>
66 <xsd:choice maxOccurs="unbounded">
67 <xsd:element name="metadata">
68 <xsd:complexType>
69 <xsd:sequence>
70 <xsd:element name="value" type="xsd:string" minOccurs="0" />
71 </xsd:sequence>
72 <xsd:attribute name="name" use="required" type="xsd:string" />
73 <xsd:attribute name="type" type="xsd:string" />
74 <xsd:attribute name="mimetype" type="xsd:string" />
75 <xsd:attribute ref="xml:space" />
76 </xsd:complexType>
77 </xsd:element>
78 <xsd:element name="assembly">
79 <xsd:complexType>
80 <xsd:attribute name="alias" type="xsd:string" />
81 <xsd:attribute name="name" type="xsd:string" />
82 </xsd:complexType>
83 </xsd:element>
84 <xsd:element name="data">
85 <xsd:complexType>
86 <xsd:sequence>
87 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
88 <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
89 </xsd:sequence>
90 <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
91 <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
92 <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
93 <xsd:attribute ref="xml:space" />
94 </xsd:complexType>
95 </xsd:element>
96 <xsd:element name="resheader">
97 <xsd:complexType>
98 <xsd:sequence>
99 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
100 </xsd:sequence>
101 <xsd:attribute name="name" type="xsd:string" use="required" />
102 </xsd:complexType>
103 </xsd:element>
104 </xsd:choice>
105 </xsd:complexType>
106 </xsd:element>
107 </xsd:schema>
108 <resheader name="resmimetype">
109 <value>text/microsoft-resx</value>
110 </resheader>
111 <resheader name="version">
112 <value>2.0</value>
113 </resheader>
114 <resheader name="reader">
115 <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
116 </resheader>
117 <resheader name="writer">
118 <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
119 </resheader>
120 <data name="ToolsetHelpFooter" xml:space="preserve">
121 <value>
122For more information see: [SupportUrl]
123</value>
124 </data>
125 <data name="ToolsetHelpHeader" xml:space="preserve">
126 <value>[AssemblyProduct] [AssemblyDescription] version [FileVersion]
127[AssemblyCopyright]
128</value>
129 </data>
130</root> \ No newline at end of file
diff --git a/src/WixToolset.Core/WixFileSearchInfo.cs b/src/WixToolset.Core/WixFileSearchInfo.cs
new file mode 100644
index 00000000..e53f7bf7
--- /dev/null
+++ b/src/WixToolset.Core/WixFileSearchInfo.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
4{
5 using System;
6 using System.Xml;
7 using WixToolset.Data;
8
9 /// <summary>
10 /// Utility class for all WixFileSearches (file and directory searches).
11 /// </summary>
12 internal class WixFileSearchInfo : WixSearchInfo
13 {
14 public WixFileSearchInfo(Row row)
15 : this((string)row[0], (string)row[1], (int)row[9])
16 {
17 }
18
19 public WixFileSearchInfo(string id, string path, int attributes)
20 : base(id)
21 {
22 this.Path = path;
23 this.Attributes = (WixFileSearchAttributes)attributes;
24 }
25
26 public string Path { get; private set; }
27 public WixFileSearchAttributes Attributes { get; private set; }
28
29 /// <summary>
30 /// Generates Burn manifest and ParameterInfo-style markup for a file/directory search.
31 /// </summary>
32 /// <param name="writer"></param>
33 public override void WriteXml(XmlTextWriter writer)
34 {
35 writer.WriteStartElement((0 == (this.Attributes & WixFileSearchAttributes.IsDirectory)) ? "FileSearch" : "DirectorySearch");
36 this.WriteWixSearchAttributes(writer);
37 writer.WriteAttributeString("Path", this.Path);
38 if (WixFileSearchAttributes.WantExists == (this.Attributes & WixFileSearchAttributes.WantExists))
39 {
40 writer.WriteAttributeString("Type", "exists");
41 }
42 else if (WixFileSearchAttributes.WantVersion == (this.Attributes & WixFileSearchAttributes.WantVersion))
43 {
44 // Can never get here for DirectorySearch.
45 writer.WriteAttributeString("Type", "version");
46 }
47 else
48 {
49 writer.WriteAttributeString("Type", "path");
50 }
51 writer.WriteEndElement();
52 }
53 }
54}
diff --git a/src/WixToolset.Core/WixGenericMessageEventArgs.cs b/src/WixToolset.Core/WixGenericMessageEventArgs.cs
new file mode 100644
index 00000000..2c1d4705
--- /dev/null
+++ b/src/WixToolset.Core/WixGenericMessageEventArgs.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
4{
5 using System;
6 using System.Resources;
7 using WixToolset.Data;
8
9 /// <summary>
10 /// Generic event args for message events.
11 /// </summary>
12 public class WixGenericMessageEventArgs : MessageEventArgs
13 {
14 /// <summary>
15 /// Creates a new generc message event arg.
16 /// </summary>
17 /// <param name="sourceLineNumbers">Source line numbers for the message.</param>
18 /// <param name="id">Id for the message.</param>
19 /// <param name="level">Level for the message.</param>
20 /// <param name="format">Format message for arguments.</param>
21 /// <param name="messageArgs">Arguments for the format string.</param>
22 public WixGenericMessageEventArgs(SourceLineNumber sourceLineNumbers, int id, MessageLevel level, string format, params object[] messageArgs)
23 : base(sourceLineNumbers, id, format, messageArgs)
24 {
25 base.Level = level;
26 base.ResourceManager = new GenericResourceManager();
27 }
28
29 /// <summary>
30 /// Private resource manager to return our format message as the "localized" string untouched.
31 /// </summary>
32 private class GenericResourceManager : ResourceManager
33 {
34 /// <summary>
35 /// Passes the "resource name" through as the format string.
36 /// </summary>
37 /// <param name="name">Format message that is passed in as the resource name.</param>
38 /// <returns>The name.</returns>
39 public override string GetString(string name)
40 {
41 return name;
42 }
43 }
44 }
45}
diff --git a/src/WixToolset.Core/WixProductSearchInfo.cs b/src/WixToolset.Core/WixProductSearchInfo.cs
new file mode 100644
index 00000000..4c57d8be
--- /dev/null
+++ b/src/WixToolset.Core/WixProductSearchInfo.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 WixToolset
4{
5 using System;
6 using System.Xml;
7 using WixToolset.Data;
8
9 /// <summary>
10 /// Utility class for all WixProductSearches.
11 /// </summary>
12 internal class WixProductSearchInfo : WixSearchInfo
13 {
14 public WixProductSearchInfo(Row row)
15 : this((string)row[0], (string)row[1], (int)row[2])
16 {
17 }
18
19 public WixProductSearchInfo(string id, string guid, int attributes)
20 : base(id)
21 {
22 this.Guid = guid;
23 this.Attributes = (WixProductSearchAttributes)attributes;
24 }
25
26 public string Guid { get; private set; }
27 public WixProductSearchAttributes Attributes { get; private set; }
28
29 /// <summary>
30 /// Generates Burn manifest and ParameterInfo-style markup for a product search.
31 /// </summary>
32 /// <param name="writer"></param>
33 public override void WriteXml(XmlTextWriter writer)
34 {
35 writer.WriteStartElement("MsiProductSearch");
36 this.WriteWixSearchAttributes(writer);
37
38 if (0 != (this.Attributes & WixProductSearchAttributes.UpgradeCode))
39 {
40 writer.WriteAttributeString("UpgradeCode", this.Guid);
41 }
42 else
43 {
44 writer.WriteAttributeString("ProductCode", this.Guid);
45 }
46
47 if (0 != (this.Attributes & WixProductSearchAttributes.Version))
48 {
49 writer.WriteAttributeString("Type", "version");
50 }
51 else if (0 != (this.Attributes & WixProductSearchAttributes.Language))
52 {
53 writer.WriteAttributeString("Type", "language");
54 }
55 else if (0 != (this.Attributes & WixProductSearchAttributes.State))
56 {
57 writer.WriteAttributeString("Type", "state");
58 }
59 else if (0 != (this.Attributes & WixProductSearchAttributes.Assignment))
60 {
61 writer.WriteAttributeString("Type", "assignment");
62 }
63
64 writer.WriteEndElement();
65 }
66 }
67}
diff --git a/src/WixToolset.Core/WixRegistrySearchInfo.cs b/src/WixToolset.Core/WixRegistrySearchInfo.cs
new file mode 100644
index 00000000..e8d7ce9b
--- /dev/null
+++ b/src/WixToolset.Core/WixRegistrySearchInfo.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
4{
5 using System;
6 using System.Xml;
7 using WixToolset.Data;
8
9 /// <summary>
10 /// Utility class for all WixRegistrySearches.
11 /// </summary>
12 internal class WixRegistrySearchInfo : WixSearchInfo
13 {
14 public WixRegistrySearchInfo(Row row)
15 : this((string)row[0], (int)row[1], (string)row[2], (string)row[3], (int)row[4])
16 {
17 }
18
19 public WixRegistrySearchInfo(string id, int root, string key, string value, int attributes)
20 : base(id)
21 {
22 this.Root = root;
23 this.Key = key;
24 this.Value = value;
25 this.Attributes = (WixRegistrySearchAttributes)attributes;
26 }
27
28 public int Root { get; private set; }
29 public string Key { get; private set; }
30 public string Value { get; private set; }
31 public WixRegistrySearchAttributes Attributes { get; private set; }
32
33 /// <summary>
34 /// Generates Burn manifest and ParameterInfo-style markup for a registry search.
35 /// </summary>
36 /// <param name="writer"></param>
37 public override void WriteXml(XmlTextWriter writer)
38 {
39 writer.WriteStartElement("RegistrySearch");
40 this.WriteWixSearchAttributes(writer);
41
42 switch (this.Root)
43 {
44 case Core.Native.MsiInterop.MsidbRegistryRootClassesRoot:
45 writer.WriteAttributeString("Root", "HKCR");
46 break;
47 case Core.Native.MsiInterop.MsidbRegistryRootCurrentUser:
48 writer.WriteAttributeString("Root", "HKCU");
49 break;
50 case Core.Native.MsiInterop.MsidbRegistryRootLocalMachine:
51 writer.WriteAttributeString("Root", "HKLM");
52 break;
53 case Core.Native.MsiInterop.MsidbRegistryRootUsers:
54 writer.WriteAttributeString("Root", "HKU");
55 break;
56 }
57
58 writer.WriteAttributeString("Key", this.Key);
59
60 if (!String.IsNullOrEmpty(this.Value))
61 {
62 writer.WriteAttributeString("Value", this.Value);
63 }
64
65 bool existenceOnly = 0 != (this.Attributes & WixRegistrySearchAttributes.WantExists);
66
67 writer.WriteAttributeString("Type", existenceOnly ? "exists" : "value");
68
69 if (0 != (this.Attributes & WixRegistrySearchAttributes.Win64))
70 {
71 writer.WriteAttributeString("Win64", "yes");
72 }
73
74 if (!existenceOnly)
75 {
76 if (0 != (this.Attributes & WixRegistrySearchAttributes.ExpandEnvironmentVariables))
77 {
78 writer.WriteAttributeString("ExpandEnvironment", "yes");
79 }
80
81 // We *always* say this is VariableType="string". If we end up
82 // needing to be more specific, we will have to expand the "Format"
83 // attribute to allow "number" and "version".
84
85 writer.WriteAttributeString("VariableType", "string");
86 }
87
88 writer.WriteEndElement();
89 }
90 }
91
92}
diff --git a/src/WixToolset.Core/WixSearchInfo.cs b/src/WixToolset.Core/WixSearchInfo.cs
new file mode 100644
index 00000000..906365a2
--- /dev/null
+++ b/src/WixToolset.Core/WixSearchInfo.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
4{
5 using System;
6 using System.Diagnostics;
7 using System.Xml;
8 using WixToolset.Data;
9
10 /// <summary>
11 /// Utility base class for all WixSearches.
12 /// </summary>
13 internal abstract class WixSearchInfo
14 {
15 public WixSearchInfo(string id)
16 {
17 this.Id = id;
18 }
19
20 public void AddWixSearchRowInfo(Row row)
21 {
22 Debug.Assert((string)row[0] == Id);
23 Variable = (string)row[1];
24 Condition = (string)row[2];
25 }
26
27 public string Id { get; private set; }
28 public string Variable { get; private set; }
29 public string Condition { get; private set; }
30
31 /// <summary>
32 /// Generates Burn manifest and ParameterInfo-style markup a search.
33 /// </summary>
34 /// <param name="writer"></param>
35 public virtual void WriteXml(XmlTextWriter writer)
36 {
37 }
38
39 /// <summary>
40 /// Writes attributes common to all WixSearch elements.
41 /// </summary>
42 /// <param name="writer"></param>
43 protected void WriteWixSearchAttributes(XmlTextWriter writer)
44 {
45 writer.WriteAttributeString("Id", this.Id);
46 writer.WriteAttributeString("Variable", this.Variable);
47 if (!String.IsNullOrEmpty(this.Condition))
48 {
49 writer.WriteAttributeString("Condition", this.Condition);
50 }
51 }
52 }
53}
diff --git a/src/WixToolset.Core/WixStrings.Designer.cs b/src/WixToolset.Core/WixStrings.Designer.cs
new file mode 100644
index 00000000..4ba9381a
--- /dev/null
+++ b/src/WixToolset.Core/WixStrings.Designer.cs
@@ -0,0 +1,442 @@
1// Copyright (c) .NET Foundation and 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 using System;
5
6
7 /// <summary>
8 /// A strongly-typed resource class, for looking up localized strings, etc.
9 /// </summary>
10 // This class was auto-generated by the StronglyTypedResourceBuilder
11 // class via a tool like ResGen or Visual Studio.
12 // To add or remove a member, edit your .ResX file then rerun ResGen
13 // with the /str option, or rebuild your VS project.
14 [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
15 [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
16 [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
17 internal class WixStrings {
18
19 private static global::System.Resources.ResourceManager resourceMan;
20
21 private static global::System.Globalization.CultureInfo resourceCulture;
22
23 [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
24 internal WixStrings() {
25 }
26
27 /// <summary>
28 /// Returns the cached ResourceManager instance used by this class.
29 /// </summary>
30 [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
31 internal static global::System.Resources.ResourceManager ResourceManager {
32 get {
33 if (object.ReferenceEquals(resourceMan, null)) {
34 global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("WixToolset.WixStrings", typeof(WixStrings).Assembly);
35 resourceMan = temp;
36 }
37 return resourceMan;
38 }
39 }
40
41 /// <summary>
42 /// Overrides the current thread's CurrentUICulture property for all
43 /// resource lookups using this strongly typed resource class.
44 /// </summary>
45 [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
46 internal static global::System.Globalization.CultureInfo Culture {
47 get {
48 return resourceCulture;
49 }
50 set {
51 resourceCulture = value;
52 }
53 }
54
55 /// <summary>
56 /// Looks up a localized string similar to Cannot index into a FileRowCollection that allows duplicate FileIds.
57 /// </summary>
58 internal static string EXP_CannotIndexIntoFileRowCollection {
59 get {
60 return ResourceManager.GetString("EXP_CannotIndexIntoFileRowCollection", resourceCulture);
61 }
62 }
63
64 /// <summary>
65 /// Looks up a localized string similar to The value &apos;{0}&apos; is not a legal identifier and therefore cannot be modularized..
66 /// </summary>
67 internal static string EXP_CannotModularizeIllegalID {
68 get {
69 return ResourceManager.GetString("EXP_CannotModularizeIllegalID", resourceCulture);
70 }
71 }
72
73 /// <summary>
74 /// Looks up a localized string similar to Cannot set column &apos;{0}&apos; with value {1} because it is greater than the maximum allowed value for this column, {2}..
75 /// </summary>
76 internal static string EXP_CannotSetColumnWithValueGreaterThanMaxValue {
77 get {
78 return ResourceManager.GetString("EXP_CannotSetColumnWithValueGreaterThanMaxValue", resourceCulture);
79 }
80 }
81
82 /// <summary>
83 /// Looks up a localized string similar to Cannot set column &apos;{0}&apos; with value {1} because it is less than the minimum allowed value for this column, {2}..
84 /// </summary>
85 internal static string EXP_CannotSetColumnWithValueLessThanMinValue {
86 get {
87 return ResourceManager.GetString("EXP_CannotSetColumnWithValueLessThanMinValue", resourceCulture);
88 }
89 }
90
91 /// <summary>
92 /// Looks up a localized string similar to A Merge table FileCompression column cannot be set to the invalid value &apos;{0}&apos;..
93 /// </summary>
94 internal static string EXP_CannotSetMergeTableFileCompressionColumnToInvalidValue {
95 get {
96 return ResourceManager.GetString("EXP_CannotSetMergeTableFileCompressionColumnToInvalidValue", resourceCulture);
97 }
98 }
99
100 /// <summary>
101 /// Looks up a localized string similar to Cannot set column &apos;{0}&apos; with a null value because this is a required field..
102 /// </summary>
103 internal static string EXP_CannotSetNullOnRequiredField {
104 get {
105 return ResourceManager.GetString("EXP_CannotSetNullOnRequiredField", resourceCulture);
106 }
107 }
108
109 /// <summary>
110 /// Looks up a localized string similar to Cannot set number column &apos;{0}&apos; with a value of type &apos;{1}&apos;..
111 /// </summary>
112 internal static string EXP_CannotSetNumberColumnWithValueOfType {
113 get {
114 return ResourceManager.GetString("EXP_CannotSetNumberColumnWithValueOfType", resourceCulture);
115 }
116 }
117
118 /// <summary>
119 /// Looks up a localized string similar to Cannot set string column &apos;{0}&apos; with a value of type &apos;{1}&apos;..
120 /// </summary>
121 internal static string EXP_CannotSetStringColumnWithValueOfType {
122 get {
123 return ResourceManager.GetString("EXP_CannotSetStringColumnWithValueOfType", resourceCulture);
124 }
125 }
126
127 /// <summary>
128 /// Looks up a localized string similar to Could not determine ProductCode from transform summary information.
129 /// </summary>
130 internal static string EXP_CouldnotDetermineProductCodeFromTransformSummaryInfo {
131 get {
132 return ResourceManager.GetString("EXP_CouldnotDetermineProductCodeFromTransformSummaryInfo", resourceCulture);
133 }
134 }
135
136 /// <summary>
137 /// Looks up a localized string similar to Could not find a unique identifier for the given resource name..
138 /// </summary>
139 internal static string EXP_CouldnotFileUniqueIDForResourceName {
140 get {
141 return ResourceManager.GetString("EXP_CouldnotFileUniqueIDForResourceName", resourceCulture);
142 }
143 }
144
145 /// <summary>
146 /// Looks up a localized string similar to Didn&apos;t find duplicated symbol..
147 /// </summary>
148 internal static string EXP_DidnotFindDuplicateSymbol {
149 get {
150 return ResourceManager.GetString("EXP_DidnotFindDuplicateSymbol", resourceCulture);
151 }
152 }
153
154 /// <summary>
155 /// Looks up a localized string similar to Expected ComplexReference type..
156 /// </summary>
157 internal static string EXP_ExpectedComplexReferenceType {
158 get {
159 return ResourceManager.GetString("EXP_ExpectedComplexReferenceType", resourceCulture);
160 }
161 }
162
163 /// <summary>
164 /// Looks up a localized string similar to Found an ActionRow with a non-existent {0} action: {1}..
165 /// </summary>
166 internal static string EXP_FoundActionRowWinNonExistentAction {
167 get {
168 return ResourceManager.GetString("EXP_FoundActionRowWinNonExistentAction", resourceCulture);
169 }
170 }
171
172 /// <summary>
173 /// Looks up a localized string similar to Found an ActionRow with no Sequence, Before, or After column set..
174 /// </summary>
175 internal static string EXP_FoundActionRowWithNoSequenceBeforeOrAfterColumnSet {
176 get {
177 return ResourceManager.GetString("EXP_FoundActionRowWithNoSequenceBeforeOrAfterColumnSet", resourceCulture);
178 }
179 }
180
181 /// <summary>
182 /// Looks up a localized string similar to Illegal arguments passed..
183 /// </summary>
184 internal static string EXP_IllegalArgumentsPassed {
185 get {
186 return ResourceManager.GetString("EXP_IllegalArgumentsPassed", resourceCulture);
187 }
188 }
189
190 /// <summary>
191 /// Looks up a localized string similar to Invalid table name passed into GenerateIdentifier..
192 /// </summary>
193 internal static string EXP_InvalidTableNamePassed {
194 get {
195 return ResourceManager.GetString("EXP_InvalidTableNamePassed", resourceCulture);
196 }
197 }
198
199 /// <summary>
200 /// Looks up a localized string similar to A Merge table FileCompression column contains an invalid value &apos;{0}&apos;..
201 /// </summary>
202 internal static string EXP_MergeTableFileCompressionColumnContainsInvalidValue {
203 get {
204 return ResourceManager.GetString("EXP_MergeTableFileCompressionColumnContainsInvalidValue", resourceCulture);
205 }
206 }
207
208 /// <summary>
209 /// Looks up a localized string similar to Multiple harvester extensions specified..
210 /// </summary>
211 internal static string EXP_MultipleHarvesterExtensionsSpecified {
212 get {
213 return ResourceManager.GetString("EXP_MultipleHarvesterExtensionsSpecified", resourceCulture);
214 }
215 }
216
217 /// <summary>
218 /// Looks up a localized string similar to The other object is not a FileRow..
219 /// </summary>
220 internal static string EXP_OtherObjectIsNotFileRow {
221 get {
222 return ResourceManager.GetString("EXP_OtherObjectIsNotFileRow", resourceCulture);
223 }
224 }
225
226 /// <summary>
227 /// Looks up a localized string similar to Transform authored into multiple Media &apos;{0}&apos; and &apos;{1}&apos;..
228 /// </summary>
229 internal static string EXP_TransformAuthoredIntoMultipleMedia {
230 get {
231 return ResourceManager.GetString("EXP_TransformAuthoredIntoMultipleMedia", resourceCulture);
232 }
233 }
234
235 /// <summary>
236 /// Looks up a localized string similar to Unexpected complex reference child type: {0}.
237 /// </summary>
238 internal static string EXP_UnexpectedComplexReferenceChildType {
239 get {
240 return ResourceManager.GetString("EXP_UnexpectedComplexReferenceChildType", resourceCulture);
241 }
242 }
243
244 /// <summary>
245 /// Looks up a localized string similar to Unexpected entry section type: {0}.
246 /// </summary>
247 internal static string EXP_UnexpectedEntrySectionType {
248 get {
249 return ResourceManager.GetString("EXP_UnexpectedEntrySectionType", resourceCulture);
250 }
251 }
252
253 /// <summary>
254 /// Looks up a localized string similar to Encountered an unexpected error while merging &apos;{0}&apos;. More information about the merge and the failure can be found in the merge log: &apos;{1}&apos;.
255 /// </summary>
256 internal static string EXP_UnexpectedMergerErrorInSourceFile {
257 get {
258 return ResourceManager.GetString("EXP_UnexpectedMergerErrorInSourceFile", resourceCulture);
259 }
260 }
261
262 /// <summary>
263 /// Looks up a localized string similar to Encountered an unexpected merge error of type &apos;{0}&apos; 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: &apos;{1}&apos;.
264 /// </summary>
265 internal static string EXP_UnexpectedMergerErrorWithType {
266 get {
267 return ResourceManager.GetString("EXP_UnexpectedMergerErrorWithType", resourceCulture);
268 }
269 }
270
271 /// <summary>
272 /// Looks up a localized string similar to Unknown control attribute: &apos;{0}&apos;..
273 /// </summary>
274 internal static string EXP_UnknowControlAttribute {
275 get {
276 return ResourceManager.GetString("EXP_UnknowControlAttribute", resourceCulture);
277 }
278 }
279
280 /// <summary>
281 /// Looks up a localized string similar to Unknown column type: {0}.
282 /// </summary>
283 internal static string EXP_UnknownColumnType {
284 get {
285 return ResourceManager.GetString("EXP_UnknownColumnType", resourceCulture);
286 }
287 }
288
289 /// <summary>
290 /// Looks up a localized string similar to Unknown compression level type: {0}.
291 /// </summary>
292 internal static string EXP_UnknownCompressionLevelType {
293 get {
294 return ResourceManager.GetString("EXP_UnknownCompressionLevelType", resourceCulture);
295 }
296 }
297
298 /// <summary>
299 /// Looks up a localized string similar to Unknown custom column category &apos;{0}&apos;..
300 /// </summary>
301 internal static string EXP_UnknownCustomColumnCategory {
302 get {
303 return ResourceManager.GetString("EXP_UnknownCustomColumnCategory", resourceCulture);
304 }
305 }
306
307 /// <summary>
308 /// Looks up a localized string similar to Unknown custom column modularization type &apos;{0}&apos;..
309 /// </summary>
310 internal static string EXP_UnknownCustomColumnModularizationType {
311 get {
312 return ResourceManager.GetString("EXP_UnknownCustomColumnModularizationType", resourceCulture);
313 }
314 }
315
316 /// <summary>
317 /// Looks up a localized string similar to Unknown custom column type &apos;{0}&apos;..
318 /// </summary>
319 internal static string EXP_UnknownCustomColumnType {
320 get {
321 return ResourceManager.GetString("EXP_UnknownCustomColumnType", resourceCulture);
322 }
323 }
324
325 /// <summary>
326 /// Looks up a localized string similar to Unknown output type..
327 /// </summary>
328 internal static string EXP_UnknownOutputType {
329 get {
330 return ResourceManager.GetString("EXP_UnknownOutputType", resourceCulture);
331 }
332 }
333
334 /// <summary>
335 /// Looks up a localized string similar to Unknown permission attribute &apos;{0}&apos;..
336 /// </summary>
337 internal static string EXP_UnknownPermissionAttribute {
338 get {
339 return ResourceManager.GetString("EXP_UnknownPermissionAttribute", resourceCulture);
340 }
341 }
342
343 /// <summary>
344 /// Looks up a localized string similar to Unknown platform enumeration &apos;{0}&apos; encountered..
345 /// </summary>
346 internal static string EXP_UnknownPlatformEnum {
347 get {
348 return ResourceManager.GetString("EXP_UnknownPlatformEnum", resourceCulture);
349 }
350 }
351
352 /// <summary>
353 /// Looks up a localized string similar to Unknown sequence table..
354 /// </summary>
355 internal static string EXP_UnknowSequenceTable {
356 get {
357 return ResourceManager.GetString("EXP_UnknowSequenceTable", resourceCulture);
358 }
359 }
360
361 /// <summary>
362 /// Looks up a localized string similar to The table {0} is not supported..
363 /// </summary>
364 internal static string EXP_UnsupportedTable {
365 get {
366 return ResourceManager.GetString("EXP_UnsupportedTable", resourceCulture);
367 }
368 }
369
370 /// <summary>
371 /// Looks up a localized string similar to {0}({1}).
372 /// </summary>
373 internal static string Format_FirstLineNumber {
374 get {
375 return ResourceManager.GetString("Format_FirstLineNumber", resourceCulture);
376 }
377 }
378
379 /// <summary>
380 /// Looks up a localized string similar to {0}.
381 /// </summary>
382 internal static string Format_InfoMessage {
383 get {
384 return ResourceManager.GetString("Format_InfoMessage", resourceCulture);
385 }
386 }
387
388 /// <summary>
389 /// Looks up a localized string similar to {0}: line {1}.
390 /// </summary>
391 internal static string Format_LineNumber {
392 get {
393 return ResourceManager.GetString("Format_LineNumber", resourceCulture);
394 }
395 }
396
397 /// <summary>
398 /// Looks up a localized string similar to {0} : {1} {2}{3:0000} : {4}.
399 /// </summary>
400 internal static string Format_NonInfoMessage {
401 get {
402 return ResourceManager.GetString("Format_NonInfoMessage", resourceCulture);
403 }
404 }
405
406 /// <summary>
407 /// Looks up a localized string similar to Source trace:{0}.
408 /// </summary>
409 internal static string INF_SourceTrace {
410 get {
411 return ResourceManager.GetString("INF_SourceTrace", resourceCulture);
412 }
413 }
414
415 /// <summary>
416 /// Looks up a localized string similar to at {0}{1}.
417 /// </summary>
418 internal static string INF_SourceTraceLocation {
419 get {
420 return ResourceManager.GetString("INF_SourceTraceLocation", resourceCulture);
421 }
422 }
423
424 /// <summary>
425 /// Looks up a localized string similar to error.
426 /// </summary>
427 internal static string MessageType_Error {
428 get {
429 return ResourceManager.GetString("MessageType_Error", resourceCulture);
430 }
431 }
432
433 /// <summary>
434 /// Looks up a localized string similar to warning.
435 /// </summary>
436 internal static string MessageType_Warning {
437 get {
438 return ResourceManager.GetString("MessageType_Warning", resourceCulture);
439 }
440 }
441 }
442}
diff --git a/src/WixToolset.Core/WixStrings.resx b/src/WixToolset.Core/WixStrings.resx
new file mode 100644
index 00000000..3fbf639e
--- /dev/null
+++ b/src/WixToolset.Core/WixStrings.resx
@@ -0,0 +1,249 @@
1<?xml version="1.0" encoding="utf-8"?>
2<root>
3 <!--
4 Microsoft ResX Schema
5
6 Version 2.0
7
8 The primary goals of this format is to allow a simple XML format
9 that is mostly human readable. The generation and parsing of the
10 various data types are done through the TypeConverter classes
11 associated with the data types.
12
13 Example:
14
15 ... ado.net/XML headers & schema ...
16 <resheader name="resmimetype">text/microsoft-resx</resheader>
17 <resheader name="version">2.0</resheader>
18 <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
19 <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
20 <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
21 <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
22 <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
23 <value>[base64 mime encoded serialized .NET Framework object]</value>
24 </data>
25 <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
26 <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
27 <comment>This is a comment</comment>
28 </data>
29
30 There are any number of "resheader" rows that contain simple
31 name/value pairs.
32
33 Each data row contains a name, and value. The row also contains a
34 type or mimetype. Type corresponds to a .NET class that support
35 text/value conversion through the TypeConverter architecture.
36 Classes that don't support this are serialized and stored with the
37 mimetype set.
38
39 The mimetype is used for serialized objects, and tells the
40 ResXResourceReader how to depersist the object. This is currently not
41 extensible. For a given mimetype the value must be set accordingly:
42
43 Note - application/x-microsoft.net.object.binary.base64 is the format
44 that the ResXResourceWriter will generate, however the reader can
45 read any of the formats listed below.
46
47 mimetype: application/x-microsoft.net.object.binary.base64
48 value : The object must be serialized with
49 : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
50 : and then encoded with base64 encoding.
51
52 mimetype: application/x-microsoft.net.object.soap.base64
53 value : The object must be serialized with
54 : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
55 : and then encoded with base64 encoding.
56
57 mimetype: application/x-microsoft.net.object.bytearray.base64
58 value : The object must be serialized into a byte array
59 : using a System.ComponentModel.TypeConverter
60 : and then encoded with base64 encoding.
61 -->
62 <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
63 <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
64 <xsd:element name="root" msdata:IsDataSet="true">
65 <xsd:complexType>
66 <xsd:choice maxOccurs="unbounded">
67 <xsd:element name="metadata">
68 <xsd:complexType>
69 <xsd:sequence>
70 <xsd:element name="value" type="xsd:string" minOccurs="0" />
71 </xsd:sequence>
72 <xsd:attribute name="name" use="required" type="xsd:string" />
73 <xsd:attribute name="type" type="xsd:string" />
74 <xsd:attribute name="mimetype" type="xsd:string" />
75 <xsd:attribute ref="xml:space" />
76 </xsd:complexType>
77 </xsd:element>
78 <xsd:element name="assembly">
79 <xsd:complexType>
80 <xsd:attribute name="alias" type="xsd:string" />
81 <xsd:attribute name="name" type="xsd:string" />
82 </xsd:complexType>
83 </xsd:element>
84 <xsd:element name="data">
85 <xsd:complexType>
86 <xsd:sequence>
87 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
88 <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
89 </xsd:sequence>
90 <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
91 <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
92 <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
93 <xsd:attribute ref="xml:space" />
94 </xsd:complexType>
95 </xsd:element>
96 <xsd:element name="resheader">
97 <xsd:complexType>
98 <xsd:sequence>
99 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
100 </xsd:sequence>
101 <xsd:attribute name="name" type="xsd:string" use="required" />
102 </xsd:complexType>
103 </xsd:element>
104 </xsd:choice>
105 </xsd:complexType>
106 </xsd:element>
107 </xsd:schema>
108 <resheader name="resmimetype">
109 <value>text/microsoft-resx</value>
110 </resheader>
111 <resheader name="version">
112 <value>2.0</value>
113 </resheader>
114 <resheader name="reader">
115 <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
116 </resheader>
117 <resheader name="writer">
118 <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
119 </resheader>
120 <data name="EXP_CannotIndexIntoFileRowCollection" xml:space="preserve">
121 <value>Cannot index into a FileRowCollection that allows duplicate FileIds</value>
122 </data>
123 <data name="EXP_CannotModularizeIllegalID" xml:space="preserve">
124 <value>The value '{0}' is not a legal identifier and therefore cannot be modularized.</value>
125 </data>
126 <data name="EXP_CannotSetColumnWithValueGreaterThanMaxValue" xml:space="preserve">
127 <value>Cannot set column '{0}' with value {1} because it is greater than the maximum allowed value for this column, {2}.</value>
128 </data>
129 <data name="EXP_CannotSetColumnWithValueLessThanMinValue" xml:space="preserve">
130 <value>Cannot set column '{0}' with value {1} because it is less than the minimum allowed value for this column, {2}.</value>
131 </data>
132 <data name="EXP_CannotSetMergeTableFileCompressionColumnToInvalidValue" xml:space="preserve">
133 <value>A Merge table FileCompression column cannot be set to the invalid value '{0}'.</value>
134 </data>
135 <data name="EXP_CannotSetNullOnRequiredField" xml:space="preserve">
136 <value>Cannot set column '{0}' with a null value because this is a required field.</value>
137 </data>
138 <data name="EXP_CannotSetNumberColumnWithValueOfType" xml:space="preserve">
139 <value>Cannot set number column '{0}' with a value of type '{1}'.</value>
140 </data>
141 <data name="EXP_CannotSetStringColumnWithValueOfType" xml:space="preserve">
142 <value>Cannot set string column '{0}' with a value of type '{1}'.</value>
143 </data>
144 <data name="EXP_CouldnotDetermineProductCodeFromTransformSummaryInfo" xml:space="preserve">
145 <value>Could not determine ProductCode from transform summary information</value>
146 </data>
147 <data name="EXP_CouldnotFileUniqueIDForResourceName" xml:space="preserve">
148 <value>Could not find a unique identifier for the given resource name.</value>
149 </data>
150 <data name="EXP_DidnotFindDuplicateSymbol" xml:space="preserve">
151 <value>Didn't find duplicated symbol.</value>
152 </data>
153 <data name="EXP_ExpectedComplexReferenceType" xml:space="preserve">
154 <value>Expected ComplexReference type.</value>
155 </data>
156 <data name="EXP_FoundActionRowWinNonExistentAction" xml:space="preserve">
157 <value>Found an ActionRow with a non-existent {0} action: {1}.</value>
158 </data>
159 <data name="EXP_FoundActionRowWithNoSequenceBeforeOrAfterColumnSet" xml:space="preserve">
160 <value>Found an ActionRow with no Sequence, Before, or After column set.</value>
161 </data>
162 <data name="EXP_IllegalArgumentsPassed" xml:space="preserve">
163 <value>Illegal arguments passed.</value>
164 </data>
165 <data name="EXP_InvalidTableNamePassed" xml:space="preserve">
166 <value>Invalid table name passed into GenerateIdentifier.</value>
167 </data>
168 <data name="EXP_MergeTableFileCompressionColumnContainsInvalidValue" xml:space="preserve">
169 <value>A Merge table FileCompression column contains an invalid value '{0}'.</value>
170 </data>
171 <data name="EXP_MultipleHarvesterExtensionsSpecified" xml:space="preserve">
172 <value>Multiple harvester extensions specified.</value>
173 </data>
174 <data name="EXP_OtherObjectIsNotFileRow" xml:space="preserve">
175 <value>The other object is not a FileRow.</value>
176 </data>
177 <data name="EXP_TransformAuthoredIntoMultipleMedia" xml:space="preserve">
178 <value>Transform authored into multiple Media '{0}' and '{1}'.</value>
179 </data>
180 <data name="EXP_UnexpectedComplexReferenceChildType" xml:space="preserve">
181 <value>Unexpected complex reference child type: {0}</value>
182 </data>
183 <data name="EXP_UnexpectedEntrySectionType" xml:space="preserve">
184 <value>Unexpected entry section type: {0}</value>
185 </data>
186 <data name="EXP_UnexpectedMergerErrorInSourceFile" xml:space="preserve">
187 <value>Encountered an unexpected error while merging '{0}'. More information about the merge and the failure can be found in the merge log: '{1}'</value>
188 </data>
189 <data name="EXP_UnexpectedMergerErrorWithType" xml:space="preserve">
190 <value>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}'</value>
191 </data>
192 <data name="EXP_UnknowControlAttribute" xml:space="preserve">
193 <value>Unknown control attribute: '{0}'.</value>
194 </data>
195 <data name="EXP_UnknownColumnType" xml:space="preserve">
196 <value>Unknown column type: {0}</value>
197 </data>
198 <data name="EXP_UnknownCompressionLevelType" xml:space="preserve">
199 <value>Unknown compression level type: {0}</value>
200 </data>
201 <data name="EXP_UnknownCustomColumnCategory" xml:space="preserve">
202 <value>Unknown custom column category '{0}'.</value>
203 </data>
204 <data name="EXP_UnknownCustomColumnModularizationType" xml:space="preserve">
205 <value>Unknown custom column modularization type '{0}'.</value>
206 </data>
207 <data name="EXP_UnknownCustomColumnType" xml:space="preserve">
208 <value>Unknown custom column type '{0}'.</value>
209 </data>
210 <data name="EXP_UnknownOutputType" xml:space="preserve">
211 <value>Unknown output type.</value>
212 </data>
213 <data name="EXP_UnknownPermissionAttribute" xml:space="preserve">
214 <value>Unknown permission attribute '{0}'.</value>
215 </data>
216 <data name="EXP_UnknowSequenceTable" xml:space="preserve">
217 <value>Unknown sequence table.</value>
218 </data>
219 <data name="EXP_UnknownPlatformEnum" xml:space="preserve">
220 <value>Unknown platform enumeration '{0}' encountered.</value>
221 </data>
222 <data name="EXP_UnsupportedTable" xml:space="preserve">
223 <value>The table {0} is not supported.</value>
224 </data>
225 <data name="Format_FirstLineNumber" xml:space="preserve">
226 <value>{0}({1})</value>
227 </data>
228 <data name="Format_InfoMessage" xml:space="preserve">
229 <value>{0}</value>
230 </data>
231 <data name="Format_LineNumber" xml:space="preserve">
232 <value>{0}: line {1}</value>
233 </data>
234 <data name="Format_NonInfoMessage" xml:space="preserve">
235 <value>{0} : {1} {2}{3:0000} : {4}</value>
236 </data>
237 <data name="INF_SourceTrace" xml:space="preserve">
238 <value>Source trace:{0}</value>
239 </data>
240 <data name="INF_SourceTraceLocation" xml:space="preserve">
241 <value>at {0}{1}</value>
242 </data>
243 <data name="MessageType_Error" xml:space="preserve">
244 <value>error</value>
245 </data>
246 <data name="MessageType_Warning" xml:space="preserve">
247 <value>warning</value>
248 </data>
249</root> \ No newline at end of file
diff --git a/src/WixToolset.Core/WixToolset.Core.csproj b/src/WixToolset.Core/WixToolset.Core.csproj
new file mode 100644
index 00000000..3db31e4d
--- /dev/null
+++ b/src/WixToolset.Core/WixToolset.Core.csproj
@@ -0,0 +1,46 @@
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>netstandard2.0</TargetFramework>
7 <Description></Description>
8 <Title>WiX Toolset Core</Title>
9 </PropertyGroup>
10
11 <PropertyGroup>
12 <NoWarn>NU1701</NoWarn>
13 </PropertyGroup>
14
15 <ItemGroup>
16 <MsgGenSource Include="Data\messages.xml">
17 <ResourcesLogicalName>WixToolset.Core.Data.messages.resources</ResourcesLogicalName>
18 </MsgGenSource>
19 </ItemGroup>
20
21 <ItemGroup>
22 <ProjectReference Include="$(WixToolsetRootFolder)\Data\src\WixToolset.Data\WixToolset.Data.csproj" Condition=" '$(Configuration)' == 'Debug' And Exists('$(WixToolsetRootFolder)\Data\src\WixToolset.Data\WixToolset.Data.csproj') " />
23 <PackageReference Include="WixToolset.Data" Version="4.0.*" Condition=" '$(Configuration)' == 'Release' Or !Exists('$(WixToolsetRootFolder)\Data\src\WixToolset.Data\WixToolset.Data.csproj') " />
24
25 <ProjectReference Include="$(WixToolsetRootFolder)\Extensibility\src\WixToolset.Extensibility\WixToolset.Extensibility.csproj" Condition=" '$(Configuration)' == 'Debug' And Exists('$(WixToolsetRootFolder)\Extensibility\src\WixToolset.Extensibility\WixToolset.Extensibility.csproj') " />
26 <PackageReference Include="WixToolset.Extensibility" Version="4.0.*" Condition=" '$(Configuration)' == 'Release' Or !Exists('$(WixToolsetRootFolder)\Extensibility\src\WixToolset.Extensibility\WixToolset.Extensibility.csproj') " />
27
28 <ProjectReference Include="$(WixToolsetRootFolder)\Core.Native\src\WixToolset.Core.Native\WixToolset.Core.Native.csproj" Condition=" '$(Configuration)' == 'Debug' And Exists('$(WixToolsetRootFolder)\Core.Native\src\WixToolset.Core.Native\WixToolset.Core.Native.csproj') " />
29 <PackageReference Include="WixToolset.Core.Native" Version="4.0.*" Condition=" '$(Configuration)' == 'Release' Or !Exists('$(WixToolsetRootFolder)\Core.Native\src\WixToolset.Core.Native\WixToolset.Core.Native.csproj') " />
30 </ItemGroup>
31
32 <ItemGroup>
33 <PackageReference Include="WixToolset.Dtf.Resources" Version="4.0.*" />
34 <PackageReference Include="WixToolset.Dtf.WindowsInstaller" Version="4.0.*" />
35 </ItemGroup>
36
37 <ItemGroup>
38 <PackageReference Include="System.IO.FileSystem.AccessControl" Version="4.4.0" />
39 </ItemGroup>
40
41 <ItemGroup>
42 <PackageReference Include="Nerdbank.GitVersioning" Version="2.0.41" PrivateAssets="all" />
43 <PackageReference Include="WixBuildTools.MsgGen" Version="4.0.*" PrivateAssets="all" />
44 <PackageReference Include="WixBuildTools.XsdGen" Version="4.0.*" PrivateAssets="all" />
45 </ItemGroup>
46</Project>
diff --git a/src/WixToolset.Core/WixVariableResolver.cs b/src/WixToolset.Core/WixVariableResolver.cs
new file mode 100644
index 00000000..921ff1e3
--- /dev/null
+++ b/src/WixToolset.Core/WixVariableResolver.cs
@@ -0,0 +1,337 @@
1// Copyright (c) .NET Foundation and 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.Collections;
7 using System.Collections.Generic;
8 using System.Diagnostics.CodeAnalysis;
9 using System.Globalization;
10 using System.Text;
11 using System.Text.RegularExpressions;
12 using WixToolset.Data;
13 using WixToolset.Data.Rows;
14
15 /// <summary>
16 /// WiX variable resolver.
17 /// </summary>
18 public sealed class WixVariableResolver
19 {
20 private Localizer localizer;
21 private Dictionary<string, string> wixVariables;
22
23 /// <summary>
24 /// Instantiate a new WixVariableResolver.
25 /// </summary>
26 public WixVariableResolver()
27 {
28 this.wixVariables = new Dictionary<string, string>();
29 }
30
31 /// <summary>
32 /// Gets or sets the localizer.
33 /// </summary>
34 /// <value>The localizer.</value>
35 public Localizer Localizer
36 {
37 get { return this.localizer; }
38 set { this.localizer = value; }
39 }
40
41 /// <summary>
42 /// Gets the count of variables added to the resolver.
43 /// </summary>
44 public int VariableCount
45 {
46 get { return this.wixVariables.Count; }
47 }
48
49 /// <summary>
50 /// Add a variable.
51 /// </summary>
52 /// <param name="name">The name of the variable.</param>
53 /// <param name="value">The value of the variable.</param>
54 public void AddVariable(string name, string value)
55 {
56 try
57 {
58 this.wixVariables.Add(name, value);
59 }
60 catch (ArgumentException)
61 {
62 Messaging.Instance.OnMessage(WixErrors.WixVariableCollision(null, name));
63 }
64 }
65
66 /// <summary>
67 /// Add a variable.
68 /// </summary>
69 /// <param name="wixVariableRow">The WixVariableRow to add.</param>
70 public void AddVariable(WixVariableRow wixVariableRow)
71 {
72 try
73 {
74 this.wixVariables.Add(wixVariableRow.Id, wixVariableRow.Value);
75 }
76 catch (ArgumentException)
77 {
78 if (!wixVariableRow.Overridable) // collision
79 {
80 Messaging.Instance.OnMessage(WixErrors.WixVariableCollision(wixVariableRow.SourceLineNumbers, wixVariableRow.Id));
81 }
82 }
83 }
84
85 /// <summary>
86 /// Resolve the wix variables in a value.
87 /// </summary>
88 /// <param name="sourceLineNumbers">The source line information for the value.</param>
89 /// <param name="value">The value to resolve.</param>
90 /// <param name="localizationOnly">true to only resolve localization variables; false otherwise.</param>
91 /// <returns>The resolved value.</returns>
92 public string ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool localizationOnly)
93 {
94 bool isDefault = false;
95 bool delayedResolve = false;
96
97 return this.ResolveVariables(sourceLineNumbers, value, localizationOnly, ref isDefault, ref delayedResolve);
98 }
99
100 /// <summary>
101 /// Resolve the wix variables in a value.
102 /// </summary>
103 /// <param name="sourceLineNumbers">The source line information for the value.</param>
104 /// <param name="value">The value to resolve.</param>
105 /// <param name="localizationOnly">true to only resolve localization variables; false otherwise.</param>
106 /// <param name="isDefault">true if the resolved value was the default.</param>
107 /// <returns>The resolved value.</returns>
108 public string ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool localizationOnly, ref bool isDefault)
109 {
110 bool delayedResolve = false;
111
112 return this.ResolveVariables(sourceLineNumbers, value, localizationOnly, ref isDefault, ref delayedResolve);
113 }
114
115 /// <summary>
116 /// Resolve the wix variables in a value.
117 /// </summary>
118 /// <param name="sourceLineNumbers">The source line information for the value.</param>
119 /// <param name="value">The value to resolve.</param>
120 /// <param name="localizationOnly">true to only resolve localization variables; false otherwise.</param>
121 /// <param name="errorOnUnknown">true if unknown variables should throw errors.</param>
122 /// <param name="isDefault">true if the resolved value was the default.</param>
123 /// <param name="delayedResolve">true if the value has variables that cannot yet be resolved.</param>
124 /// <returns>The resolved value.</returns>
125 public string ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool localizationOnly, ref bool isDefault, ref bool delayedResolve)
126 {
127 return this.ResolveVariables(sourceLineNumbers, value, localizationOnly, true, ref isDefault, ref delayedResolve);
128 }
129
130 /// <summary>
131 /// Resolve the wix variables in a value.
132 /// </summary>
133 /// <param name="sourceLineNumbers">The source line information for the value.</param>
134 /// <param name="value">The value to resolve.</param>
135 /// <param name="localizationOnly">true to only resolve localization variables; false otherwise.</param>
136 /// <param name="errorOnUnknown">true if unknown variables should throw errors.</param>
137 /// <param name="isDefault">true if the resolved value was the default.</param>
138 /// <param name="delayedResolve">true if the value has variables that cannot yet be resolved.</param>
139 /// <returns>The resolved value.</returns>
140 public string ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool localizationOnly, bool errorOnUnknown, ref bool isDefault, ref bool delayedResolve)
141 {
142 MatchCollection matches = Common.WixVariableRegex.Matches(value);
143
144 // the value is the default unless its substituted further down
145 isDefault = true;
146 delayedResolve = false;
147
148 if (0 < matches.Count)
149 {
150 StringBuilder sb = new StringBuilder(value);
151
152 // notice how this code walks backward through the list
153 // because it modifies the string as we through it
154 for (int i = matches.Count - 1; 0 <= i; i--)
155 {
156 string variableNamespace = matches[i].Groups["namespace"].Value;
157 string variableId = matches[i].Groups["fullname"].Value;
158 string variableDefaultValue = null;
159
160 // get the default value if one was specified
161 if (matches[i].Groups["value"].Success)
162 {
163 variableDefaultValue = matches[i].Groups["value"].Value;
164
165 // localization variables to not support inline default values
166 if ("loc" == variableNamespace)
167 {
168 Messaging.Instance.OnMessage(WixErrors.IllegalInlineLocVariable(sourceLineNumbers, variableId, variableDefaultValue));
169 }
170 }
171
172 // get the scope if one was specified
173 if (matches[i].Groups["scope"].Success)
174 {
175 if ("bind" == variableNamespace)
176 {
177 variableId = matches[i].Groups["name"].Value;
178 }
179 }
180
181 // check for an escape sequence of !! indicating the match is not a variable expression
182 if (0 < matches[i].Index && '!' == sb[matches[i].Index - 1])
183 {
184 if (!localizationOnly)
185 {
186 sb.Remove(matches[i].Index - 1, 1);
187 }
188 }
189 else
190 {
191 string resolvedValue = null;
192
193 if ("loc" == variableNamespace)
194 {
195 // warn about deprecated syntax of $(loc.var)
196 if ('$' == sb[matches[i].Index])
197 {
198 Messaging.Instance.OnMessage(WixWarnings.DeprecatedLocalizationVariablePrefix(sourceLineNumbers, variableId));
199 }
200
201 if (null != this.localizer)
202 {
203 resolvedValue = this.localizer.GetLocalizedValue(variableId);
204 }
205 }
206 else if (!localizationOnly && "wix" == variableNamespace)
207 {
208 // illegal syntax of $(wix.var)
209 if ('$' == sb[matches[i].Index])
210 {
211 Messaging.Instance.OnMessage(WixErrors.IllegalWixVariablePrefix(sourceLineNumbers, variableId));
212 }
213 else
214 {
215 if (this.wixVariables.TryGetValue(variableId, out resolvedValue))
216 {
217 resolvedValue = resolvedValue ?? String.Empty;
218 isDefault = false;
219 }
220 else if (null != variableDefaultValue) // default the resolved value to the inline value if one was specified
221 {
222 resolvedValue = variableDefaultValue;
223 }
224 }
225 }
226
227 if ("bind" == variableNamespace)
228 {
229 // can't resolve these yet, but keep track of where we find them so they can be resolved later with less effort
230 delayedResolve = true;
231 }
232 else
233 {
234 // insert the resolved value if it was found or display an error
235 if (null != resolvedValue)
236 {
237 sb.Remove(matches[i].Index, matches[i].Length);
238 sb.Insert(matches[i].Index, resolvedValue);
239 }
240 else if ("loc" == variableNamespace && errorOnUnknown) // unresolved loc variable
241 {
242 Messaging.Instance.OnMessage(WixErrors.LocalizationVariableUnknown(sourceLineNumbers, variableId));
243 }
244 else if (!localizationOnly && "wix" == variableNamespace && errorOnUnknown) // unresolved wix variable
245 {
246 Messaging.Instance.OnMessage(WixErrors.WixVariableUnknown(sourceLineNumbers, variableId));
247 }
248 }
249 }
250 }
251
252 value = sb.ToString();
253 }
254
255 return value;
256 }
257
258 /// <summary>
259 /// Resolve the delay variables in a value.
260 /// </summary>
261 /// <param name="sourceLineNumbers">The source line information for the value.</param>
262 /// <param name="value">The value to resolve.</param>
263 /// <param name="resolutionData"></param>
264 /// <returns>The resolved value.</returns>
265 [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "sourceLineNumbers")]
266 [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "This string is not round tripped, and not used for any security decisions")]
267 public static string ResolveDelayedVariables(SourceLineNumber sourceLineNumbers, string value, IDictionary<string, string> resolutionData)
268 {
269 MatchCollection matches = Common.WixVariableRegex.Matches(value);
270
271 if (0 < matches.Count)
272 {
273 StringBuilder sb = new StringBuilder(value);
274
275 // notice how this code walks backward through the list
276 // because it modifies the string as we go through it
277 for (int i = matches.Count - 1; 0 <= i; i--)
278 {
279 string variableNamespace = matches[i].Groups["namespace"].Value;
280 string variableId = matches[i].Groups["fullname"].Value;
281 string variableDefaultValue = null;
282 string variableScope = null;
283
284 // get the default value if one was specified
285 if (matches[i].Groups["value"].Success)
286 {
287 variableDefaultValue = matches[i].Groups["value"].Value;
288 }
289
290 // get the scope if one was specified
291 if (matches[i].Groups["scope"].Success)
292 {
293 variableScope = matches[i].Groups["scope"].Value;
294 if ("bind" == variableNamespace)
295 {
296 variableId = matches[i].Groups["name"].Value;
297 }
298 }
299
300 // check for an escape sequence of !! indicating the match is not a variable expression
301 if (0 < matches[i].Index && '!' == sb[matches[i].Index - 1])
302 {
303 sb.Remove(matches[i].Index - 1, 1);
304 }
305 else
306 {
307 string key = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", variableId, variableScope).ToLower(CultureInfo.InvariantCulture);
308 string resolvedValue = variableDefaultValue;
309
310 if (resolutionData.ContainsKey(key))
311 {
312 resolvedValue = resolutionData[key];
313 }
314
315 if ("bind" == variableNamespace)
316 {
317 // insert the resolved value if it was found or display an error
318 if (null != resolvedValue)
319 {
320 sb.Remove(matches[i].Index, matches[i].Length);
321 sb.Insert(matches[i].Index, resolvedValue);
322 }
323 else
324 {
325 throw new WixException(WixErrors.UnresolvedBindReference(sourceLineNumbers, value));
326 }
327 }
328 }
329 }
330
331 value = sb.ToString();
332 }
333
334 return value;
335 }
336 }
337}
diff --git a/src/candle/App.ico b/src/candle/App.ico
new file mode 100644
index 00000000..3a5525fd
--- /dev/null
+++ b/src/candle/App.ico
Binary files differ
diff --git a/src/candle/AssemblyInfo.cs b/src/candle/AssemblyInfo.cs
new file mode 100644
index 00000000..7df55940
--- /dev/null
+++ b/src/candle/AssemblyInfo.cs
@@ -0,0 +1,11 @@
1// Copyright (c) .NET Foundation 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.CompilerServices;
6using System.Runtime.InteropServices;
7
8[assembly: AssemblyTitle("WiX Toolset Compiler")]
9[assembly: AssemblyDescription("Compiler")]
10[assembly: AssemblyCulture("")]
11[assembly: ComVisible(false)]
diff --git a/src/candle/CandleCommandLine.cs b/src/candle/CandleCommandLine.cs
new file mode 100644
index 00000000..81fdf7b2
--- /dev/null
+++ b/src/candle/CandleCommandLine.cs
@@ -0,0 +1,343 @@
1// Copyright (c) .NET Foundation and 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.Tools
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.IO;
9 using WixToolset.Data;
10
11 /// <summary>
12 /// Parse command line for candle.
13 /// </summary>
14 public class CandleCommandLine
15 {
16 public CandleCommandLine()
17 {
18 this.Platform = Platform.X86;
19
20 this.ShowLogo = true;
21 this.Extensions = new List<string>();
22 this.Files = new List<CompileFile>();
23 this.IncludeSearchPaths = new List<string>();
24 this.PreprocessorVariables = new Dictionary<string, string>();
25 }
26
27 public Platform Platform { get; private set; }
28
29 public bool ShowLogo { get; private set; }
30
31 public bool ShowHelp { get; private set; }
32
33 public bool ShowPedanticMessages { get; private set; }
34
35 public string OutputFolder { get; private set; }
36
37 public string OutputFile { get; private set; }
38
39 public List<string> Extensions { get; private set; }
40
41 public List<CompileFile> Files { get; private set; }
42
43 public List<string> IncludeSearchPaths { get; private set; }
44
45 public string PreprocessFile { get; private set; }
46
47 public Dictionary<string, string> PreprocessorVariables { get; private set; }
48
49 /// <summary>
50 /// Parse the commandline arguments.
51 /// </summary>
52 /// <param name="args">Commandline arguments.</param>
53 public string[] Parse(string[] args)
54 {
55 List<string> unprocessed = new List<string>();
56
57 for (int i = 0; i < args.Length; ++i)
58 {
59 string arg = args[i];
60 if (String.IsNullOrEmpty(arg)) // skip blank arguments
61 {
62 continue;
63 }
64
65 if (1 == arg.Length) // treat '-' and '@' as filenames when by themselves.
66 {
67 unprocessed.Add(arg);
68 }
69 else if ('-' == arg[0] || '/' == arg[0])
70 {
71 string parameter = arg.Substring(1);
72 if ('d' == parameter[0])
73 {
74 if (1 >= parameter.Length || '=' == parameter[1])
75 {
76 Messaging.Instance.OnMessage(WixErrors.InvalidVariableDefinition(arg));
77 break;
78 }
79
80 parameter = arg.Substring(2);
81
82 string[] value = parameter.Split("=".ToCharArray(), 2);
83
84 if (this.PreprocessorVariables.ContainsKey(value[0]))
85 {
86 Messaging.Instance.OnMessage(WixErrors.DuplicateVariableDefinition(value[0], (1 == value.Length) ? String.Empty : value[1], this.PreprocessorVariables[value[0]]));
87 break;
88 }
89
90 if (1 == value.Length)
91 {
92 this.PreprocessorVariables.Add(value[0], String.Empty);
93 }
94 else
95 {
96 this.PreprocessorVariables.Add(value[0], value[1]);
97 }
98 }
99 else if ('I' == parameter[0])
100 {
101 this.IncludeSearchPaths.Add(parameter.Substring(1));
102 }
103 else if ("ext" == parameter)
104 {
105 if (!CommandLine.IsValidArg(args, ++i))
106 {
107 Messaging.Instance.OnMessage(WixErrors.TypeSpecificationForExtensionRequired("-ext"));
108 break;
109 }
110 else
111 {
112 this.Extensions.Add(args[i]);
113 }
114 }
115 else if ("nologo" == parameter)
116 {
117 this.ShowLogo = false;
118 }
119 else if ("o" == parameter || "out" == parameter)
120 {
121 string path = CommandLine.GetFileOrDirectory(parameter, args, ++i);
122
123 if (!String.IsNullOrEmpty(path))
124 {
125 if (path.EndsWith("\\", StringComparison.Ordinal) || path.EndsWith("/", StringComparison.Ordinal))
126 {
127 this.OutputFolder = path;
128 }
129 else
130 {
131 this.OutputFile = path;
132 }
133 }
134 else
135 {
136 break;
137 }
138 }
139 else if ("pedantic" == parameter)
140 {
141 this.ShowPedanticMessages = true;
142 }
143 else if ("arch" == parameter)
144 {
145 if (!CommandLine.IsValidArg(args, ++i))
146 {
147 Messaging.Instance.OnMessage(WixErrors.InvalidPlatformParameter(parameter, String.Empty));
148 break;
149 }
150
151 if (String.Equals(args[i], "intel", StringComparison.OrdinalIgnoreCase) || String.Equals(args[i], "x86", StringComparison.OrdinalIgnoreCase))
152 {
153 this.Platform = Platform.X86;
154 }
155 else if (String.Equals(args[i], "x64", StringComparison.OrdinalIgnoreCase))
156 {
157 this.Platform = Platform.X64;
158 }
159 else if (String.Equals(args[i], "intel64", StringComparison.OrdinalIgnoreCase) || String.Equals(args[i], "ia64", StringComparison.OrdinalIgnoreCase))
160 {
161 this.Platform = Platform.IA64;
162 }
163 else if (String.Equals(args[i], "arm", StringComparison.OrdinalIgnoreCase))
164 {
165 this.Platform = Platform.ARM;
166 }
167 else
168 {
169 Messaging.Instance.OnMessage(WixErrors.InvalidPlatformParameter(parameter, args[i]));
170 }
171 }
172 else if ('p' == parameter[0])
173 {
174 string file = parameter.Substring(1);
175 this.PreprocessFile = String.IsNullOrEmpty(file) ? "con:" : file;
176 }
177 else if (parameter.StartsWith("sw", StringComparison.Ordinal))
178 {
179 string paramArg = parameter.Substring(2);
180 try
181 {
182 if (0 == paramArg.Length)
183 {
184 Messaging.Instance.SuppressAllWarnings = true;
185 }
186 else
187 {
188 int suppressWarning = Convert.ToInt32(paramArg, CultureInfo.InvariantCulture.NumberFormat);
189 if (0 >= suppressWarning)
190 {
191 Messaging.Instance.OnMessage(WixErrors.IllegalSuppressWarningId(paramArg));
192 }
193
194 Messaging.Instance.SuppressWarningMessage(suppressWarning);
195 }
196 }
197 catch (FormatException)
198 {
199 Messaging.Instance.OnMessage(WixErrors.IllegalSuppressWarningId(paramArg));
200 }
201 catch (OverflowException)
202 {
203 Messaging.Instance.OnMessage(WixErrors.IllegalSuppressWarningId(paramArg));
204 }
205 }
206 else if (parameter.StartsWith("wx", StringComparison.Ordinal))
207 {
208 string paramArg = parameter.Substring(2);
209 try
210 {
211 if (0 == paramArg.Length)
212 {
213 Messaging.Instance.WarningsAsError = true;
214 }
215 else
216 {
217 int elevateWarning = Convert.ToInt32(paramArg, CultureInfo.InvariantCulture.NumberFormat);
218 if (0 >= elevateWarning)
219 {
220 Messaging.Instance.OnMessage(WixErrors.IllegalWarningIdAsError(paramArg));
221 }
222
223 Messaging.Instance.ElevateWarningMessage(elevateWarning);
224 }
225 }
226 catch (FormatException)
227 {
228 Messaging.Instance.OnMessage(WixErrors.IllegalWarningIdAsError(paramArg));
229 }
230 catch (OverflowException)
231 {
232 Messaging.Instance.OnMessage(WixErrors.IllegalWarningIdAsError(paramArg));
233 }
234 }
235 else if ("v" == parameter)
236 {
237 Messaging.Instance.ShowVerboseMessages = true;
238 }
239 else if ("?" == parameter || "help" == parameter)
240 {
241 this.ShowHelp = true;
242 break;
243 }
244 else
245 {
246 unprocessed.Add(arg);
247 }
248 }
249 else if ('@' == arg[0])
250 {
251 string[] parsedArgs = CommandLineResponseFile.Parse(arg.Substring(1));
252 string[] unparsedArgs = this.Parse(parsedArgs);
253 unprocessed.AddRange(unparsedArgs);
254 }
255 else
256 {
257 unprocessed.Add(arg);
258 }
259 }
260
261 return unprocessed.ToArray();
262 }
263
264 public string[] ParsePostExtensions(string[] remaining)
265 {
266 List<string> unprocessed = new List<string>();
267 List<string> files = new List<string>();
268
269 for (int i = 0; i < remaining.Length; ++i)
270 {
271 string arg = remaining[i];
272 if (String.IsNullOrEmpty(arg)) // skip blank arguments
273 {
274 continue;
275 }
276
277 if (1 < arg.Length && ('-' == arg[0] || '/' == arg[0]))
278 {
279 unprocessed.Add(arg);
280 }
281 else
282 {
283 files.AddRange(CommandLine.GetFiles(arg, "Source"));
284 }
285 }
286
287 if (0 == files.Count)
288 {
289 this.ShowHelp = true;
290 }
291 else
292 {
293 Dictionary<string, List<string>> sourcesForOutput = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
294 foreach (string file in files)
295 {
296 string sourceFileName = Path.GetFileName(file);
297
298 CompileFile compileFile = new CompileFile();
299 compileFile.SourcePath = Path.GetFullPath(file);
300
301 if (null != this.OutputFile)
302 {
303 compileFile.OutputPath = this.OutputFile;
304 }
305 else if (null != this.OutputFolder)
306 {
307 compileFile.OutputPath = Path.Combine(this.OutputFolder, Path.ChangeExtension(sourceFileName, ".wixobj"));
308 }
309 else
310 {
311 compileFile.OutputPath = Path.ChangeExtension(sourceFileName, ".wixobj");
312 }
313
314 // Track which source files result in a given output file, to ensure we aren't
315 // overwriting the output.
316 List<string> sources;
317 string targetPath = Path.GetFullPath(compileFile.OutputPath);
318 if (!sourcesForOutput.TryGetValue(targetPath, out sources))
319 {
320 sources = new List<string>();
321 sourcesForOutput.Add(targetPath, sources);
322 }
323
324 sources.Add(compileFile.SourcePath);
325
326 this.Files.Add(compileFile);
327 }
328
329 // Show an error for every output file that had more than 1 source file.
330 foreach (KeyValuePair<string, List<string>> outputSources in sourcesForOutput)
331 {
332 if (1 < outputSources.Value.Count)
333 {
334 string sourceFiles = String.Join(", ", outputSources.Value);
335 Messaging.Instance.OnMessage(WixErrors.DuplicateSourcesForOutput(sourceFiles, outputSources.Key));
336 }
337 }
338 }
339
340 return unprocessed.ToArray();
341 }
342 }
343}
diff --git a/src/candle/CandleStrings.Designer.cs b/src/candle/CandleStrings.Designer.cs
new file mode 100644
index 00000000..aaf7254e
--- /dev/null
+++ b/src/candle/CandleStrings.Designer.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.Tools {
4 using System;
5
6
7 /// <summary>
8 /// A strongly-typed resource class, for looking up localized strings, etc.
9 /// </summary>
10 // This class was auto-generated by the StronglyTypedResourceBuilder
11 // class via a tool like ResGen or Visual Studio.
12 // To add or remove a member, edit your .ResX file then rerun ResGen
13 // with the /str option, or rebuild your VS project.
14 [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
15 [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
16 [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
17 internal class CandleStrings {
18
19 private static global::System.Resources.ResourceManager resourceMan;
20
21 private static global::System.Globalization.CultureInfo resourceCulture;
22
23 [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
24 internal CandleStrings() {
25 }
26
27 /// <summary>
28 /// Returns the cached ResourceManager instance used by this class.
29 /// </summary>
30 [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
31 internal static global::System.Resources.ResourceManager ResourceManager {
32 get {
33 if (object.ReferenceEquals(resourceMan, null)) {
34 global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("WixToolset.Tools.CandleStrings", typeof(CandleStrings).Assembly);
35 resourceMan = temp;
36 }
37 return resourceMan;
38 }
39 }
40
41 /// <summary>
42 /// Overrides the current thread's CurrentUICulture property for all
43 /// resource lookups using this strongly typed resource class.
44 /// </summary>
45 [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
46 internal static global::System.Globalization.CultureInfo Culture {
47 get {
48 return resourceCulture;
49 }
50 set {
51 resourceCulture = value;
52 }
53 }
54
55 /// <summary>
56 /// Looks up a localized string similar to Cannot specify more than one source file with single output file. Either specify an output directory for the -out argument by ending the argument with a &apos;\&apos; or remove the -out argument to have the source files compiled to the current directory..
57 /// </summary>
58 internal static string CannotSpecifyMoreThanOneSourceFileForSingleTargetFile {
59 get {
60 return ResourceManager.GetString("CannotSpecifyMoreThanOneSourceFileForSingleTargetFile", resourceCulture);
61 }
62 }
63
64 /// <summary>
65 /// Looks up a localized string similar to usage: candle.exe [-?] [-nologo] [-out outputFile] sourceFile [sourceFile ...] [@responseFile]
66 ///
67 /// -arch set architecture defaults for package, components, etc.
68 /// values: x86, x64, or ia64 (default: x86)
69 /// -d&lt;name&gt;[=&lt;value&gt;] define a parameter for the preprocessor
70 /// -ext &lt;extension&gt; extension assembly or &quot;class, assembly&quot;
71 /// -I&lt;dir&gt; add to include search path
72 /// -nologo skip printing candle logo information
73 /// -o[ut] s [rest of string was truncated]&quot;;.
74 /// </summary>
75 internal static string HelpMessage {
76 get {
77 return ResourceManager.GetString("HelpMessage", resourceCulture);
78 }
79 }
80 }
81}
diff --git a/src/candle/CandleStrings.resx b/src/candle/CandleStrings.resx
new file mode 100644
index 00000000..d77788ca
--- /dev/null
+++ b/src/candle/CandleStrings.resx
@@ -0,0 +1,142 @@
1<?xml version="1.0" encoding="utf-8"?>
2<root>
3 <!--
4 Microsoft ResX Schema
5
6 Version 2.0
7
8 The primary goals of this format is to allow a simple XML format
9 that is mostly human readable. The generation and parsing of the
10 various data types are done through the TypeConverter classes
11 associated with the data types.
12
13 Example:
14
15 ... ado.net/XML headers & schema ...
16 <resheader name="resmimetype">text/microsoft-resx</resheader>
17 <resheader name="version">2.0</resheader>
18 <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
19 <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
20 <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
21 <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
22 <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
23 <value>[base64 mime encoded serialized .NET Framework object]</value>
24 </data>
25 <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
26 <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
27 <comment>This is a comment</comment>
28 </data>
29
30 There are any number of "resheader" rows that contain simple
31 name/value pairs.
32
33 Each data row contains a name, and value. The row also contains a
34 type or mimetype. Type corresponds to a .NET class that support
35 text/value conversion through the TypeConverter architecture.
36 Classes that don't support this are serialized and stored with the
37 mimetype set.
38
39 The mimetype is used for serialized objects, and tells the
40 ResXResourceReader how to depersist the object. This is currently not
41 extensible. For a given mimetype the value must be set accordingly:
42
43 Note - application/x-microsoft.net.object.binary.base64 is the format
44 that the ResXResourceWriter will generate, however the reader can
45 read any of the formats listed below.
46
47 mimetype: application/x-microsoft.net.object.binary.base64
48 value : The object must be serialized with
49 : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
50 : and then encoded with base64 encoding.
51
52 mimetype: application/x-microsoft.net.object.soap.base64
53 value : The object must be serialized with
54 : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
55 : and then encoded with base64 encoding.
56
57 mimetype: application/x-microsoft.net.object.bytearray.base64
58 value : The object must be serialized into a byte array
59 : using a System.ComponentModel.TypeConverter
60 : and then encoded with base64 encoding.
61 -->
62 <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
63 <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
64 <xsd:element name="root" msdata:IsDataSet="true">
65 <xsd:complexType>
66 <xsd:choice maxOccurs="unbounded">
67 <xsd:element name="metadata">
68 <xsd:complexType>
69 <xsd:sequence>
70 <xsd:element name="value" type="xsd:string" minOccurs="0" />
71 </xsd:sequence>
72 <xsd:attribute name="name" use="required" type="xsd:string" />
73 <xsd:attribute name="type" type="xsd:string" />
74 <xsd:attribute name="mimetype" type="xsd:string" />
75 <xsd:attribute ref="xml:space" />
76 </xsd:complexType>
77 </xsd:element>
78 <xsd:element name="assembly">
79 <xsd:complexType>
80 <xsd:attribute name="alias" type="xsd:string" />
81 <xsd:attribute name="name" type="xsd:string" />
82 </xsd:complexType>
83 </xsd:element>
84 <xsd:element name="data">
85 <xsd:complexType>
86 <xsd:sequence>
87 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
88 <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
89 </xsd:sequence>
90 <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
91 <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
92 <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
93 <xsd:attribute ref="xml:space" />
94 </xsd:complexType>
95 </xsd:element>
96 <xsd:element name="resheader">
97 <xsd:complexType>
98 <xsd:sequence>
99 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
100 </xsd:sequence>
101 <xsd:attribute name="name" type="xsd:string" use="required" />
102 </xsd:complexType>
103 </xsd:element>
104 </xsd:choice>
105 </xsd:complexType>
106 </xsd:element>
107 </xsd:schema>
108 <resheader name="resmimetype">
109 <value>text/microsoft-resx</value>
110 </resheader>
111 <resheader name="version">
112 <value>2.0</value>
113 </resheader>
114 <resheader name="reader">
115 <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
116 </resheader>
117 <resheader name="writer">
118 <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
119 </resheader>
120 <data name="CannotSpecifyMoreThanOneSourceFileForSingleTargetFile" xml:space="preserve">
121 <value>Cannot specify more than one source file with single output file. Either specify an output directory for the -out argument by ending the argument with a '\' or remove the -out argument to have the source files compiled to the current directory.</value>
122 </data>
123 <data name="HelpMessage" xml:space="preserve">
124 <value> usage: candle.exe [-?] [-nologo] [-out outputFile] sourceFile [sourceFile ...] [@responseFile]
125
126 -arch set architecture defaults for package, components, etc.
127 values: x86, x64, ia64 or arm (default: x86)
128 -d&lt;name&gt;[=&lt;value&gt;] define a parameter for the preprocessor
129 -ext &lt;extension&gt; extension assembly or "class, assembly"
130 -I&lt;dir&gt; add to include search path
131 -nologo skip printing candle logo information
132 -o[ut] specify output file (default: write to current directory)
133 -p&lt;file&gt; preprocess to a file (or stdout if no file supplied)
134 -pedantic show pedantic messages
135 -sw[N] suppress all warnings or a specific message ID
136 (example: -sw1009 -sw1103)
137 -v verbose output
138 -wx[N] treat all warnings or a specific message ID as an error
139 (example: -wx1009 -wx1103)
140 -? | -help this help information</value>
141 </data>
142</root> \ No newline at end of file
diff --git a/src/candle/CompileFile.cs b/src/candle/CompileFile.cs
new file mode 100644
index 00000000..682acc21
--- /dev/null
+++ b/src/candle/CompileFile.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.Tools
4{
5 /// <summary>
6 /// Source code file to be compiled.
7 /// </summary>
8 public class CompileFile
9 {
10 /// <summary>
11 /// Path to the source code file.
12 /// </summary>
13 public string SourcePath { get; set; }
14
15 /// <summary>
16 /// Path to compile the output to.
17 /// </summary>
18 public string OutputPath { get; set; }
19 }
20}
diff --git a/src/candle/app.config b/src/candle/app.config
new file mode 100644
index 00000000..71c529fb
--- /dev/null
+++ b/src/candle/app.config
@@ -0,0 +1,9 @@
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
5<configuration>
6 <runtime>
7 <loadFromRemoteSources enabled="true"/>
8 </runtime>
9</configuration>
diff --git a/src/candle/candle.cs b/src/candle/candle.cs
new file mode 100644
index 00000000..f5c65cb1
--- /dev/null
+++ b/src/candle/candle.cs
@@ -0,0 +1,200 @@
1// Copyright (c) .NET Foundation and 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.Tools
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 using System.Runtime.InteropServices;
10 using System.Xml.Linq;
11 using WixToolset.Data;
12 using WixToolset.Extensibility;
13
14 /// <summary>
15 /// The main entry point for candle.
16 /// </summary>
17 public sealed class Candle
18 {
19 private CandleCommandLine commandLine;
20
21 private IEnumerable<IPreprocessorExtension> preprocessorExtensions;
22 private IEnumerable<ICompilerExtension> compilerExtensions;
23 private IEnumerable<IExtensionData> extensionData;
24
25 /// <summary>
26 /// The main entry point for candle.
27 /// </summary>
28 /// <param name="args">Commandline arguments for the application.</param>
29 /// <returns>Returns the application error code.</returns>
30 [MTAThread]
31 public static int Main(string[] args)
32 {
33 AppCommon.PrepareConsoleForLocalization();
34 Messaging.Instance.InitializeAppName("CNDL", "candle.exe").Display += AppCommon.ConsoleDisplayMessage;
35
36 Candle candle = new Candle();
37 return candle.Execute(args);
38 }
39
40 private int Execute(string[] args)
41 {
42 try
43 {
44 string[] unparsed = this.ParseCommandLineAndLoadExtensions(args);
45
46 if (!Messaging.Instance.EncounteredError)
47 {
48 if (this.commandLine.ShowLogo)
49 {
50 AppCommon.DisplayToolHeader();
51 }
52
53 if (this.commandLine.ShowHelp)
54 {
55 Console.WriteLine(CandleStrings.HelpMessage);
56 AppCommon.DisplayToolFooter();
57 }
58 else
59 {
60 foreach (string arg in unparsed)
61 {
62 Messaging.Instance.OnMessage(WixWarnings.UnsupportedCommandLineArgument(arg));
63 }
64
65 this.Run();
66 }
67 }
68 }
69 catch (WixException we)
70 {
71 Messaging.Instance.OnMessage(we.Error);
72 }
73 catch (Exception e)
74 {
75 Messaging.Instance.OnMessage(WixErrors.UnexpectedException(e.Message, e.GetType().ToString(), e.StackTrace));
76 if (e is NullReferenceException || e is SEHException)
77 {
78 throw;
79 }
80 }
81
82 return Messaging.Instance.LastErrorNumber;
83 }
84
85 private string[] ParseCommandLineAndLoadExtensions(string[] args)
86 {
87 this.commandLine = new CandleCommandLine();
88 string[] unprocessed = commandLine.Parse(args);
89 if (Messaging.Instance.EncounteredError)
90 {
91 return unprocessed;
92 }
93
94 // Load extensions.
95 ExtensionManager extensionManager = new ExtensionManager();
96 foreach (string extension in this.commandLine.Extensions)
97 {
98 extensionManager.Load(extension);
99 }
100
101 // Preprocessor extension command line processing.
102 this.preprocessorExtensions = extensionManager.Create<IPreprocessorExtension>();
103 foreach (IExtensionCommandLine pce in this.preprocessorExtensions.Where(e => e is IExtensionCommandLine).Cast<IExtensionCommandLine>())
104 {
105 pce.MessageHandler = Messaging.Instance;
106 unprocessed = pce.ParseCommandLine(unprocessed);
107 }
108
109 // Compiler extension command line processing.
110 this.compilerExtensions = extensionManager.Create<ICompilerExtension>();
111 foreach (IExtensionCommandLine cce in this.compilerExtensions.Where(e => e is IExtensionCommandLine).Cast<IExtensionCommandLine>())
112 {
113 cce.MessageHandler = Messaging.Instance;
114 unprocessed = cce.ParseCommandLine(unprocessed);
115 }
116
117 // Extension data command line processing.
118 this.extensionData = extensionManager.Create<IExtensionData>();
119 foreach (IExtensionCommandLine dce in this.extensionData.Where(e => e is IExtensionCommandLine).Cast<IExtensionCommandLine>())
120 {
121 dce.MessageHandler = Messaging.Instance;
122 unprocessed = dce.ParseCommandLine(unprocessed);
123 }
124
125 return commandLine.ParsePostExtensions(unprocessed);
126 }
127
128 private void Run()
129 {
130 // Create the preprocessor and compiler
131 Preprocessor preprocessor = new Preprocessor();
132 preprocessor.CurrentPlatform = this.commandLine.Platform;
133
134 foreach (string includePath in this.commandLine.IncludeSearchPaths)
135 {
136 preprocessor.IncludeSearchPaths.Add(includePath);
137 }
138
139 foreach (IPreprocessorExtension pe in this.preprocessorExtensions)
140 {
141 preprocessor.AddExtension(pe);
142 }
143
144 Compiler compiler = new Compiler();
145 compiler.ShowPedanticMessages = this.commandLine.ShowPedanticMessages;
146 compiler.CurrentPlatform = this.commandLine.Platform;
147
148 foreach (IExtensionData ed in this.extensionData)
149 {
150 compiler.AddExtensionData(ed);
151 }
152
153 foreach (ICompilerExtension ce in this.compilerExtensions)
154 {
155 compiler.AddExtension(ce);
156 }
157
158 // Preprocess then compile each source file.
159 foreach (CompileFile file in this.commandLine.Files)
160 {
161 // print friendly message saying what file is being compiled
162 Console.WriteLine(file.SourcePath);
163
164 // preprocess the source
165 XDocument sourceDocument;
166 try
167 {
168 if (!String.IsNullOrEmpty(this.commandLine.PreprocessFile))
169 {
170 preprocessor.PreprocessOut = this.commandLine.PreprocessFile.Equals("con:", StringComparison.OrdinalIgnoreCase) ? Console.Out : new StreamWriter(this.commandLine.PreprocessFile);
171 }
172
173 sourceDocument = preprocessor.Process(file.SourcePath, this.commandLine.PreprocessorVariables);
174 }
175 finally
176 {
177 if (null != preprocessor.PreprocessOut && Console.Out != preprocessor.PreprocessOut)
178 {
179 preprocessor.PreprocessOut.Close();
180 }
181 }
182
183 // If we're not actually going to compile anything, move on to the next file.
184 if (null == sourceDocument || !String.IsNullOrEmpty(this.commandLine.PreprocessFile))
185 {
186 continue;
187 }
188
189 // and now we do what we came here to do...
190 Intermediate intermediate = compiler.Compile(sourceDocument);
191
192 // save the intermediate to disk if no errors were found for this source file
193 if (null != intermediate)
194 {
195 intermediate.Save(file.OutputPath);
196 }
197 }
198 }
199 }
200}
diff --git a/src/candle/candle.csproj b/src/candle/candle.csproj
new file mode 100644
index 00000000..cea0cf62
--- /dev/null
+++ b/src/candle/candle.csproj
@@ -0,0 +1,62 @@
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
5<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
6 <PropertyGroup>
7 <ProjectGuid>{956401A5-3C04-4786-9611-B2AEC6207686}</ProjectGuid>
8 <AssemblyName>candle</AssemblyName>
9 <OutputType>Exe</OutputType>
10 <RootNamespace>WixToolset.Tools</RootNamespace>
11 <PlatformTarget>x86</PlatformTarget>
12 </PropertyGroup>
13 <ItemGroup>
14 <Compile Include="AssemblyInfo.cs" />
15 <Compile Include="candle.cs" />
16 <Compile Include="CandleCommandLine.cs" />
17 <Compile Include="CompileFile.cs" />
18 <Compile Include="CandleStrings.Designer.cs">
19 <AutoGen>True</AutoGen>
20 <DesignTime>True</DesignTime>
21 <DependentUpon>CandleStrings.resx</DependentUpon>
22 </Compile>
23 </ItemGroup>
24 <ItemGroup>
25 <EmbeddedNativeResource Include="candle.rc" />
26 </ItemGroup>
27 <ItemGroup>
28 <None Include="app.config" />
29 </ItemGroup>
30 <ItemGroup>
31 <EmbeddedResource Include="CandleStrings.resx">
32 <SubType>Designer</SubType>
33 <Generator>ResXFileCodeGenerator</Generator>
34 <LastGenOutput>CandleStrings.Designer.cs</LastGenOutput>
35 </EmbeddedResource>
36 </ItemGroup>
37 <ItemGroup>
38 <Service Include="{B4F97281-0DBD-4835-9ED8-7DFB966E87FF}" />
39 </ItemGroup>
40 <ItemGroup>
41 <Reference Include="System" />
42 <Reference Include="System.Xml" />
43 <ProjectReference Include="..\..\libs\WixToolset.Data\WixToolset.Data.csproj">
44 <Project>{6a98499e-40ec-4335-9c31-96a2511d47c6}</Project>
45 <Name>WixToolset.Data</Name>
46 </ProjectReference>
47 <ProjectReference Include="..\..\libs\WixToolset.Extensibility\WixToolset.Extensibility.csproj">
48 <Project>{eee88c2a-45a0-4e48-a40a-431a4ba458d8}</Project>
49 <Name>WixToolset.Extensibility</Name>
50 </ProjectReference>
51 <ProjectReference Include="..\wconsole\wconsole.csproj">
52 <Project>{4B2BD779-59F7-4BF1-871C-A75952BCA749}</Project>
53 <Name>wconsole</Name>
54 </ProjectReference>
55 <ProjectReference Include="..\wix\Wix.csproj">
56 <Project>{9E03A94C-C70E-45C6-A269-E737BBD8B319}</Project>
57 <Name>Wix</Name>
58 </ProjectReference>
59 <Reference Include="System.Xml.Linq" />
60 </ItemGroup>
61 <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), wix.proj))\tools\WixBuild.targets" />
62</Project>
diff --git a/src/candle/candle.exe.manifest b/src/candle/candle.exe.manifest
new file mode 100644
index 00000000..b05ab18b
--- /dev/null
+++ b/src/candle/candle.exe.manifest
@@ -0,0 +1,9 @@
1<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
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
5<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
6 <assemblyIdentity name="WixToolset.Tools.Candle" version="2.0.0.0" processorArchitecture="x86" type="win32"/>
7 <description>WiX Toolset Compiler</description>
8 <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"><security><requestedPrivileges><requestedExecutionLevel level="asInvoker" uiAccess="false"/></requestedPrivileges></security></trustInfo>
9</assembly>
diff --git a/src/candle/candle.rc b/src/candle/candle.rc
new file mode 100644
index 00000000..47864811
--- /dev/null
+++ b/src/candle/candle.rc
@@ -0,0 +1,10 @@
1// Copyright (c) .NET Foundation 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#define VER_APP
4#define VER_ORIGINAL_FILENAME "candle.exe"
5#define VER_INTERNAL_NAME "candle"
6#define VER_FILE_DESCRIPTION "WiX Toolset Compiler"
7#include "wix.rc"
8
9#define MANIFEST_RESOURCE_ID 1
10MANIFEST_RESOURCE_ID RT_MANIFEST "candle.exe.manifest"
diff --git a/src/test/WixToolsetTest.BuildTasks/WixToolsetTest.BuildTasks.csproj b/src/test/WixToolsetTest.BuildTasks/WixToolsetTest.BuildTasks.csproj
new file mode 100644
index 00000000..46bbf4cf
--- /dev/null
+++ b/src/test/WixToolsetTest.BuildTasks/WixToolsetTest.BuildTasks.csproj
@@ -0,0 +1,21 @@
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>netcoreapp2.0</TargetFramework>
7 <Description></Description>
8 <Title>WiX Toolset Tests for MSBuild Tasks</Title>
9 </PropertyGroup>
10
11 <PropertyGroup>
12 </PropertyGroup>
13
14 <ItemGroup>
15 <!-- <ProjectReference Include="..\..\WixToolset.BuildTasks\WixToolset.BuildTasks.csproj" /> -->
16 </ItemGroup>
17
18 <ItemGroup>
19 <PackageReference Include="Nerdbank.GitVersioning" Version="2.0.41" PrivateAssets="all" />
20 </ItemGroup>
21</Project>
diff --git a/src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/MsiPackage/MsiPackage.wixproj b/src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/MsiPackage/MsiPackage.wixproj
new file mode 100644
index 00000000..cd956964
--- /dev/null
+++ b/src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/MsiPackage/MsiPackage.wixproj
@@ -0,0 +1,51 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Project ToolsVersion="15.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>7fb77005-c6e0-454f-8c2d-0a4a79c918ba</ProjectGuid>
8 <OutputName>MsiPackage</OutputName>
9 <OutputType>Package</OutputType>
10 <Name>MsiPackage</Name>
11 <RootNamespace>MsiPackage</RootNamespace>
12 </PropertyGroup>
13
14 <PropertyGroup>
15 <WixTargetsPath>..\..\..\..\..\..\build\Debug\net462\wix.targets</WixTargetsPath>
16 <WixTargetsPath>..\..\..\..\..\..\build\Debug\netstandard2.0\wix.targets</WixTargetsPath>
17 <WixTargetsPath>..\..\..\..\..\..\build\publish\wix.targets</WixTargetsPath>
18 <WixTargetsPath>..\..\..\..\..\..\build\Debug\net462\wix.targets</WixTargetsPath>
19 </PropertyGroup>
20
21 <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
22 <PlatformName>$(Platform)</PlatformName>
23 <OutputPath>bin\$(Platform)\$(Configuration)\</OutputPath>
24 <DefineConstants>Debug</DefineConstants>
25 </PropertyGroup>
26 <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
27 <PlatformName>$(Platform)</PlatformName>
28 <OutputPath>bin\$(Platform)\$(Configuration)\</OutputPath>
29 </PropertyGroup>
30 <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' ">
31 <PlatformName>$(Platform)</PlatformName>
32 <OutputPath>bin\$(Platform)\$(Configuration)\</OutputPath>
33 <DefineConstants>Debug</DefineConstants>
34 </PropertyGroup>
35 <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x64' ">
36 <PlatformName>$(Platform)</PlatformName>
37 <OutputPath>bin\$(Platform)\$(Configuration)\</OutputPath>
38 </PropertyGroup>
39 <ItemGroup>
40 <Compile Include="Package.wxs" />
41 <Compile Include="PackageComponents.wxs" />
42 </ItemGroup>
43 <ItemGroup>
44 <EmbeddedResource Include="Package.en-us.wxl" />
45 </ItemGroup>
46 <Import Project="$(WixTargetsPath)" Condition=" '$(WixTargetsPath)' != '' " />
47 <Import Project="$(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\wix.targets" Condition=" '$(WixTargetsPath)' == '' AND Exists('$(MSBuildExtensionsPath32)\Microsoft\WiX\v3.x\wix.targets') " />
48 <Target Name="EnsureWixToolsetInstalled" Condition=" '$(WixTargetsImported)' != 'true' ">
49 <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/." />
50 </Target>
51</Project> \ No newline at end of file
diff --git a/src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/MsiPackage/Package.en-us.wxl b/src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/MsiPackage/Package.en-us.wxl
new file mode 100644
index 00000000..38c12ac1
--- /dev/null
+++ b/src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/MsiPackage/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/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/MsiPackage/Package.wxs b/src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/MsiPackage/Package.wxs
new file mode 100644
index 00000000..d5a5a40d
--- /dev/null
+++ b/src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/MsiPackage/Package.wxs
@@ -0,0 +1,21 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Product Id="*" Name="MsiPackage" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a">
4 <Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />
5
6 <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" />
7 <MediaTemplate />
8
9 <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)">
10 <ComponentGroupRef Id="ProductComponents" />
11 </Feature>
12 </Product>
13
14 <Fragment>
15 <Directory Id="TARGETDIR" Name="SourceDir">
16 <Directory Id="ProgramFilesFolder">
17 <Directory Id="INSTALLFOLDER" Name="MsiPackage" />
18 </Directory>
19 </Directory>
20 </Fragment>
21</Wix>
diff --git a/src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/MsiPackage/PackageComponents.wxs b/src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/MsiPackage/PackageComponents.wxs
new file mode 100644
index 00000000..76a12a9a
--- /dev/null
+++ b/src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/MsiPackage/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="Package.wxs" />
7 </Component>
8 </ComponentGroup>
9 </Fragment>
10</Wix>
diff --git a/src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/MsiPackage/i.txt b/src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/MsiPackage/i.txt
new file mode 100644
index 00000000..53f23c98
--- /dev/null
+++ b/src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/MsiPackage/i.txt
Binary files differ
diff --git a/src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/SimpleMsiPackage.sln b/src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/SimpleMsiPackage.sln
new file mode 100644
index 00000000..2c88704e
--- /dev/null
+++ b/src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/SimpleMsiPackage.sln
@@ -0,0 +1,31 @@
1
2Microsoft Visual Studio Solution File, Format Version 12.00
3# Visual Studio 15
4VisualStudioVersion = 15.0.26730.8
5MinimumVisualStudioVersion = 10.0.40219.1
6Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "MsiPackage", "MsiPackage\MsiPackage.wixproj", "{7FB77005-C6E0-454F-8C2D-0A4A79C918BA}"
7EndProject
8Global
9 GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 Debug|x64 = Debug|x64
11 Debug|x86 = Debug|x86
12 Release|x64 = Release|x64
13 Release|x86 = Release|x86
14 EndGlobalSection
15 GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 {7FB77005-C6E0-454F-8C2D-0A4A79C918BA}.Debug|x64.ActiveCfg = Debug|x64
17 {7FB77005-C6E0-454F-8C2D-0A4A79C918BA}.Debug|x64.Build.0 = Debug|x64
18 {7FB77005-C6E0-454F-8C2D-0A4A79C918BA}.Debug|x86.ActiveCfg = Debug|x86
19 {7FB77005-C6E0-454F-8C2D-0A4A79C918BA}.Debug|x86.Build.0 = Debug|x86
20 {7FB77005-C6E0-454F-8C2D-0A4A79C918BA}.Release|x64.ActiveCfg = Release|x64
21 {7FB77005-C6E0-454F-8C2D-0A4A79C918BA}.Release|x64.Build.0 = Release|x64
22 {7FB77005-C6E0-454F-8C2D-0A4A79C918BA}.Release|x86.ActiveCfg = Release|x86
23 {7FB77005-C6E0-454F-8C2D-0A4A79C918BA}.Release|x86.Build.0 = Release|x86
24 EndGlobalSection
25 GlobalSection(SolutionProperties) = preSolution
26 HideSolutionNode = FALSE
27 EndGlobalSection
28 GlobalSection(ExtensibilityGlobals) = postSolution
29 SolutionGuid = {585B0599-4EB5-4AB6-BC66-819CC78B63D5}
30 EndGlobalSection
31EndGlobal
diff --git a/src/wix/Program.cs b/src/wix/Program.cs
new file mode 100644
index 00000000..3e4fa8f8
--- /dev/null
+++ b/src/wix/Program.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
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using WixToolset.Data;
9
10 /// <summary>
11 /// The main entry point for candle.
12 /// </summary>
13 public sealed class Program
14 {
15 //private IEnumerable<IPreprocessorExtension> preprocessorExtensions;
16 //private IEnumerable<ICompilerExtension> compilerExtensions;
17 //private IEnumerable<IExtensionData> extensionData;
18
19 /// <summary>
20 /// The main entry point for candle.
21 /// </summary>
22 /// <param name="args">Commandline arguments for the application.</param>
23 /// <returns>Returns the application error code.</returns>
24 [MTAThread]
25 public static int Main(string[] args)
26 {
27 var command = CommandLine.ParseStandardCommandLine(args);
28
29 return command?.Execute() ?? 1;
30 }
31
32#if false
33 private static ICommand ParseCommandLine(string[] args)
34 {
35 var next = String.Empty;
36
37 var command = Commands.Unknown;
38 var showLogo = true;
39 var showVersion = false;
40 var outputFolder = String.Empty;
41 var outputFile = String.Empty;
42 var sourceFile = String.Empty;
43 var verbose = false;
44 var files = new List<string>();
45 var defines = new List<string>();
46 var includePaths = new List<string>();
47 var locFiles = new List<string>();
48 var suppressedWarnings = new List<int>();
49
50 var cli = CommandLine.Parse(args, (cmdline, arg) => Enum.TryParse(arg, true, out command), (cmdline, arg) =>
51 {
52 if (cmdline.IsSwitch(arg))
53 {
54 var parameter = arg.TrimStart(new[] { '-', '/' });
55 switch (parameter.ToLowerInvariant())
56 {
57 case "?":
58 case "h":
59 case "help":
60 cmdline.ShowHelp = true;
61 return true;
62
63 case "d":
64 case "define":
65 cmdline.GetNextArgumentOrError(defines);
66 return true;
67
68 case "i":
69 case "includepath":
70 cmdline.GetNextArgumentOrError(includePaths);
71 return true;
72
73 case "loc":
74 cmdline.GetNextArgumentAsFilePathOrError(locFiles, "localization files");
75 return true;
76
77 case "o":
78 case "out":
79 cmdline.GetNextArgumentOrError(ref outputFile);
80 return true;
81
82 case "nologo":
83 showLogo = false;
84 return true;
85
86 case "v":
87 case "verbose":
88 verbose = true;
89 return true;
90
91 case "version":
92 case "-version":
93 showVersion = true;
94 return true;
95 }
96
97 return false;
98 }
99 else
100 {
101 files.AddRange(cmdline.GetFiles(arg, "source code"));
102 return true;
103 }
104 });
105
106 if (showVersion)
107 {
108 return new VersionCommand();
109 }
110
111 if (showLogo)
112 {
113 AppCommon.DisplayToolHeader();
114 }
115
116 if (cli.ShowHelp)
117 {
118 return new HelpCommand(command);
119 }
120
121 switch (command)
122 {
123 case Commands.Build:
124 {
125 var sourceFiles = GatherSourceFiles(files, outputFolder);
126 var variables = GatherPreprocessorVariables(defines);
127 var extensions = cli.ExtensionManager;
128 return new BuildCommand(sourceFiles, variables, locFiles, outputFile);
129 }
130
131 case Commands.Compile:
132 {
133 var sourceFiles = GatherSourceFiles(files, outputFolder);
134 var variables = GatherPreprocessorVariables(defines);
135 return new CompileCommand(sourceFiles, variables);
136 }
137 }
138
139 return null;
140 }
141
142 private static IEnumerable<SourceFile> GatherSourceFiles(IEnumerable<string> sourceFiles, string intermediateDirectory)
143 {
144 var files = new List<SourceFile>();
145
146 foreach (var item in sourceFiles)
147 {
148 var sourcePath = item;
149 var outputPath = Path.Combine(intermediateDirectory, Path.GetFileNameWithoutExtension(sourcePath) + ".wir");
150
151 files.Add(new SourceFile(sourcePath, outputPath));
152 }
153
154 return files;
155 }
156
157 private static IDictionary<string, string> GatherPreprocessorVariables(IEnumerable<string> defineConstants)
158 {
159 var variables = new Dictionary<string, string>();
160
161 foreach (var pair in defineConstants)
162 {
163 string[] value = pair.Split(new[] { '=' }, 2);
164
165 if (variables.ContainsKey(value[0]))
166 {
167 Messaging.Instance.OnMessage(WixErrors.DuplicateVariableDefinition(value[0], (1 == value.Length) ? String.Empty : value[1], variables[value[0]]));
168 continue;
169 }
170
171 variables.Add(value[0], (1 == value.Length) ? String.Empty : value[1]);
172 }
173
174 return variables;
175 }
176#endif
177
178#if false
179 private static ICommand ParseCommandLine2(string[] args)
180 {
181 var command = Commands.Unknown;
182
183 var nologo = false;
184 var outputFolder = String.Empty;
185 var outputFile = String.Empty;
186 var sourceFile = String.Empty;
187 var verbose = false;
188 IReadOnlyList<string> files = Array.Empty<string>();
189 IReadOnlyList<string> defines = Array.Empty<string>();
190 IReadOnlyList<string> includePaths = Array.Empty<string>();
191 IReadOnlyList<int> suppressedWarnings = Array.Empty<int>();
192 IReadOnlyList<string> locFiles = Array.Empty<string>();
193
194 ArgumentSyntax parsed = null;
195 try
196 {
197 parsed = ArgumentSyntax.Parse(args, syntax =>
198 {
199 syntax.HandleErrors = false;
200 //syntax.HandleHelp = false;
201 syntax.ErrorOnUnexpectedArguments = false;
202
203 syntax.DefineCommand("build", ref command, Commands.Build, "Build to final output");
204 syntax.DefineOptionList("d|D|define", ref defines, "Preprocessor name value pairs");
205 syntax.DefineOptionList("I|includePath", ref includePaths, "Include search paths");
206 syntax.DefineOption("nologo", ref nologo, false, "Do not display logo");
207 syntax.DefineOption("o|out", ref outputFile, "Output file");
208 syntax.DefineOptionList("sw", ref suppressedWarnings, false, "Do not display logo");
209 syntax.DefineOption("v|verbose", ref verbose, false, "Display verbose messages");
210 syntax.DefineOptionList("l|loc", ref locFiles, "Localization files to load (.wxl)");
211 syntax.DefineParameterList("files", ref files, "Source files to compile (.wxs)");
212
213 syntax.DefineCommand("preprocess", ref command, Commands.Preprocess, "Preprocess a source files");
214 syntax.DefineOptionList("d|D|define", ref defines, "Preprocessor name value pairs");
215 syntax.DefineOptionList("I|includePath", ref includePaths, "Include search paths");
216 syntax.DefineOption("nologo", ref nologo, false, "Do not display logo");
217 syntax.DefineOption("o|out", ref outputFile, "Output file");
218 syntax.DefineParameter("file", ref sourceFile, "File to process");
219
220 syntax.DefineCommand("compile", ref command, Commands.Compile, "Compile source files");
221 syntax.DefineOptionList("I|includePath", ref includePaths, "Include search paths");
222 syntax.DefineOption("nologo", ref nologo, false, "Do not display logo");
223 syntax.DefineOption("o|out", ref outputFolder, "Output folder");
224 syntax.DefineOptionList("sw", ref suppressedWarnings, false, "Do not display logo");
225 syntax.DefineOption("v|verbose", ref verbose, false, "Display verbose messages");
226 syntax.DefineParameterList("files", ref files, "Source files to compile (.wxs)");
227
228 syntax.DefineCommand("link", ref command, Commands.Link, "Link intermediate files");
229 syntax.DefineOption("nologo", ref nologo, "Do not display logo");
230 syntax.DefineOption("o|out", ref outputFile, "Output intermediate file (.wir)");
231 syntax.DefineParameterList("files", ref files, "Intermediate files to link (.wir)");
232
233 syntax.DefineCommand("bind", ref command, Commands.Bind, "Bind to final output");
234 syntax.DefineOption("nologo", ref nologo, false, "Do not display logo");
235 syntax.DefineOption("o|out", ref outputFile, "Output file");
236 syntax.DefineParameterList("files", ref files, "Intermediate files to bind (.wir)");
237
238 syntax.DefineCommand("version", ref command, Commands.Version, "Display version information");
239 });
240
241 if (IsHelpRequested(parsed))
242 {
243 var width = Console.WindowWidth - 2;
244 var text = parsed.GetHelpText(width < 20 ? 72 : width);
245 Console.Error.WriteLine(text);
246
247 return null;
248 }
249
250 //var u = result.GetArguments();
251
252 //var p = result.GetActiveParameters();
253
254 //var o = result.GetActiveOptions();
255
256 //var a = result.GetActiveArguments();
257
258 //var h = result.GetHelpText();
259
260 //foreach (var x in p)
261 //{
262 // Console.WriteLine("{0}", x.Name);
263 //}
264
265 switch (command)
266 {
267 case Commands.Build:
268 {
269 var sourceFiles = GatherSourceFiles(files, outputFolder);
270 var variables = GatherPreprocessorVariables(defines);
271 return new BuildCommand(sourceFiles, variables, locFiles, outputFile);
272 }
273
274 case Commands.Compile:
275 {
276 var sourceFiles = GatherSourceFiles(files, outputFolder);
277 var variables = GatherPreprocessorVariables(defines);
278 return new CompileCommand(sourceFiles, variables);
279 }
280
281 case Commands.Version:
282 return new VersionCommand();
283 }
284
285 //var preprocessorVariables = this.GatherPreprocessorVariables();
286
287 //foreach (var sourceFile in sourceFiles)
288 //{
289 // var document = preprocessor.Process(sourceFile.SourcePath, preprocessorVariables);
290
291 // var intermediate = compiler.Compile(document);
292
293 // intermediate.Save(sourceFile.OutputPath);
294 //}
295 }
296 //catch (ArgumentSyntaxException e)
297 //{
298 // if (IsHelpRequested(parsed))
299 // {
300 // var width = Console.WindowWidth - 2;
301 // var text = parsed.GetHelpText(width < 20 ? 72 : width);
302 // Console.Error.WriteLine(text);
303 // }
304 // else
305 // {
306 // Console.Error.WriteLine(e.Message);
307 // }
308 //}
309
310 return null;
311 }
312
313 //private static bool IsHelpRequested(ArgumentSyntax syntax)
314 //{
315 // return syntax?.RemainingArguments
316 // .Any(a => String.Equals(a, @"-?", StringComparison.Ordinal) ||
317 // String.Equals(a, @"-h", StringComparison.Ordinal) ||
318 // String.Equals(a, @"--help", StringComparison.Ordinal)) ?? false;
319 //}
320#endif
321
322#if false
323 private int Execute(string[] args)
324 {
325 try
326 {
327 string[] unparsed = this.ParseCommandLineAndLoadExtensions(args);
328
329 if (!Messaging.Instance.EncounteredError)
330 {
331 if (this.commandLine.ShowLogo)
332 {
333 AppCommon.DisplayToolHeader();
334 }
335
336 if (this.commandLine.ShowHelp)
337 {
338 Console.WriteLine(CandleStrings.HelpMessage);
339 AppCommon.DisplayToolFooter();
340 }
341 else
342 {
343 foreach (string arg in unparsed)
344 {
345 Messaging.Instance.OnMessage(WixWarnings.UnsupportedCommandLineArgument(arg));
346 }
347
348 this.Run();
349 }
350 }
351 }
352 catch (WixException we)
353 {
354 Messaging.Instance.OnMessage(we.Error);
355 }
356 catch (Exception e)
357 {
358 Messaging.Instance.OnMessage(WixErrors.UnexpectedException(e.Message, e.GetType().ToString(), e.StackTrace));
359 if (e is NullReferenceException || e is SEHException)
360 {
361 throw;
362 }
363 }
364
365 return Messaging.Instance.LastErrorNumber;
366 }
367
368 private string[] ParseCommandLineAndLoadExtensions(string[] args)
369 {
370 this.commandLine = new CandleCommandLine();
371 string[] unprocessed = commandLine.Parse(args);
372 if (Messaging.Instance.EncounteredError)
373 {
374 return unprocessed;
375 }
376
377 // Load extensions.
378 ExtensionManager extensionManager = new ExtensionManager();
379 foreach (string extension in this.commandLine.Extensions)
380 {
381 extensionManager.Load(extension);
382 }
383
384 // Preprocessor extension command line processing.
385 this.preprocessorExtensions = extensionManager.Create<IPreprocessorExtension>();
386 foreach (IExtensionCommandLine pce in this.preprocessorExtensions.Where(e => e is IExtensionCommandLine).Cast<IExtensionCommandLine>())
387 {
388 pce.MessageHandler = Messaging.Instance;
389 unprocessed = pce.ParseCommandLine(unprocessed);
390 }
391
392 // Compiler extension command line processing.
393 this.compilerExtensions = extensionManager.Create<ICompilerExtension>();
394 foreach (IExtensionCommandLine cce in this.compilerExtensions.Where(e => e is IExtensionCommandLine).Cast<IExtensionCommandLine>())
395 {
396 cce.MessageHandler = Messaging.Instance;
397 unprocessed = cce.ParseCommandLine(unprocessed);
398 }
399
400 // Extension data command line processing.
401 this.extensionData = extensionManager.Create<IExtensionData>();
402 foreach (IExtensionCommandLine dce in this.extensionData.Where(e => e is IExtensionCommandLine).Cast<IExtensionCommandLine>())
403 {
404 dce.MessageHandler = Messaging.Instance;
405 unprocessed = dce.ParseCommandLine(unprocessed);
406 }
407
408 return commandLine.ParsePostExtensions(unprocessed);
409 }
410
411 private void Run()
412 {
413 // Create the preprocessor and compiler
414 Preprocessor preprocessor = new Preprocessor();
415 preprocessor.CurrentPlatform = this.commandLine.Platform;
416
417 foreach (string includePath in this.commandLine.IncludeSearchPaths)
418 {
419 preprocessor.IncludeSearchPaths.Add(includePath);
420 }
421
422 foreach (IPreprocessorExtension pe in this.preprocessorExtensions)
423 {
424 preprocessor.AddExtension(pe);
425 }
426
427 Compiler compiler = new Compiler();
428 compiler.ShowPedanticMessages = this.commandLine.ShowPedanticMessages;
429 compiler.CurrentPlatform = this.commandLine.Platform;
430
431 foreach (IExtensionData ed in this.extensionData)
432 {
433 compiler.AddExtensionData(ed);
434 }
435
436 foreach (ICompilerExtension ce in this.compilerExtensions)
437 {
438 compiler.AddExtension(ce);
439 }
440
441 // Preprocess then compile each source file.
442 foreach (CompileFile file in this.commandLine.Files)
443 {
444 // print friendly message saying what file is being compiled
445 Console.WriteLine(file.SourcePath);
446
447 // preprocess the source
448 XDocument sourceDocument;
449 try
450 {
451 if (!String.IsNullOrEmpty(this.commandLine.PreprocessFile))
452 {
453 preprocessor.PreprocessOut = this.commandLine.PreprocessFile.Equals("con:", StringComparison.OrdinalIgnoreCase) ? Console.Out : new StreamWriter(this.commandLine.PreprocessFile);
454 }
455
456 sourceDocument = preprocessor.Process(file.SourcePath, this.commandLine.PreprocessorVariables);
457 }
458 finally
459 {
460 if (null != preprocessor.PreprocessOut && Console.Out != preprocessor.PreprocessOut)
461 {
462 preprocessor.PreprocessOut.Close();
463 }
464 }
465
466 // If we're not actually going to compile anything, move on to the next file.
467 if (null == sourceDocument || !String.IsNullOrEmpty(this.commandLine.PreprocessFile))
468 {
469 continue;
470 }
471
472 // and now we do what we came here to do...
473 Intermediate intermediate = compiler.Compile(sourceDocument);
474
475 // save the intermediate to disk if no errors were found for this source file
476 if (null != intermediate)
477 {
478 intermediate.Save(file.OutputPath);
479 }
480 }
481 }
482
483 public interface IOptions
484 {
485 IEnumerable<SourceFile> SourceFiles { get; }
486 }
487
488 public class CompilerOptions : IOptions
489 {
490 public CompilerOptions(IEnumerable<SourceFile> sources)
491 {
492 this.SourceFiles = sources;
493 }
494
495 public IEnumerable<SourceFile> SourceFiles { get; private set; }
496 }
497#endif
498 }
499}
diff --git a/src/wix/X_CommandLine.cs b/src/wix/X_CommandLine.cs
new file mode 100644
index 00000000..2c7ceb83
--- /dev/null
+++ b/src/wix/X_CommandLine.cs
@@ -0,0 +1,394 @@
1// Copyright (c) .NET Foundation and 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 System.Text;
10 using System.Text.RegularExpressions;
11 using WixToolset.Extensibility;
12
13 internal class X_CommandLine
14 {
15 private X_CommandLine()
16 {
17 }
18
19 public static string ExpectedArgument { get; } = "expected argument";
20
21 public string ActiveCommand { get; private set; }
22
23 public string[] OriginalArguments { get; private set; }
24
25 public Queue<string> RemainingArguments { get; } = new Queue<string>();
26
27 public ExtensionManager ExtensionManager { get; } = new ExtensionManager();
28
29 public string ErrorArgument { get; set; }
30
31 public bool ShowHelp { get; set; }
32
33 public static X_CommandLine Parse(string commandLineString, Func<X_CommandLine, string, bool> parseArgument)
34 {
35 var arguments = X_CommandLine.ParseArgumentsToArray(commandLineString).ToArray();
36
37 return X_CommandLine.Parse(arguments, null, parseArgument);
38 }
39
40 public static X_CommandLine Parse(string[] commandLineArguments, Func<X_CommandLine, string, bool> parseArgument)
41 {
42 return X_CommandLine.Parse(commandLineArguments, null, parseArgument);
43 }
44
45 public static X_CommandLine Parse(string[] commandLineArguments, Func<X_CommandLine, string, bool> parseCommand, Func<X_CommandLine, string, bool> parseArgument)
46 {
47 var cmdline = new X_CommandLine();
48
49 cmdline.FlattenArgumentsWithResponseFilesIntoOriginalArguments(commandLineArguments);
50
51 cmdline.QueueArgumentsAndLoadExtensions(cmdline.OriginalArguments);
52
53 cmdline.ProcessRemainingArguments(parseArgument, parseCommand);
54
55 return cmdline;
56 }
57
58 /// <summary>
59 /// Get a set of files that possibly have a search pattern in the path (such as '*').
60 /// </summary>
61 /// <param name="searchPath">Search path to find files in.</param>
62 /// <param name="fileType">Type of file; typically "Source".</param>
63 /// <returns>An array of files matching the search path.</returns>
64 /// <remarks>
65 /// This method is written in this verbose way because it needs to support ".." in the path.
66 /// It needs the directory path isolated from the file name in order to use Directory.GetFiles
67 /// or DirectoryInfo.GetFiles. The only way to get this directory path is manually since
68 /// Path.GetDirectoryName does not support ".." in the path.
69 /// </remarks>
70 /// <exception cref="WixFileNotFoundException">Throws WixFileNotFoundException if no file matching the pattern can be found.</exception>
71 public string[] GetFiles(string searchPath, string fileType)
72 {
73 if (null == searchPath)
74 {
75 throw new ArgumentNullException(nameof(searchPath));
76 }
77
78 // Convert alternate directory separators to the standard one.
79 string filePath = searchPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
80 int lastSeparator = filePath.LastIndexOf(Path.DirectorySeparatorChar);
81 string[] files = null;
82
83 try
84 {
85 if (0 > lastSeparator)
86 {
87 files = Directory.GetFiles(".", filePath);
88 }
89 else // found directory separator
90 {
91 files = Directory.GetFiles(filePath.Substring(0, lastSeparator + 1), filePath.Substring(lastSeparator + 1));
92 }
93 }
94 catch (DirectoryNotFoundException)
95 {
96 // Don't let this function throw the DirectoryNotFoundException. This exception
97 // occurs for non-existant directories and invalid characters in the searchPattern.
98 }
99 catch (ArgumentException)
100 {
101 // Don't let this function throw the ArgumentException. This exception
102 // occurs in certain situations such as when passing a malformed UNC path.
103 }
104 catch (IOException)
105 {
106 throw new WixFileNotFoundException(searchPath, fileType);
107 }
108
109 if (null == files || 0 == files.Length)
110 {
111 throw new WixFileNotFoundException(searchPath, fileType);
112 }
113
114 return files;
115 }
116
117 /// <summary>
118 /// Validates that a valid switch (starts with "/" or "-"), and returns a bool indicating its validity
119 /// </summary>
120 /// <param name="args">The list of strings to check.</param>
121 /// <param name="index">The index (in args) of the commandline parameter to be validated.</param>
122 /// <returns>True if a valid switch exists there, false if not.</returns>
123 public bool IsSwitch(string arg)
124 {
125 return arg != null && ('/' == arg[0] || '-' == arg[0]);
126 }
127
128 /// <summary>
129 /// Validates that a valid switch (starts with "/" or "-"), and returns a bool indicating its validity
130 /// </summary>
131 /// <param name="args">The list of strings to check.</param>
132 /// <param name="index">The index (in args) of the commandline parameter to be validated.</param>
133 /// <returns>True if a valid switch exists there, false if not.</returns>
134 public bool IsSwitchAt(IEnumerable<string> args, int index)
135 {
136 var arg = args.ElementAtOrDefault(index);
137 return IsSwitch(arg);
138 }
139
140 public void GetNextArgumentOrError(ref string arg)
141 {
142 this.TryGetNextArgumentOrError(out arg);
143 }
144
145 public void GetNextArgumentOrError(IList<string> args)
146 {
147 if (this.TryGetNextArgumentOrError(out var arg))
148 {
149 args.Add(arg);
150 }
151 }
152
153 public void GetNextArgumentAsFilePathOrError(IList<string> args, string fileType)
154 {
155 if (this.TryGetNextArgumentOrError(out var arg))
156 {
157 foreach (var path in this.GetFiles(arg, fileType))
158 {
159 args.Add(path);
160 }
161 }
162 }
163
164 public bool TryGetNextArgumentOrError(out string arg)
165 {
166 if (this.RemainingArguments.TryDequeue(out arg) && !this.IsSwitch(arg))
167 {
168 return true;
169 }
170
171 this.ErrorArgument = arg ?? X_CommandLine.ExpectedArgument;
172
173 return false;
174 }
175
176 private void FlattenArgumentsWithResponseFilesIntoOriginalArguments(string[] commandLineArguments)
177 {
178 List<string> args = new List<string>();
179
180 foreach (var arg in commandLineArguments)
181 {
182 if ('@' == arg[0])
183 {
184 var responseFileArguments = X_CommandLine.ParseResponseFile(arg.Substring(1));
185 args.AddRange(responseFileArguments);
186 }
187 else
188 {
189 args.Add(arg);
190 }
191 }
192
193 this.OriginalArguments = args.ToArray();
194 }
195
196 private void QueueArgumentsAndLoadExtensions(string[] args)
197 {
198 for (var i = 0; i < args.Length; ++i)
199 {
200 var arg = args[i];
201
202 if ("-ext" == arg || "/ext" == arg)
203 {
204 if (!this.IsSwitchAt(args, ++i))
205 {
206 this.ExtensionManager.Load(args[i]);
207 }
208 else
209 {
210 this.ErrorArgument = arg;
211 break;
212 }
213 }
214 else
215 {
216 this.RemainingArguments.Enqueue(arg);
217 }
218 }
219 }
220
221 private void ProcessRemainingArguments(Func<X_CommandLine, string, bool> parseArgument, Func<X_CommandLine, string, bool> parseCommand)
222 {
223 var extensions = this.ExtensionManager.Create<IExtensionCommandLine>();
224
225 while (!this.ShowHelp &&
226 String.IsNullOrEmpty(this.ErrorArgument) &&
227 this.RemainingArguments.TryDequeue(out var arg))
228 {
229 if (String.IsNullOrWhiteSpace(arg)) // skip blank arguments.
230 {
231 continue;
232 }
233
234 if ('-' == arg[0] || '/' == arg[0])
235 {
236 if (!parseArgument(this, arg) &&
237 !this.TryParseCommandLineArgumentWithExtension(arg, extensions))
238 {
239 this.ErrorArgument = arg;
240 }
241 }
242 else if (String.IsNullOrEmpty(this.ActiveCommand) && parseCommand != null) // First non-switch must be the command, if commands are supported.
243 {
244 if (parseCommand(this, arg))
245 {
246 this.ActiveCommand = arg;
247 }
248 else
249 {
250 this.ErrorArgument = arg;
251 }
252 }
253 else if (!this.TryParseCommandLineArgumentWithExtension(arg, extensions) &&
254 !parseArgument(this, arg))
255 {
256 this.ErrorArgument = arg;
257 }
258 }
259 }
260
261 private bool TryParseCommandLineArgumentWithExtension(string arg, IEnumerable<IExtensionCommandLine> extensions)
262 {
263 foreach (var extension in extensions)
264 {
265 //if (extension.ParseArgument(this, arg))
266 //{
267 // return true;
268 //}
269 }
270
271 return false;
272 }
273
274 /// <summary>
275 /// Parses a response file.
276 /// </summary>
277 /// <param name="responseFile">The file to parse.</param>
278 /// <returns>The array of arguments.</returns>
279 private static List<string> ParseResponseFile(string responseFile)
280 {
281 string arguments;
282
283 using (StreamReader reader = new StreamReader(responseFile))
284 {
285 arguments = reader.ReadToEnd();
286 }
287
288 return X_CommandLine.ParseArgumentsToArray(arguments);
289 }
290
291 /// <summary>
292 /// Parses an argument string into an argument array based on whitespace and quoting.
293 /// </summary>
294 /// <param name="arguments">Argument string.</param>
295 /// <returns>Argument array.</returns>
296 private static List<string> ParseArgumentsToArray(string arguments)
297 {
298 // Scan and parse the arguments string, dividing up the arguments based on whitespace.
299 // Unescaped quotes cause whitespace to be ignored, while the quotes themselves are removed.
300 // Quotes may begin and end inside arguments; they don't necessarily just surround whole arguments.
301 // Escaped quotes and escaped backslashes also need to be unescaped by this process.
302
303 // Collects the final list of arguments to be returned.
304 var argsList = new List<string>();
305
306 // True if we are inside an unescaped quote, meaning whitespace should be ignored.
307 var insideQuote = false;
308
309 // Index of the start of the current argument substring; either the start of the argument
310 // or the start of a quoted or unquoted sequence within it.
311 var partStart = 0;
312
313 // The current argument string being built; when completed it will be added to the list.
314 var arg = new StringBuilder();
315
316 for (int i = 0; i <= arguments.Length; i++)
317 {
318 if (i == arguments.Length || (Char.IsWhiteSpace(arguments[i]) && !insideQuote))
319 {
320 // Reached a whitespace separator or the end of the string.
321
322 // Finish building the current argument.
323 arg.Append(arguments.Substring(partStart, i - partStart));
324
325 // Skip over the whitespace character.
326 partStart = i + 1;
327
328 // Add the argument to the list if it's not empty.
329 if (arg.Length > 0)
330 {
331 argsList.Add(X_CommandLine.ExpandEnvVars(arg.ToString()));
332 arg.Length = 0;
333 }
334 }
335 else if (i > partStart && arguments[i - 1] == '\\')
336 {
337 // Check the character following an unprocessed backslash.
338 // Unescape quotes, and backslashes followed by a quote.
339 if (arguments[i] == '"' || (arguments[i] == '\\' && arguments.Length > i + 1 && arguments[i + 1] == '"'))
340 {
341 // Unescape the quote or backslash by skipping the preceeding backslash.
342 arg.Append(arguments.Substring(partStart, i - 1 - partStart));
343 arg.Append(arguments[i]);
344 partStart = i + 1;
345 }
346 }
347 else if (arguments[i] == '"')
348 {
349 // Add the quoted or unquoted section to the argument string.
350 arg.Append(arguments.Substring(partStart, i - partStart));
351
352 // And skip over the quote character.
353 partStart = i + 1;
354
355 insideQuote = !insideQuote;
356 }
357 }
358
359 return argsList;
360 }
361
362 /// <summary>
363 /// Expand enxironment variables contained in the passed string
364 /// </summary>
365 /// <param name="arguments"></param>
366 /// <returns></returns>
367 private static string ExpandEnvVars(string arguments)
368 {
369 var id = Environment.GetEnvironmentVariables();
370
371 var regex = new Regex("(?<=\\%)(?:[\\w\\.]+)(?=\\%)");
372 MatchCollection matches = regex.Matches(arguments);
373
374 string value = String.Empty;
375 for (int i = 0; i <= (matches.Count - 1); i++)
376 {
377 try
378 {
379 var key = matches[i].Value;
380 regex = new Regex(String.Concat("(?i)(?:\\%)(?:", key, ")(?:\\%)"));
381 value = id[key].ToString();
382 arguments = regex.Replace(arguments, value);
383 }
384 catch (NullReferenceException)
385 {
386 // Collapse unresolved environment variables.
387 arguments = regex.Replace(arguments, value);
388 }
389 }
390
391 return arguments;
392 }
393 }
394}
diff --git a/src/wix/wix.csproj b/src/wix/wix.csproj
new file mode 100644
index 00000000..88017e3f
--- /dev/null
+++ b/src/wix/wix.csproj
@@ -0,0 +1,31 @@
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>netcoreapp2.0</TargetFramework>
7 <OutputType>Exe</OutputType>
8 <Description></Description>
9 <Title>WiX Toolset Compiler</Title>
10 </PropertyGroup>
11
12 <PropertyGroup>
13 <NoWarn>NU1701</NoWarn>
14 </PropertyGroup>
15
16 <ItemGroup>
17 <ProjectReference Include="..\WixToolset.Core\WixToolset.Core.csproj" />
18
19 <ProjectReference Include="$(WixToolsetRootFolder)\Data\src\WixToolset.Data\WixToolset.Data.csproj" Condition=" '$(Configuration)' == 'Debug' And Exists('$(WixToolsetRootFolder)\Data\src\WixToolset.Data\WixToolset.Data.csproj') " />
20 <PackageReference Include="WixToolset.Data" Version="4.0.*" Condition=" '$(Configuration)' == 'Release' Or !Exists('$(WixToolsetRootFolder)\Data\src\WixToolset.Data\WixToolset.Data.csproj') " />
21
22 <ProjectReference Include="$(WixToolsetRootFolder)\Extensibility\src\WixToolset.Extensibility\WixToolset.Extensibility.csproj" Condition=" '$(Configuration)' == 'Debug' And Exists('$(WixToolsetRootFolder)\Extensibility\src\WixToolset.Extensibility\WixToolset.Extensibility.csproj') " />
23 <PackageReference Include="WixToolset.Extensibility" Version="4.0.*" Condition=" '$(Configuration)' == 'Release' Or !Exists('$(WixToolsetRootFolder)\Extensibility\src\WixToolset.Extensibility\WixToolset.Extensibility.csproj') " />
24 </ItemGroup>
25
26 <ItemGroup>
27 <PackageReference Include="Nerdbank.GitVersioning" Version="2.0.41" PrivateAssets="all" />
28 <PackageReference Include="WixBuildTools.MsgGen" Version="4.0.*" PrivateAssets="all" />
29 <PackageReference Include="WixBuildTools.XsdGen" Version="4.0.*" PrivateAssets="all" />
30 </ItemGroup>
31</Project>