@joebigelow / wix-1 / commits / d3d3649a

Initial code commit

Rob Mensching committed Sep 17, 2017 at 15:35 UTC d3d3649a68cb1fa589fdd987a6690dbd5d671f0d
210 files changed +80333 -1
.gitignore
+3
@@ -24,6 +24,9 @@ bld/
24 [Oo]bj/
25 [Ll]og/
26
27 +# VSCode
28 +.vscode/
29 +
30 # Visual Studio 2015 cache/options directory
31 .vs/
32 # Uncomment if you have tasks that create the project's static files in wwwroot
WixToolset.Core.sln new
+36
@@ -0,0 +1,36 @@
1 +Microsoft Visual Studio Solution File, Format Version 12.00
2 +# Visual Studio 15
3 +VisualStudioVersion = 15.0.26730.12
4 +MinimumVisualStudioVersion = 15.0.26124.0
5 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.Core", "src\WixToolset.Core\WixToolset.Core.csproj", "{0B524850-5B9A-472B-85CC-D25920A1DFE1}"
6 +EndProject
7 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "wix", "src\wix\wix.csproj", "{A948BD6C-A430-4927-B7A5-F6FFAE7B01B1}"
8 +EndProject
9 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WixToolset.BuildTasks", "src\WixToolset.BuildTasks\WixToolset.BuildTasks.csproj", "{C199C723-73E1-4E79-AF86-898DBA007FC3}"
10 +EndProject
11 +Global
12 + GlobalSection(SolutionConfigurationPlatforms) = preSolution
13 + Debug|Any CPU = Debug|Any CPU
14 + Release|Any CPU = Release|Any CPU
15 + EndGlobalSection
16 + GlobalSection(ProjectConfigurationPlatforms) = postSolution
17 + {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
18 + {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Debug|Any CPU.Build.0 = Debug|Any CPU
19 + {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Release|Any CPU.ActiveCfg = Release|Any CPU
20 + {0B524850-5B9A-472B-85CC-D25920A1DFE1}.Release|Any CPU.Build.0 = Release|Any CPU
21 + {A948BD6C-A430-4927-B7A5-F6FFAE7B01B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
22 + {A948BD6C-A430-4927-B7A5-F6FFAE7B01B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
23 + {A948BD6C-A430-4927-B7A5-F6FFAE7B01B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
24 + {A948BD6C-A430-4927-B7A5-F6FFAE7B01B1}.Release|Any CPU.Build.0 = Release|Any CPU
25 + {C199C723-73E1-4E79-AF86-898DBA007FC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
26 + {C199C723-73E1-4E79-AF86-898DBA007FC3}.Debug|Any CPU.Build.0 = Debug|Any CPU
27 + {C199C723-73E1-4E79-AF86-898DBA007FC3}.Release|Any CPU.ActiveCfg = Release|Any CPU
28 + {C199C723-73E1-4E79-AF86-898DBA007FC3}.Release|Any CPU.Build.0 = Release|Any CPU
29 + EndGlobalSection
30 + GlobalSection(SolutionProperties) = preSolution
31 + HideSolutionNode = FALSE
32 + EndGlobalSection
33 + GlobalSection(ExtensibilityGlobals) = postSolution
34 + SolutionGuid = {BB8820D5-723D-426D-B4A0-4D221603C5FA}
35 + EndGlobalSection
36 +EndGlobal
appveyor.cmd new
+7
@@ -0,0 +1,7 @@
1 +@setlocal
2 +@pushd %~dp0
3 +
4 +dotnet pack -c Release
5 +
6 +@popd
7 +@endlocal
\ No newline at end of file
appveyor.yml
+1 -1
@@ -9,7 +9,7 @@ environment:
9 NUGET_XMLDOC_MODE: skip
10
11 build_script:
12 -- dotnet pack
12 +- appveyor.cmd
13
14 pull_requests:
15 do_not_increment_build_number: true
nuget.config
+2
@@ -2,7 +2,9 @@
2 <configuration>
3 <packageSources>
4 <clear />
5 + <add key="wixtoolset-core-native" value="https://ci.appveyor.com/nuget/wixtoolset-core-native" />
6 <add key="wixtoolset-data" value="https://ci.appveyor.com/nuget/wixtoolset-data" />
7 + <add key="wixtoolset-dtf" value="https://ci.appveyor.com/nuget/wixtoolset-dtf" />
8 <add key="wixtoolset-extensibility" value="https://ci.appveyor.com/nuget/wixtoolset-extensibility" />
9 <add key="wixbuildtools" value="https://ci.appveyor.com/nuget/wixbuildtools" />
10 <add key="api.nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
src/Directory.Build.props new
+20
@@ -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>
src/WixToolset.BuildTasks/AssemblyInfo.cs new
+7
@@ -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 +
3 +using System.Reflection;
4 +using System.Runtime.InteropServices;
5 +
6 +[assembly: AssemblyCulture("")]
7 +[assembly: ComVisible(false)]
src/WixToolset.BuildTasks/BuildException.cs new
+26
@@ -0,0 +1,26 @@
1 +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2 +
3 +namespace 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 +}
src/WixToolset.BuildTasks/Candle.cs new
+199
@@ -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 +
3 +namespace 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 +}
src/WixToolset.BuildTasks/Common.cs new
+41
@@ -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 +
3 +namespace 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 +}
src/WixToolset.BuildTasks/ConvertReferences.cs new
+93
@@ -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 +
3 +namespace 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 +}
src/WixToolset.BuildTasks/CreateItemAvoidingInference.cs new
+60
@@ -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 +
3 +namespace 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 +}
src/WixToolset.BuildTasks/CreateProjectReferenceDefineConstants.cs new
+270
@@ -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 +
3 +namespace 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 +}
src/WixToolset.BuildTasks/DoIt-Compile.cs new
+192
@@ -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
4 +namespace 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
src/WixToolset.BuildTasks/DoIt.cs new
+233
@@ -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 +
3 +namespace 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 +}
src/WixToolset.BuildTasks/FileSearchHelperMethods.cs new
+60
@@ -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 +
3 +namespace 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 +}
src/WixToolset.BuildTasks/GenerateCompileWithObjectPath.cs new
+146
@@ -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 +
3 +namespace 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 +}
src/WixToolset.BuildTasks/GetCabList.cs new
+87
@@ -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 +
3 +namespace 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 +}
src/WixToolset.BuildTasks/GetLooseFileList.cs new
+230
@@ -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 +
3 +namespace 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 +}
src/WixToolset.BuildTasks/GlobalSuppressions.cs new
+8
@@ -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")]
src/WixToolset.BuildTasks/Insignia.cs new
+118
@@ -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 +
3 +namespace 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 +}
src/WixToolset.BuildTasks/Light.cs new
+488
@@ -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 +
3 +namespace 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 +}
src/WixToolset.BuildTasks/Lit.cs new
+178
@@ -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 +
3 +namespace 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 +}
src/WixToolset.BuildTasks/Pyro.cs new
+140
@@ -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 +
3 +namespace 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 +}
src/WixToolset.BuildTasks/RefreshBundleGeneratedFile.cs new
+132
@@ -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 +
3 +namespace 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 +}
src/WixToolset.BuildTasks/RefreshGeneratedFile.cs new
+118
@@ -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 +
3 +namespace 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 +}
src/WixToolset.BuildTasks/ReplaceString.cs new
+54
@@ -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 +
3 +namespace 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 +}
src/WixToolset.BuildTasks/ResolveWixReferences.cs new
+212
@@ -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 +
3 +namespace 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 +}
src/WixToolset.BuildTasks/TaskBase.cs new
+65
@@ -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 +
3 +namespace 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 +}
src/WixToolset.BuildTasks/Torch.cs new
+159
@@ -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 +
3 +namespace 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 +}
src/WixToolset.BuildTasks/WixAssignCulture.cs new
+231
@@ -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 +
3 +namespace 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 +}
src/WixToolset.BuildTasks/WixCommandLineBuilder.cs new
+180
@@ -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 +
3 +namespace 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 +}
src/WixToolset.BuildTasks/WixToolTask.cs new
+406
@@ -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 +
3 +namespace 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 +}
src/WixToolset.BuildTasks/WixToolset.BuildTasks.csproj new
+38
@@ -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>
src/WixToolset.BuildTasks/heatdirectory.cs new
+103
@@ -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 +
3 +namespace 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 +}
src/WixToolset.BuildTasks/heatfile.cs new
+95
@@ -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 +
3 +namespace 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 +}
src/WixToolset.BuildTasks/heatproject.cs new
+108
@@ -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 +
3 +namespace 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 +}
src/WixToolset.BuildTasks/heattask.cs new
+121
@@ -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 +
3 +namespace 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 +}
src/WixToolset.BuildTasks/redirect.wix.ca.targets new
+11
@@ -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>
src/WixToolset.BuildTasks/redirect.wix.targets new
+11
@@ -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>
src/WixToolset.BuildTasks/wix.ca.targets new
+123
@@ -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>
src/WixToolset.BuildTasks/wix.harvest.targets new
+511
@@ -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>
src/WixToolset.BuildTasks/wix.signing.targets new
+378
@@ -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>
src/WixToolset.BuildTasks/wix.targets new
+1353
@@ -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>
src/WixToolset.Core/AppCommon.cs new
+145
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/AssemblyInfo.cs new
+9
@@ -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 +
3 +using System;
4 +using System.Reflection;
5 +using System.Runtime.InteropServices;
6 +
7 +[assembly: AssemblyCulture("")]
8 +[assembly: CLSCompliant(true)]
9 +[assembly: ComVisible(false)]
src/WixToolset.Core/Bind/BindBundleCommand.cs new
+905
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/BindDatabaseCommand.cs new
+1311
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/BindTransformCommand.cs new
+473
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/Bundles/AutomaticallySlipstreamPatchesCommand.cs new
+112
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/Bundles/BurnCommon.cs new
+378
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/Bundles/BurnReader.cs new
+210
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/Bundles/BurnWriter.cs new
+239
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/Bundles/CreateBootstrapperApplicationManifestCommand.cs new
+241
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/Bundles/CreateBurnManifestCommand.cs new
+686
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/Bundles/CreateContainerCommand.cs new
+68
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/Bundles/GetPackageFacadesCommand.cs new
+62
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs new
+145
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/Bundles/PackageFacade.cs new
+58
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/Bundles/ProcessExePackageCommand.cs new
+33
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/Bundles/ProcessMsiPackageCommand.cs new
+560
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/Bundles/ProcessMspPackageCommand.cs new
+189
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/Bundles/ProcessMsuPackageCommand.cs new
+30
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/Bundles/ProcessPayloadsCommand.cs new
+159
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/Bundles/VerifyPayloadsWithCatalogCommand.cs new
+148
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/Databases/AssignMediaCommand.cs new
+314
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/Databases/BindSummaryInfoCommand.cs new
+135
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/Databases/CabinetBuilder.cs new
+176
@@ -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 +
3 +namespace 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 +
src/WixToolset.Core/Bind/Databases/CabinetWorkItem.cs new
+78
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/Databases/ConfigurationCallback.cs new
+91
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/Databases/CopyTransformDataCommand.cs new
+606
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/Databases/CreateCabinetsCommand.cs new
+489
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/Databases/CreateDeltaPatchesCommand.cs new
+86
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/Databases/CreateSpecialPropertiesCommand.cs new
+68
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/Databases/ExtractMergeModuleFilesCommand.cs new
+225
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/Databases/FileFacade.cs new
+44
@@ -0,0 +1,44 @@
1 +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/Databases/GetFileFacadesCommand.cs new
+148
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/Databases/MergeModulesCommand.cs new
+350
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/Databases/ProcessUncompressedFilesCommand.cs new
+115
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/Databases/UpdateControlTextCommand.cs new
+80
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/Databases/UpdateFileFacadesCommand.cs new
+532
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/DelayedField.cs new
+38
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs new
+83
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs new
+53
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/FileTransfer.cs new
+113
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/GenerateDatabaseCommand.cs new
+335
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs new
+121
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/ResolveFieldsCommand.cs new
+215
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/ResolvedDirectory.cs new
+31
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Bind/TransferFilesCommand.cs new
+214
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Binder.cs new
+686
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/BinderCore.cs new
+58
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/BinderFileManager.cs new
+370
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/BinderFileManagerCore.cs new
+108
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/CLR/Interop/CLRInterop.cs new
+147
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Cab/CabinetFileInfo.cs new
+64
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Cab/Interop/CabInterop.cs new
+316
@@ -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 +
5 +namespace 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
src/WixToolset.Core/Cab/WixCreateCab.cs new
+249
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Cab/WixEnumerateCab.cs new
+89
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Cab/WixExtractCab.cs new
+76
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/CommandLine/BuildCommand.cs new
+100
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/CommandLine/CommandLine.cs new
+592
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/CommandLine/CommandLineHelper.cs new
+255
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/CommandLine/CommandLineOption.cs new
+27
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/CommandLine/CommandLineResponseFile.cs new
+137
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/CommandLine/CompileCommand.cs new
+39
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/CommandLine/HelpCommand.cs new
+30
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/CommandLine/ICommand.cs new
+9
@@ -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 +
3 +namespace WixToolset.Core
4 +{
5 + public interface ICommand
6 + {
7 + int Execute();
8 + }
9 +}
src/WixToolset.Core/CommandLine/VersionCommand.cs new
+17
@@ -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 +
3 +using System;
4 +
5 +namespace 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 +}
src/WixToolset.Core/Common.cs new
+665
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Compiler.cs new
+20667
@@ -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 +
3 +namespace 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;

This file is too large to show in full.

src/WixToolset.Core/CompilerCore.cs new
+1932
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Converter.cs new
+614
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Data/messages.xml new
+4010
@@ -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>
src/WixToolset.Core/Decompiler.cs new
+9357
@@ -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 +
3 +namespace 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 +

This file is too large to show in full.

src/WixToolset.Core/DecompilerCore.cs new
+152
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Differ.cs new
+621
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Exceptions/WixFileNotFoundException.cs new
+52
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Extensibility/AssemblyDefaultHeatExtensionAttribute.cs new
+33
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Extensibility/HarvesterExtension.cs new
+26
@@ -0,0 +1,26 @@
1 +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2 +
3 +namespace 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 +}
src/WixToolset.Core/Extensibility/HeatExtension.cs new
+204
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Extensibility/IHarvesterCore.cs new
+62
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Extensibility/IHeatCore.cs new
+36
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Extensibility/MutatorExtension.cs new
+198
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Extensibility/ValidatorExtension.cs new
+299
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/ExtensionManager.cs new
+110
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/GlobalSuppressions.cs new
+24
@@ -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")]
src/WixToolset.Core/Harvester.cs new
+80
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/HarvesterCore.cs new
+103
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/HeatCore.cs new
+65
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/ICommand.cs new
+9
@@ -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 +
3 +namespace WixToolset
4 +{
5 + internal interface ICommand
6 + {
7 + void Execute();
8 + }
9 +}
src/WixToolset.Core/IfDefEventHandler.cs new
+46
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/IncludedFileEventHandler.cs new
+52
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Inscriber.cs new
+457
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/InspectorCore.cs new
+32
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Librarian.cs new
+95
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Link/ConnectToFeature.cs new
+95
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Link/ConnectToFeatureCollection.cs new
+92
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Link/ConnectToModule.cs new
+54
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Link/ConnectToModuleCollection.cs new
+92
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Link/FindEntrySectionAndLoadSymbolsCommand.cs new
+109
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Link/ReportConflictingSymbolsCommand.cs new
+49
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Link/ResolveReferencesCommand.cs new
+178
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Link/WixGroupingOrdering.cs new
+726
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Linker.cs new
+2434
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Localizer.cs new
+384
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Melter.cs new
+398
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/MelterCore.cs new
+30
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/MergeMod/NativeMethods.cs new
+508
@@ -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
3 +namespace 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
src/WixToolset.Core/Msi/Database.cs new
+303
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Msi/Installer.cs new
+484
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Msi/Interop/MsiInterop.cs new
+697
@@ -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
3 +namespace 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
src/WixToolset.Core/Msi/MsiException.cs new
+78
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Msi/MsiHandle.cs new
+116
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Msi/Record.cs new
+182
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Msi/Session.cs new
+45
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Msi/SummaryInformation.cs new
+323
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Msi/View.cs new
+189
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Mutator.cs new
+115
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Ole32/Storage.cs new
+437
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Patch.cs new
+1284
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/PatchAPI/PatchInterop.cs new
+1002
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/PatchTransform.cs new
+274
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Preprocess/IfContext.cs new
+110
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Preprocessor.cs new
+1598
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/PreprocessorCore.cs new
+560
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/ProcessedStreamEventHandler.cs new
+43
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/ProvidesDependency.cs new
+108
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/ProvidesDependencyCollection.cs new
+64
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/ResolvedVariableEventHandler.cs new
+39
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/SourceFile.cs new
+21
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Unbinder.cs new
+1491
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Util.cs new
+17
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Uuid.cs new
+89
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/Validator.cs new
+401
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/VerifyInterop.cs new
+68
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/WixComponentSearchInfo.cs new
+64
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/WixDistribution.cs new
+109
@@ -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 +
3 +using System;
4 +using System.Diagnostics;
5 +using System.Reflection;
6 +using System.Resources;
7 +
8 +[assembly: NeutralResourcesLanguage("en-US")]
9 +
10 +namespace 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 +}
src/WixToolset.Core/WixDistributionSpecificStrings.Designer.cs new
+77
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/WixDistributionSpecificStrings.resx new
+130
@@ -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>
122 +For 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
src/WixToolset.Core/WixFileSearchInfo.cs new
+54
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/WixGenericMessageEventArgs.cs new
+45
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/WixProductSearchInfo.cs new
+67
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/WixRegistrySearchInfo.cs new
+92
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/WixSearchInfo.cs new
+53
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/WixStrings.Designer.cs new
+442
@@ -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 +
3 +namespace 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 +}
src/WixToolset.Core/WixStrings.resx new
+249
@@ -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
src/WixToolset.Core/WixToolset.Core.csproj new
+46
@@ -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>
src/WixToolset.Core/WixVariableResolver.cs new
+337
@@ -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 +
3 +namespace 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 +}
src/candle/App.ico
Binary files /dev/null and b/src/candle/App.ico differ
src/candle/AssemblyInfo.cs new
+11
@@ -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 +
3 +using System;
4 +using System.Reflection;
5 +using System.Runtime.CompilerServices;
6 +using System.Runtime.InteropServices;
7 +
8 +[assembly: AssemblyTitle("WiX Toolset Compiler")]
9 +[assembly: AssemblyDescription("Compiler")]
10 +[assembly: AssemblyCulture("")]
11 +[assembly: ComVisible(false)]
src/candle/CandleCommandLine.cs new
+343
@@ -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 +
3 +namespace 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 +}
src/candle/CandleStrings.Designer.cs new
+81
@@ -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 +
3 +namespace 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 +}
src/candle/CandleStrings.resx new
+142
@@ -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
src/candle/CompileFile.cs new
+20
@@ -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 +
3 +namespace 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 +}
src/candle/app.config new
+9
@@ -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>
src/candle/candle.cs new
+200
@@ -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 +
3 +namespace 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 +}
src/candle/candle.csproj new
+62
@@ -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>
src/candle/candle.exe.manifest new
+9
@@ -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>
src/candle/candle.rc new
+10
@@ -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
10 +MANIFEST_RESOURCE_ID RT_MANIFEST "candle.exe.manifest"
src/test/WixToolsetTest.BuildTasks/WixToolsetTest.BuildTasks.csproj new
+21
@@ -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>
src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/MsiPackage/MsiPackage.wixproj new
+51
@@ -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
src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/MsiPackage/Package.en-us.wxl new
+11
@@ -0,0 +1,11 @@
1 +<?xml version="1.0" encoding="utf-8"?>
2 +
3 +<!--
4 +This 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>
src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/MsiPackage/Package.wxs new
+21
@@ -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>
src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/MsiPackage/PackageComponents.wxs new
+10
@@ -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>
src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/MsiPackage/i.txt
Binary files /dev/null and b/src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/MsiPackage/i.txt differ
src/test/WixToolsetTest.BuildTasks/data/SimpleMsiPackage/SimpleMsiPackage.sln new
+31
@@ -0,0 +1,31 @@
1 +
2 +Microsoft Visual Studio Solution File, Format Version 12.00
3 +# Visual Studio 15
4 +VisualStudioVersion = 15.0.26730.8
5 +MinimumVisualStudioVersion = 10.0.40219.1
6 +Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "MsiPackage", "MsiPackage\MsiPackage.wixproj", "{7FB77005-C6E0-454F-8C2D-0A4A79C918BA}"
7 +EndProject
8 +Global
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
31 +EndGlobal
src/wix/Program.cs new
+499
@@ -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 +
3 +namespace 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 +}
src/wix/X_CommandLine.cs new
+394
@@ -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 +
3 +namespace 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 +}
src/wix/wix.csproj new
+31
@@ -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>